标签:
Open-source software is awesome. If I found that a piece of closed-source software was missing a feature that I wanted, well, bad luck. I probably couldn‘t even tell if was actually missing or if I just didn‘t know about it. When the source is available, maintained, and documented however, things get fun. We can identify, and perhaps fill gaps.
I‘ve thought for a couple of projects which had bar-graphs that it would be neat to have the categories labelled by an icon or a picture. Say, the logo for a company or an illustrative example. Sure, you could fire up GIMP/Inkscape and manually insert them over the top of the text labels (each and every time you re-produce the graph... no thanks) but that‘s not how I operate.
There are probably very few cases for which this is technically a good idea (trying to be a featured author on JunkCharts might very well be one of those reasons). Nonetheless, there are at least a couple of requests for this floating around on stackoverflow; here and here for example. I struggled to find any satisfactory solutions that were in current working order (though perhaps my Google-fu has failed me).
The second link there has a working example, but the big update to ggplot2
breaks that pretty strongly; opts
was deprecated and now element_text()
has a gatekeeper validation routine that prevents any such messing around. The first link however takes a different route. I couldn‘t get that one to work either, but in any case the answer is a year out of date (updates in ggplot2
can easily have broken the gTree
relations), not particularly flexible, and relies on saving intermittent image files for PostScriptTrace
to read back in which I‘m not a fan of (and couldn‘t get to work anyway).
I decided that I perhaps had enough ammunition to hack something together myself (emphasis on hack), and sure enough it seems to have worked (for a limited definition of "worked" with no attached or implied guarantees whatsoever).
The way to go about making your own is as follows;
pics
).theme()
to remove the labels. Save as an object (e.g. g
).
devtools:: source_gist ( "1d1bdb00a7b3910d62bf3eec8a77b4a7" ) |
#‘ Replace categorical x-axis labels with images | |
#‘ | |
#‘ Pipe a ggplot2 graph (with categorical x-axis) into this function with the argument of a list of | |
#‘ pictures (e.g. loaded via readImage) and it builds a new grob with the x-axis categories | |
#‘ now labelled by the images. Solves a problem that you perhaps shouldn‘t have. | |
#‘ | |
#‘ @author J. Carroll, \email{jono@@jcarroll.com.au} | |
#‘ @references \url{http://stackoverflow.com/questions/29939447/icons-as-x-axis-labels-in-r-ggplot2} | |
#‘ | |
#‘ @param g ggplot graph with categorical x axis | |
#‘ @param pics ordered list of pictures to place along x-axis | |
#‘ | |
#‘ @return NULL (called for the side-effect of producing a new grob with images for x-axis labels) | |
#‘ | |
#‘ @import grid | |
#‘ @import ggplot2 | |
#‘ | |
#‘ @export | |
#‘ | |
#‘ @example | |
#‘ \dontrun{ggplot(data, aes(x=factor(x),y=y)) + geom_point() %>% add_images_as_xlabels(pics)} | |
#‘ | |
add_images_as_xlabels <- function(g, pics) { | |
## ensure that the input is a ggplot | |
if(!inherits(g, "ggplot")) stop("Requires a valid ggplot to attach images to.") | |
## extract the components of the ggplot | |
gb <- ggplot_build(gg) | |
xpos <- gb$panel$ranges[[1]]$x.major | |
yrng <- gb$panel$ranges[[1]]$y.range | |
## ensure that the number of pictures to use for labels | |
## matches the number of x categories | |
if(length(xpos) != length(pics)) stop("Detected a different number of pictures to x categories") | |
## create a new grob of the images aligned to the x-axis | |
## at the categorical x positions | |
my_g <- do.call("grobTree", Map(rasterGrob, pics, x=xpos, y=0)) | |
## annotate the original ggplot with the new grob | |
gg <- gg + annotation_custom(my_g, | |
xmin = -Inf, | |
xmax = Inf, | |
ymax = yrng[1] + 0.25*(yrng[2]-yrng[1])/npoints, | |
ymin = yrng[1] - 0.50*(yrng[2]-yrng[1])/npoints) | |
## turn off clipping to allow plotting outside of the plot area | |
gg2 <- ggplotGrob(gg) | |
gg2$layout$clip[gg2$layout$name=="panel"] <- "off" | |
## produce the final, combined grob | |
grid.newpage() | |
grid.draw(gg2) | |
return(invisible(NULL)) | |
} |
ggplot
object to) the function:
g %>% add_images_as_xlabels (pics) ## or add_images_as_xlabels (g, pics) |
Here‘s an example of the code used to generate the GDP per capita image, featuring some fairly brief (for what it does) rvest
scraping (to reiterate; I don‘t want to have to do any of this by hand, so let‘s code it up!).
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?")}) | |
} | |
library(EBImage) ## readImage | |
library(dplyr) ## %>% | |
library(ggplot2) ## devtools::install_github("hadley/ggplot2) | |
library(grid) ## rasterGrob | |
library(ggthemes) ## theme_minimal | |
library(scales) ## comma | |
## create a dummy dataset | |
npoints <- length(flags_name) | |
y <- gdppc$`US$` | |
x <- seq(npoints) | |
dat <- data.frame(x=factor(x), y=y) | |
## load the images from filenames | |
## one day I‘ll remember to make these sorted on save | |
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))))] | |
## save the images into a list | |
for(i in 1:npoints) { | |
pics[[i]] <- EBImage::readImage(image.file[i]) | |
} | |
## create the graph, as per normal | |
## 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 + theme(plot.margin = unit(c(0.5,0.5,5,0.5), "lines"), | |
axis.text.x = element_blank(), | |
axis.text.y = element_text(size=14)) | |
gg <- gg + scale_fill_discrete(guide=FALSE) | |
gg <- gg + theme(plot.background = element_rect(fill="grey90")) | |
gg <- gg + labs(title="GDP per Capita", subtitle=paste0("Top 10 countries\n(", url, ")"), x="", y="$US/1000") | |
gg | |
## insert imags (pics) as x-axis labels | |
## well, at least appear to do so | |
gg %>% add_images_as_xlabels(pics) |
At least a few caveats surround what I did manage to get working, including but not limited to:
"\n\n\n\nX-AXIS TITLE"
).caption
line from labs()
(assuming you‘re using the development version of ggplot2
on GitHub with @hrbrmstr‘s excellent annotation additions) so it potentially gets drawn over.I‘ve no doubt that there must be a better way to do this, but it‘s beyond my understanding of how ggproto
works, and I can‘t seem to bypass element_text
‘s requirements with what I do know. If you would like to help develop this into something more robust then I‘m most interested. Given that it‘s a single function I wasn‘t going to create a package just for this, but I‘m willing to help incorporate it into someone‘s existing package. Hit the comments or ping me on Twitter (@carroll_jono)!
转自:http://jcarroll.com.au/2016/06/02/images-as-x-axis-labels/
标签:
原文地址:http://www.cnblogs.com/payton/p/5555736.html