Images as x-axis labels

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 ggplot2can 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).
GDP per capita with flags for x-axis labels. This was harder to make than it seemed, but I've since added a little more flexibility to it.
The way to go about making your own is as follows;
- Stop and carefully re-evaluate the choices that you've made to bring you to this decision. Are you sure? Okay...
- Save the images (in the correct factor order) into a list (e.g.
pics). - Build your bar graph with categorical x-axis as per normal, using
theme()to remove the labels. Save as an object (e.g.g). - Source the function from this gist (at your own risk... copy and paste if you prefer):
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)) | |
| } |
- Call (or pipe your
ggplotobject to) the function:
g %>% add_images_as_xlabels(pics)## oradd_images_as_xlabels(g, pics) |
- Your image will be re-drawn with your pictures labelling the categories.
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:
- I'm not sure how to put the x-axis title back in at the right position without padding it with a lot of linebreaks (
"\n\n\n\nX-AXIS TITLE"). - I'm not sure how to move the
captionline fromlabs()(assuming you're using the development version ofggplot2on GitHub with @hrbrmstr's excellent annotation additions) so it potentially gets drawn over. - The spacing below the graph is currently arbitrarily set to a few lines more than necessary, but it's a compromise in having an arbitrary number of images loaded at their correct sizes.
- Similarly, I've just expanded the plot range of the original graph by a seemingly okay amount which has worked for the few examples I've tried.
- Using a graph like this places the onus of domain knowledge onto the reader; if you don't know what those flags refer to then this graph is less useful than one with the countries labelled with words. Prettier though.
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/
Images as x-axis labels的更多相关文章
- Axis.Labels.CustomSize
tChart1.Axes.Bottom.Labels.CustomSize = ; //Changes spacing occupied by the axis labels between the ...
- 3D Slicer Hide 3D Cube and Axis Labels Programmatically 使用代码隐藏三维视图中的方框和坐标轴标签
在3D Slicer中,我们如果想在自己写的插件中来修改三维视图中的默认设置的话,那么首先就需要获得三维视图的结点,其类型为vtkMRMLViewNode,获得了这个结点后,我们就可以用代码来修改一系 ...
- TeeChart中Axis的CalcIncrement属性
private void Init() { tChart = new TChart(); panel1.Controls.Add(tChart); tChart.Aspect.View3D = fal ...
- 应用matplotlib绘制地图
#!/usr/bin/env python # -*- coding: utf-8 -*- from math import sqrt import shapefile from matplotlib ...
- 数字格式化函数:Highcharts.numberFormat()
(转)数字格式化函数:Highcharts.numberFormat() 一.函数说明 该函数用于图表中数值的格式化,常见用途有数值精度控制.小数点符.千位符显示控制等. 二.函数使用 1.函 ...
- Highcharts X轴名称太长,如何设置下面这种样式
Highcharts所有的图表除了饼图都有X轴和Y轴,默认情况下,x轴显示在图表的底部,y轴显示在左侧(多个y轴时可以是显示在左右两侧),通过chart.inverted = true 可以让x, ...
- R绘图基础
一,布局 R绘图所占的区域,被分成两大部分,一是外围边距,一是绘图区域. 外围边距可使用par()函数中的oma来进行设置.比如oma=c(4,3,2,1),就是指外围边距分别为下边距:4行,左边距3 ...
- Python图表绘制:matplotlib绘图库入门
matplotlib 是Python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地行制图.而且也可以方便地将它作为绘图控件,嵌入GUI应用程序中. 它的文档相当完备,并 ...
- (转)数字格式化函数:Highcharts.numberFormat()
一.函数说明 该函数用于图表中数值的格式化,常见用途有数值精度控制.小数点符.千位符显示控制等. 二.函数使用 1.函数构造及参数 Highcharts.numberFormat (Numbe ...
- 数据可视化(5)--jqplot经典实例
本来想把实例也写到上篇博客里,最后发现太长了,拆成两篇博客了. 实例来源于官方文档:http://www.jqplot.com/tests/ 这篇博客主要是翻译了官方文档关于经典实例的解说,并在相应代 ...
随机推荐
- C++11 新特性总结
前言 转载请注明出处,感谢! C++11 的新特性 1 变量和基本类型 1.1 long long 类型 扩展精度浮点数,10位有效数字 1.2 列表初始化 初始化的几种不同形式,其中用花括号来初始化 ...
- C#并行编程--命令式数据并行(Parallel.Invoke)
命令式数据并行 Visual C# 2010和.NETFramework4.0提供了很多令人激动的新特性,这些特性是为应对多核处理器和多处理器的复杂性设计的.然而,因为他们包括了完整的新的特性,开 ...
- 随机抽样一致算法(Random sample consensus,RANSAC)
作者:桂. 时间:2017-04-25 21:05:07 链接:http://www.cnblogs.com/xingshansi/p/6763668.html 前言 仍然是昨天的问题,别人问到最小 ...
- html常用的知识点以及混合框架
html中: <hr/> 在页面中创建水平线 例如: <p> p标签是定义段落 > alt 作为可预备可替换信息,在无法加载图片时显示文字信息 定义htm ...
- (函数封装)domReady
一般的我们用window.onload()来判断文档是否加载完成,我们一般采用下面的做法: 当文档加载全部完后,我们在执行代码块(很显然,当需要加载的文档及节点庞大时,用户体验可能会变很差) wind ...
- AspNetCore-MVC实战系列(三)之个人中心
AspNetCore - MVC实战系列目录 . 爱留图网站诞生 . git源码:https://github.com/shenniubuxing3/LovePicture.Web . AspNetC ...
- 蓝桥杯-搭积木-java
/* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2016, 广州科技贸易职业学院信息工程系学生 * All rights reserved. * 文件名称: ...
- web前端技术框架选型参考
一.出发点 随着Web技术的不断发展,前端架构框架.UI框架.构建工具.CSS预处理等层出不穷,各有千秋.太多的框架在形成初期,都曾在web领域 掀起过一场技术浪潮,可有些却仅仅是昙花一现,随着他们用 ...
- [瞎玩儿系列] 使用SQL实现Logistic回归
本来想发在知乎专栏的,但是文章死活提交不了,我也是醉了,于是乎我就干脆提交到CNBLOGS了. 前言 前段时间我们介绍了Logistic的数学原理和C语言实现,而我呢?其实还是习惯使用Matlab进行 ...
- 使用Angular 4、Bootstrap 4、TypeScript和ASP.NET Core开发的Apworks框架案例应用:Task List
最近我为我自己的应用开发框架Apworks设计了一套案例应用程序,并以Apache 2.0开源,开源地址是:https://github.com/daxnet/apworks-examples,目的是 ...
by