1. 程式人生 > >用500行純前端程式碼在瀏覽器中構建一個Tableau

用500行純前端程式碼在瀏覽器中構建一個Tableau

在Gartner最新的對商務智慧軟體的專業分析報告中,Tableau持續領跑。Microsoft因為PowerBI表現出色也處於領導者象限。而昔日的領導者像SAP,SAS,IBM,MicroStrategy等逐漸被拉開了差距。

Tableau因為其靈活,出色的資料表現已經成為BI領域裡無可爭議的領頭羊。而其資料驅動的視覺化和核心思想是來自於Leland Wilkinson的同樣受到該思想影響的還有R的圖形庫ggplot

在資料視覺化開源領域裡,大家對百度開發的echarts可謂耳熟能詳,echarts經過多年的發展,其功能確實非常強大,可用出色來形容。但是螞蟻金服開源的基於的語法驅動的視覺化庫

G2,讓人眼前一亮。那我們就看看如何利用G2和500行左右的純前端程式碼來實現一個的類似Tableau的資料分析功能。

資料載入

第一步是載入資料:

資料載入主要用到了三個庫:

  • axios  基於Promise的HTTP客戶端
  • alasql 基於JS的開源SQL資料庫

資料通過我存放在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來進行查詢和分析。

JavaScript
123456789101112 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});}

這一步有兩點要注意:

  1. 資料中,如果列的名字中有包含點,空格等字元,例如Iris資料集中的Sepal.Length,datatable是無法正常顯示的,這裡要呼叫sanitizeData()方法把列名,也就是JsonArray中Json物件的屬性名中的點和空格去掉。
  2. 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的功能:

  • 源資料的解析,將csv, dsv,geojson 轉成標準的JSON,檢視Connector
  • 加工資料,包括 filter,map,fold(補資料) 等操作,檢視 Transform
  • 統計函式,彙總統計、百分比、封箱 等統計函式,檢視 Transform
  • 特殊資料處理,包括 地理資料、矩形樹圖、桑基圖、文字雲 的資料處理,檢視 Transform

資料處理是一個比較大的話題,我們的目標是利用盡可能少的程式碼完成一個數據分析的工具,所以這一步僅僅是利用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

圖形屬性對應視覺編碼中的不同元素,大家可以參考我的另一部落格 資料視覺化中的視覺屬性 。

圖形屬性主要有以下幾種。

  1. position:位置,二維座標系內對映至 x 軸、y 軸;
  2. color:顏色,包含了色調、飽和度和亮度;
  3. size:大小,不同的幾何標記對大小的定義有差異;
  4. shape:形狀,幾何標記的形狀決定了某個具體圖表型別的表現形式,例如點圖,可以使用圓點、三角形、圖片表示;線圖可以有折線、曲線、點線等表現形式;
  5. opacity:透明度,圖形的透明度,這個屬性從某種意義上來說可以使用顏色代替,需要使用 ‘rgba’ 的形式,所以在 G2 中我們獨立出來。

在構建語法的時候,我們把圖形屬性繫結一個或者多個數據欄位。

座標系 Coordinates

座標系是將兩種位置標度結合在一起組成的 2 維定位系統,描述了資料是如何對映到圖形所在的平面。

G2提供了以下幾種座標系:

coordType 說明
rect 直角座標系,目前僅支援二維,由 x, y 兩個互相垂直的座標軸構成。
polar 極座標系,由角度和半徑 2 個維度構成。
theta 一種特殊的極座標系,半徑長度固定,僅僅將資料對映到角度,常用於餅圖的繪製。
helix 螺旋座標系,基於阿基米德螺旋線。

分面 Facet

分面,將一份資料按照某個維度分隔成若干子集,然後建立一個圖表的矩陣,將每一個數據子集繪製到圖形矩陣的窗格中。分面其實提供了兩個功能:

  1. 按照指定的維度劃分資料集;
  2. 對圖表進行排版。

G2支援以下的分面型別:

分面型別 說明
rect 預設型別,指定 2 個維度作為行列,形成圖表的矩陣。
list 指定一個維度,可以指定一行有幾列,超出自動換行。
指定一個維度,沿著圓分佈。
tree 指定多個維度,每個維度作為樹的一級,展開多層圖表。
指定一個維度,形成映象圖表。
指定一個維度,形成矩陣分面。

注意,在我的程式碼中,為了簡化使用,只支援list和rect,當繫結一個欄位的時候用list,繫結兩個欄位的時候用rect。

除了上面提到的元素,當然還有許多其它的元素我們沒有包含和支援,例如:座標軸,圖例,提示等等。

關於圖形的語法的更多內容,請參考這裡

生成圖形語法的核心程式碼如下:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 functiongetFacet(faced,grammarScript){<