我們每天乘坐的地鐵是一個恢弘的藝術作品。
拋開路線、站點的規劃不說,地鐵的線路圖本身就蘊藏了極其精妙的設計。
比如說倫敦地鐵圖。
倫敦的地鐵路線圖圖可謂是地鐵路線圖的鼻祖。多年來,它形成的配色與排版方案,造就了它獨特的外觀和風格,但最令人驚歎的,還是其神來之筆的設計思路。
擁有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發出了這樣的感嘆。