我们每天乘坐的地铁是一个恢弘的艺术作品。
抛开路线、站点的规划不说,地铁的线路图本身就蕴藏了极其精妙的设计。
比如说伦敦地铁图。
伦敦的地铁路线图图可谓是地铁路线图的鼻祖。多年来,它形成的配色与排版方案,造就了它独特的外观和风格,但最令人惊叹的,还是其神来之笔的设计思路。
拥有13条路线,300多个站点,伦敦地铁的结果复杂至极。一些站点甚至连接了3到4条路线。
怎样才能有效地可视化这个网络?
20世纪初的设计大师Harry Beck交出了一份堪称完美的答卷。今天,一位数据科学家Keith McNulty也想来重新挑战一下这个难题。
这场跨越时空的pk究竟谁更胜一筹呢?赶紧搬来小板凳!
选手A:20世纪天才设计师Harry Beck
20世纪初期,伦敦在过去的半个多世纪内完成了很多雄心勃勃的地下和地面铁路项目,建成了一系列相互连接的复杂路线。
当年,伦敦地铁地图的设计者Harry Beck惊奇地发现,人们乘坐地铁时,他们并不在意自己所在的地理位置——他们真正关心的是乘坐多少站以及在哪里需要换乘。
意识到这点,Beck设计了今日地铁图的初稿,以一种尽可能简单的方法将所有线路绘制成直线,清楚显示线路互相连接的位置。
但Beck也知道,线路的地理方向是也不能在设计中被完全忽略,否则人们无法辨认东南西北——在看地图时,人们需要基本的方向感。
所以,在许多方面,Beck的地图富有设计感又兼顾准确性。
于是,他就创作出了下面这幅地铁路线图👇
请一秒钟记住这张图!
先不急着给出评价,接下来让我们来看看当代选手的作品。
选手B:21世纪数据科学家Keith McNulty
Keith给出了两种方案,分别遵从两种极端的设计原则。
1.完全忽略地理位置:使用“力导向图”决定站点的位置,与实际地理位置信息不相关。
2.完全遵从地理位置:类似于原始早期的Beck地铁图,使用空间坐标将网络叠在伦敦地铁上。
首先,我们需要找一个能够呈现伦敦地铁网络的数据源,包括站点和线路信息。
好消息是,这样的数据集已经在网上公开啦。这份数据甚至包含了地图线路的十六进制颜色编码。顺便说一下,伦敦交通局(Transport for London)发布过一个设计风格指南。
数据集:
https://github.com/nicola/tubemaps/tree/master/datasets
1、完全无地理信息的地铁图方案
现在,我们需要一个能够生成力导向图,并能够轻松进行可视化的算法。
R中 networkD3的forceNetwork()函数就是不二的选择 。
鉴于已有的数据和networkD3函数易于使用,这里不需要写太多复杂的代码。我们先加载库和三个调整过的原始文件。
# load libraries library(networkD3) library(dplyr) # load data stations <- read.csv("stations.csv") connections <- read.csv("connections.csv") lines <- read.csv("lines.csv")
stations 数据框(dataframe)只是一个列表,包含站点名称、每个站的ID号码以及站点的空间坐标(因为我们现在不考虑地理位置,所以暂时不需要该信息)。地铁图总共有302个站点。
lines数据框是包含整个网络13条线路的列表,附带线路的ID号码、线路名称和官方颜色。
connections 数据框表示所有线路任意两个站点之间的连接和连接线路的号码。这里共计有406个连接。
首先,让我们将网络的边变成官方地铁图的配色,并且根据节点所处的线路给节点(即站点)上色。当节点属于多条线路时,我们可以选择ID号码最小的线路为该节点的颜色。这意味着我们需要在stations 和connections 数据框中增加几列,用来获取站点的颜色和连接的颜色。
# bring in line colour into connections dataframe for edge colours connections <- merge(connections, lines) connections <- connections[ ,c("station1", "station2", "line", "colour")] # define a colour for each station using min of line ID connections_unique_lines1 <- connections %>% dplyr::group_by(station1) %>% dplyr::summarise(line = min(line)) colnames(connections_unique_lines1) <- c("station", "line") connections_unique_lines2 <- connections %>% dplyr::group_by(station2) %>% dplyr::summarise(line = min(line)) colnames(connections_unique_lines2) <- c("station", "line") connections_unique_lines3 <- rbind(connections_unique_lines1, connections_unique_lines2) connections_unique_lines <- connections_unique_lines3 %>% dplyr::group_by(station) %>% dplyr::summarise(line = min(line)) # merge line IDs into stations dataframe stations <- dplyr::left_join(stations, connections_unique_lines, by = c("name" = "station")) # merge with lines dataframe to capture line_name stations <- dplyr::left_join(stations, lines, by = "line")
现在大部分工作已经完成。我们只需要对站点的索引从零开始进行编号,以符合的 D3.js格式要求:
# create indices for each name to fit forceNetwork data format connections$source.index <- match(connections$station1, stations$name) - 1 connections$target.index <- match(connections$station2, stations$name) – 1
现在,我们有了绘制网络的所有东西。我们将使用networkD3包中的forceNetwork() 函数。
connections数据框包含了我们所需要的线路,而stations 数据框包含了节点的详细信息。我们使用stations数据框中的line_name 列对站点分组,以便对节点进行颜色编码;我们使用 connections 数据框中的 colour 列对线路进行颜色编码(根据线路的官方颜色)。
我们还需要定义与线路匹配的节点颜色,以及与伦敦地铁图相近的字体。我用的是Gill Sans,虽然它是非官方字体,但是非常接近(Eric Gill实际上为设计了原始地铁图字体的Edward Johnson工作)。
此处是生成网络的代码。
networkD3::forceNetwork(Links = connections, Nodes = stations, Source = "source.index", Target = "target.index", NodeID = "name", Group = "line_name", colourScale = JS('d3.scaleOrdinal().domain(["Bakerloo", "Central", "Circle", "District", "East London", "Hammersmith & City", "Jubilee", "Metropolitan", "Northern", "Piccadilly", "Victoria", "Waterloo & City", "Docklands"]).range(["#AE6017", "#FF0000", "#FFE02B", "#00A166", "#FBAE34", "#F491A8", "#949699", "#91005A", "#000000", "#094FA3", "#0A9CDA", "#88D0C4", "#00A77E"])'), linkColour = as.character(connections$colour), charge = -30, linkDistance = 25, opacity = 1, zoom = T, fontSize = 12, fontFamily = "Gill Sans Nova", legend = TRUE)
最后的结果就是这样啦👇
动态演示可以在这儿查看:
http://rpubs.com/keithmcnulty/tubemap
在绘制这张图时,我们完全不考虑地铁图的地理位置意义,将Beck的设计原则发挥到极致,并借助数据科学方法以最美观的方式将地铁图可视化。
如果你熟悉伦敦的区域分布,你会发现很多奇奇怪怪的事情。比如,现在位于伦敦南部的是艾坪镇(Epping)而非埃塞克斯(Essex)了。这对用户来说是非常糟糕的。
2、地理位置完全精确的地铁图方案
让我们看看另一个极端:完全遵从地理位置。
我们将主要使用ggplot2,当然这里还需要一些其他的库。
# load libraries library(dplyr) library(ggplot2) library(sp) library(rgdal) # load data stations <- read.csv("stations.csv") connections <- read.csv("connections.csv") lines <- read.csv("lines.csv")
为了完全遵从地理位置,我们可以将这些站点直接绘制在一张伦敦地图的相应位置。
在这里我们可以获得一份包含行政区边界的伦敦地铁图文件。
链接:
https://data.london.gov.uk/dataset/statistical-gis-boundary-files-london
首先,将其解压缩到一个名为london-map-data的文件夹中。然后,将数据转换成 ggplot2 可以使用的格式。
# import London borough GIS data london <- rgdal::readOGR(file.path("london-map-data")) sp::proj4string(london) <- sp::CRS("+init=epsg:27700") london.map <- sp::spTransform(london, sp::CRS("+init=epsg:4326"))
有了正确格式的伦敦地图数据,我们便可使用ggplot2绘图。
# plot London boundaries map1 <- ggplot(london.map) + geom_polygon(aes(x = long, y = lat, group = group), fill = "white", colour = "black") map1 <- map1 + labs(x = "Longitude", y = "Latitude", title = "London Tube Routes")
在这张简单的地图上,我们会画上地铁线路和站点:
因为stations 数据框有每个站点的空间坐标信息,画站点就十分方便。要绘制线路,我们需要将每个站点的空间坐标与 connections数据框相匹配。
# get spatial co-ordinates for each station pair in network connections <- connections %>% dplyr::inner_join(stations, by = c('station1' = 'name')) %>% dplyr::rename(x = longitude, y = latitude) %>% dplyr::inner_join(stations, by = c('station2' = 'name')) %>% dplyr::rename(xend = longitude, yend = latitude) connections <- merge(connections, lines)
由于ggplot2的调色板缺少部分十六进制的颜色,我们还需要人工选取与官方配色最接近的线路颜色。
#define line colours linecolours <- c("brown", "yellow", "pink", "grey", "lightblue", "red", "darkgreen", "orange", "maroon", "black", "darkblue", "lightgreen", "#00A77E") names(linecolours) <- lines$line_name
万事俱备,我们只需要在伦敦地图上绘制站点和线路即可——为求真实,这里我们仍旧使用Erci Gill的字体。
# plot network on London map map1 + geom_point(data = stations, aes(x = longitude, y = latitude)) + geom_curve(aes(x = x, y = y, xend = xend, yend = yend, color = line_name), data = connections, curvature = 0.33, size = 1) + scale_color_manual(values = linecolours, name = "Line") + theme(text = element_text(family="Gill Sans Nova"))
更清楚的地铁图:
http://rpubs.com/keithmcnulty/geotubemap
就是这样!
这张路线图虽然完全遵从了地理位置信息,但位处市中心的几个关键站点却挤到了一起,难以分辨,反而是位于郊区的站点得到了更充分的展示。
让我们最后再回过头看看Harry Beck的作品。
这张地铁图既保证了站点信息的清晰可见,又极大程度地还原了站点的相对地理位置。
更厉害的是,合理的信息分布让这一切都能被很好地呈现在一张小纸片上。
Harry的作品也被称为“世上最易识别和最有影响力的交通地图”。在此之后,几乎所有城市的地铁线路图设计方案都遵从了Harry当年的原则。
在折腾了这一通之后,这位数据家Keith McNulty也表示,他输得心服口服了。
“没有什么能替代人类聪明的设计——是的,什么都不行!” Keith McNulty发出了这样的感叹。