Even more images as x-axis labels
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
This is the last update to this strange saga… I hope.
![](https://i0.wp.com/jcarroll.com.au/wp-content/uploads/2018/10/labels.jpg?w=680&ssl=1)
Image labels… Photo: http://www.premierpaper.com/shop/custom-labels/
Easily two of the most popular posts on my blog are this one and this one describing a couple of ways in which I managed to hack together using an image as a category label in a ggplot.
There are likely many people who believe one should never do such a thing, but given the popularity, it seems a lot of people aren’t listening to that. Good on you.
One of these posts was recently shared again by the amazing #rstats amplifier Mara Averick (if you’re not following her on Twitter, you’re missing out) and @baptiste_auguie (the saviour of the previous implementation) mentioned that he had written a ‘hack’ to get chemical symbols as a categorical axis label using tikzDevice
. That package leverages (of which I am very familiar, having written my PhD thesis entirely in
many moons ago) to treat all of the text in an image as potential
commands and produce a working source code which generates the required plot.
The example code is straightforward enough
options(tikzLatexPackages = c(getOption('tikzLatexPackages'),"\\usepackage{acide-amine}\n")) d = data.frame(x=1:10, y=1:10, f=factor(sample(letters[1:2], 10, repl=TRUE))) p <- qplot(x,y,data=d) + theme_bw() + opts(plot.margin = unit(c(1, 1, 5, 1), "lines"), axis.text.x = theme_text(size = 12 * 0.8, lineheight = 0.9, vjust = 10)) + scale_x_continuous(breaks = c(2, 8), labels=c("\\phe{15}", "\\leu{15}")) tikz("annotation.tex",standAlone=T,width=4,height=4) print(p) dev.off()
and produces this
![](https://i2.wp.com/jcarroll.com.au/wp-content/uploads/2018/10/annotation.png?w=680&ssl=1)
annotation.png
This got me curious, though — if it can process arbitrary , could it process a
\\includegraphics
call?
Efficient! If it's arbitrary LaTeX, could the labels just be \includegraphics calls?
— Jonathan Carroll (@carroll_jono) October 11, 2018
Yes, as it turns out.
A quick test showed that it was indeed possible, which only leaves re-implementing the previous posts’ images using this method.
I’ve done so, and the code isn’t particularly shorter than the other method.
library(rvest) | |
## GDP per capita, top 11 countries | |
n_countries <- 11 | |
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:n_countries, ] | |
## 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:n_countries] | |
flags_url <- paste0("http://", sub("[0-9]*px", "200px", sub('\\".*$', "", sub('^.*src=\\"//', "", flags_img)))) | |
flags_name <- sub(".svg.png", ".png", sub(".*(Flag_of)", "\\1", flags_url)) | |
## download each of the flags into a directory | |
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?") | |
} | |
) | |
} | |
# /etc/ImageMagick-6/policy.xml | |
# |
|
# |
|
# https://stackoverflow.com/questions/42928765/convertnot-authorized-aaaa-error-constitute-c-readimage-453 | |
library(magick) | |
## load the images from filenames | |
npoints <- length(flags_name) | |
pics <- vector(mode = "list", length = npoints) | |
image.file <- file.path(getwd(), dir("flags", full.names = TRUE)) | |
image.file <- image.file[order(as.integer(sub("_.*", "", sub("^.*flags/", "", image.file))))] | |
for (i in 1:npoints) { | |
pics[[i]] <- image_read(image.file[i]) | |
} | |
names(pics) <- sub(".svg.png", "", sub(".*Flag_of_", "", image.file)) | |
names(pics)[8] <- "USA" | |
## create a dummy dataset | |
y <- gdppc$`US$` | |
x <- names(pics) | |
dat <- data.frame(x = factor(x, levels = names(pics)), y = y) | |
library(ggplot2) | |
## 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 + 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 11 countries", | |
x = "", y = "\\$US/1000", ## escaping for LaTeX | |
caption = paste0("\\shortstack{\\vspace{-1em}\\scriptsize{Source: ", | |
gsub("_", "\\\\_", url), "}}")) ## escaping for LaTeX | |
## use tikzDevice and include graphicx in the preamble | |
library(tikzDevice) | |
options(tikzLatexPackages = c(getOption("tikzLatexPackages"), "\\usepackage{graphicx}\n")) | |
## rather than typing this out every time, make it a function | |
make_label <- function(labels, files, scale = 0.2, offset = "2em") { | |
paste0("\\shortstack{\\includegraphics[scale=", scale, "]{", | |
files, "}\\\\\\hspace{-", offset, | |
"}\\rotatebox[origin=rt]{45}{\\small{", labels, "}}}") | |
} | |
## make some room for the images and caption | |
gg <- gg + theme(axis.text.x = element_text(size = 5, | |
lineheight = 0.9, | |
vjust = -4), | |
plot.margin = unit(c(5.5, 5.5, 12, 5.5), "points")) | |
## this is where most of the LaTeX magic is happening - the labels | |
gg <- gg + scale_x_discrete(breaks = names(pics), | |
labels = make_label(sub(".png", "", names(pics)), image.file)) | |
## process the object with LaTeX and produce a PDF | |
tikz("xaxis.tex", standAlone = TRUE, width = 4, height = 4) | |
print(gg) | |
dev.off() | |
tools::texi2dvi("xaxis.tex", pdf = TRUE) | |
## convert the resulting PDF to a png using magick | |
image_read_pdf("xaxis.pdf") %>% | |
image_convert(format = "png") %>% | |
image_write("xaxis.png", density = 500) |
Producing nearly the same end result.
![](https://i0.wp.com/jcarroll.com.au/wp-content/uploads/2018/10/xaxis.png?w=680&ssl=1)
tikzDevice result
There are a few differences compared to the previous version(s):
– I had a request for rotating the additional text, which I actually also updated recently, and it seemed to fit better, so I rotated the labels within the command.
– Since all of the text has been rendered via , the fonts are a bit different.
– The rankings have since changed, so I’ve added an 11th to keep Australia in the list.
The component of this also meant that a few changes were necessary in the other labels, such as the dollar sign in the y-axis label, and the underscores throughout (these are considered special characters in
). Lastly, the result of running the
tikz
command is that a .tex
( source code) file is produced. This isn’t quite the plot image file we want. It does however have the commands to generate one. The last steps in the above gist are to process this
.tex
file with . Here I used the
tools::texi2dvi
function, but one could also use a system
command to their installation.
That still only produced a PDF. The last step was to use the magick
package to convert this into an image.
Overall, this is a nice proof of concept, but I don’t think it’s a particularly tidy way of achieving the goal of image axis labels. It does however lay the groundwork for anyone else who decides this might be a useful route to take. Plus I learned a bit more about how tikzDevice
works and got to revisit my knowledge.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.