用500行純前端程式碼在瀏覽器中構建一個Tableau
在Gartner最新的對商務智慧軟體的專業分析報告中,Tableau持續領跑。Microsoft因為PowerBI表現出色也處於領導者象限。而昔日的領導者像SAP,SAS,IBM,MicroStrategy等逐漸被拉開了差距。
Tableau因為其靈活,出色的資料表現已經成為BI領域裡無可爭議的領頭羊。而其資料驅動的視覺化和核心思想是來自於Leland Wilkinson的同樣受到該思想影響的還有R的圖形庫ggplot。
在資料視覺化開源領域裡,大家對百度開發的echarts可謂耳熟能詳,echarts經過多年的發展,其功能確實非常強大,可用出色來形容。但是螞蟻金服開源的基於的語法驅動的視覺化庫
資料載入
第一步是載入資料:
資料載入主要用到了三個庫:
資料通過我存放在GitHub中的csv格式的檔案,以REST請求的方式來載入。下面的程式碼把Axios的Promise變成 async/wait方式。
123456789101112131415 | // Ajax async requestconstrequest={get:url=>{returnnewPromise((resolve,reject)=>{axios.get(url).then(response=>{resolve({data:response.data});}).catch(error=>{resolve({data:error});});});}}; |
封裝好後,我們就可以用request.get()方法傳送REST請求,獲取csv檔案。
1 | let csv=await request.get(url); |
這一步可能會遇到跨域請求的問題,github上的檔案支援跨域。
把資料儲存在一個SQL資料庫中,這樣做的好處是為了下一步做資料準備的時候,可以方便的利用SQL來進行查詢和分析。
JavaScript123456789101112 | classSqlTable{constructor(data){this.data=data;}async query(sql){// following line of code does not run in full page view due to security concern.// const query_str = sql.replace(/(?<=FROM\s+)\w+/, "CSV(?)");constquery_str=sql.replace("table","CSV(?)");returnawait alasql.promise(query_str,[this.data]);}} |
SqlTable是一個對資料表的封裝,把csv資料存在SQL資料庫表中,提供一個query()方法。這裡要做的是把SQL查詢個從 “SELECT * FROM table” 變成 “SELECT * FROM CSV(?)” 表示查詢引數是CSV資料。因為codepen的安全性限制,執行前向查詢的replace語句(這裡的regex表示把前面是“FROM ”詞的替換為CSV(?)的)在full page view下是不能執行的,所以我用了一個更簡單的假定,使用者的表名就是table,這樣做有很多問題,大家如果在codepen之外的環境,可以用註釋掉的程式碼。
然後把”SELECT * FROM table”的查詢結果(JSON Array)用datatable來展示。
12345678910111213141516171819202122232425262728 | functionsanitizeData(jsonArray){let newKey;jsonArray.forEach(function(item){for(key initem){newKey=key.replace(/s/g,"").replace(/./g,"");if(key!=newKey){item[newKey]=item[key];delete item[key];}}});returnjsonArray;}functiondisplayData(tableId,data){// tricky to clone arraylet display_data=JSON.parse(JSON.stringify(data));display_data=sanitizeData(display_data);let columns=[];for(let item indisplay_data[0]){columns.push({data:item,title:item});}$("#"+tableId).DataTable({data:display_data,columns:columns,destroy:true});} |
這一步有兩點要注意:
- 資料中,如果列的名字中有包含點,空格等字元,例如Iris資料集中的Sepal.Length,datatable是無法正常顯示的,這裡要呼叫sanitizeData()方法把列名,也就是JsonArray中Json物件的屬性名中的點和空格去掉。
- sanitizeData()方法會改變輸入物件,所以在傳入之前做了一個深度拷貝,這裡利用JSON的stringfy和parse方法可以對JSON相容的物件有效的拷貝。
這裡要注意,Iris資料集中在datatable中的列名都不顯示點,但實際資料並沒有改變。
資料準備
資料載入完畢,我們來到第二步的資料準備階段。資料準備是資料科學專案最花時間的一步,通常需要對資料進行大量的清洗,變形,抽取等工作,使得資料變得可用。
在這一步我們做了兩件事:
一是顯示資料的一個摘要,讓我們初步瞭解資料的概貌,為進一步的資料變形和處理做好準備。
這個是Iris資料集的摘要:
1234567891011121314151617181920212223242526272829303132 | functionisString(o){returntypeofo=="string"||(typeofo=="object"&&o.constructor===String);}functionsummaryData(data){let summary={};summary.count=data.length;summary.fields=[];for(letpindata[0]){let field={};field.name=p;if(isString(data[0][p])){field.type="string";}else{field.type="number";}summary.fields.push(field);}for(letfof summary.fields){if(f.type=="number"){f.max=d3.max(data,x=>x[f.name]);f.min=d3.min(data,x=>x[f.name]);f.mean=d3.mean(data,x=>x[f.name]);f.median=d3.median(data,x=>x[f.name]);f.deviation=d3.deviation(data,x=>x[f.name]);}else{f.values=Array.from(newSet(data.map(x=>x[f.name])));}}returnsummary;} |
這裡我們利用資料的型別判斷出每一個欄位是數值型還是字元型。對於字元型的欄位,我們利用JS6的Set來獲得所有的Unique資料。對於數值型,我們利用d3的max,min,mean,median,deviation方法計算出對應的最大值,最小值,平均數,中位數和偏差。
另一個就是利用SQL查詢來對資料進行進一步的加工。
上圖的例子中我們利用限制條件得到一個Iris資料的子集。
另外G2還提供了Dataset的功能:
資料處理是一個比較大的話題,我們的目標是利用盡可能少的程式碼完成一個數據分析的工具,所以這一步僅僅是利用alasql提供的SQL查詢來處理資料。
資料展示
資料處理好後就是我們的核心內容,資料展示了。
這一步主要是利用select2提供的選擇控制元件構建圖形語法來驅動資料展示。如上圖所示,對應的G2程式碼圖形語法為:
123456 | g2chart.facet('rect',{fields:['Admit','Dept'],eachView(view){view.interval().position('Gender*Freq').color('Gender').label('Freq');}}); |
圖形語法主要包含以下幾個主要的元素:
幾何標記 Geometry
幾何標記定義了使用什麼樣的幾何圖形來表徵資料。G2現在支援如下這些幾何標記:
geom 型別 | 描述 |
---|---|
point |
點,用於繪製各種點圖。 |
path |
路徑,無序的點連線而成的一條線,常用於路徑圖的繪製。 |
line |
線,點按照 x 軸連線成一條線,構成線圖。 |
area |
填充線圖跟座標系之間構成區域圖,也可以指定上下範圍。 |
interval |
使用矩形或者弧形,用面積來表示大小關係的圖形,一般構成柱狀圖、餅圖等圖表。 |
polygon |
多邊形,可以用於構建色塊圖、地圖等圖表型別。 |
edge |
兩個點之間的連結,用於構建樹圖和關係圖中的邊、流程圖中的連線線。 |
schema |
自定義圖形,用於構建箱型圖(或者稱箱須圖)、蠟燭圖(或者稱 K 線圖、股票圖)等圖表。 |
heatmap |
用於熱力圖的繪製。 |
這裡要注意,intervalstack是官方支援的,但是文件沒有提到,在閱讀G2的API文件的時候,我也發現文件講的不是很清楚,有很多地方沒有講清楚如何使用API。這也是開源軟體值得改進的地方。
圖形屬性 Attributes
圖形屬性對應視覺編碼中的不同元素,大家可以參考我的另一部落格 資料視覺化中的視覺屬性 。
圖形屬性主要有以下幾種。
- position:位置,二維座標系內對映至 x 軸、y 軸;
- color:顏色,包含了色調、飽和度和亮度;
- size:大小,不同的幾何標記對大小的定義有差異;
- shape:形狀,幾何標記的形狀決定了某個具體圖表型別的表現形式,例如點圖,可以使用圓點、三角形、圖片表示;線圖可以有折線、曲線、點線等表現形式;
- opacity:透明度,圖形的透明度,這個屬性從某種意義上來說可以使用顏色代替,需要使用 ‘rgba’ 的形式,所以在 G2 中我們獨立出來。
在構建語法的時候,我們把圖形屬性繫結一個或者多個數據欄位。
座標系 Coordinates
座標系是將兩種位置標度結合在一起組成的 2 維定位系統,描述了資料是如何對映到圖形所在的平面。
G2提供了以下幾種座標系:
coordType | 說明 |
---|---|
rect |
直角座標系,目前僅支援二維,由 x, y 兩個互相垂直的座標軸構成。 |
polar |
極座標系,由角度和半徑 2 個維度構成。 |
theta |
一種特殊的極座標系,半徑長度固定,僅僅將資料對映到角度,常用於餅圖的繪製。 |
helix |
螺旋座標系,基於阿基米德螺旋線。 |
分面 Facet
分面,將一份資料按照某個維度分隔成若干子集,然後建立一個圖表的矩陣,將每一個數據子集繪製到圖形矩陣的窗格中。分面其實提供了兩個功能:
- 按照指定的維度劃分資料集;
- 對圖表進行排版。
G2支援以下的分面型別:
分面型別 | 說明 |
---|---|
rect | 預設型別,指定 2 個維度作為行列,形成圖表的矩陣。 |
list | 指定一個維度,可以指定一行有幾列,超出自動換行。 |
指定一個維度,沿著圓分佈。 | |
tree | 指定多個維度,每個維度作為樹的一級,展開多層圖表。 |
指定一個維度,形成映象圖表。 | |
指定一個維度,形成矩陣分面。 |
注意,在我的程式碼中,為了簡化使用,只支援list和rect,當繫結一個欄位的時候用list,繫結兩個欄位的時候用rect。
除了上面提到的元素,當然還有許多其它的元素我們沒有包含和支援,例如:座標軸,圖例,提示等等。
關於圖形的語法的更多內容,請參考這裡。
生成圖形語法的核心程式碼如下:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 | functiongetFacet(faced,grammarScript){< |