當前位置:
首頁 > 最新 > 用 500 行純前端代碼在瀏覽器中構建一個 Tableau

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

作者:naughty

https://my.oschina.net/taogang/blog/1811573

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

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

在數據可視化開源領域裡,大家對百度開發的echarts可謂耳熟能詳,echarts經過多年的發展,其功能確實非常強大,可用出色來形容。但是螞蟻金服開源的基於The Grammar Of Graphics的語法驅動的可視化庫G2,讓人眼前一亮。那我們就看看如何利用G2和500行左右的純前端代碼來實現一個的類似Tableau的數據分析功能。

演示參見 https://codepen.io/gangtao/full/OZvedx/

代碼參見 https://gist.github.com/gangtao/e053cf9722b64ef8544afa371c2daaee

數據載入

第一步是載入數據:

數據載入主要用到了三個庫:

axios 基於Promise的HTTP客戶端

alasql 基於JS的開源SQL資料庫

jquery datatable JQuery的數據表格插件

數據通過我存放在GitHub中的csv格式的文件,以REST請求的方式來載入。下面的代碼把Axios的Promise變成 async/wait方式。

// Ajax async request

constrequest={

get:url=>{

returnnewPromise((resolve,reject)=>{

axios

.get(url)

.then(response=>{

resolve({data:response.data});

})

.catch(error=>{

resolve({data:error});

});

});

}

};

封裝好後,我們就可以用request.get()方法發送REST請求,獲取csv文件。

let csv = await request.get(url);

這一步可能會遇到跨域請求的問題,github上的文件支持跨域。

把數據存儲在一個SQL資料庫中,這樣做的好處是為了下一步做數據準備的時候,可以方便的利用SQL來進行查詢和分析。

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(/(?

constquery_str=sql.replace("table","CSV(?)");

returnawaitalasql.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來展示。

functionsanitizeData(jsonArray){

letnewKey;

jsonArray.forEach(function(item){

for(keyinitem){

newKey=key.replace(/s/g,"").replace(/./g,"");

if(key!=newKey){

item[newKey]=item[key];

deleteitem[key];

}

}

});

returnjsonArray;

}

functiondisplayData(tableId,data){

// tricky to clone array

letdisplay_data=JSON.parse(JSON.stringify(data));

display_data=sanitizeData(display_data);

letcolumns=[];

for(let itemindisplay_data[]){

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數據集的摘要:

functionisString(o){

returntypeofo=="string"||(typeofo=="object"&&o.constructor===String);

}

functionsummaryData(data){

letsummary={};

summary.count=data.length;

summary.fields=[];

for(letpindata[]){

letfield={};

field.name=p;

if(isString(data[][p])){

field.type="string";

}else{

field.type="number";

}

}

for(letfofsummary.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代碼圖形語法為:

g2chart.facet("rect",{

fields:["Admit","Dept"],

eachView(view){

view.interval().position("Gender*Freq").color("Gender").label("Freq");

}

});

圖形語法主要包含以下幾個主要的元素:

幾何標記 Geometry

幾何標記定義了使用什麼樣的幾何圖形來表徵數據。G2現在支持如下這些幾何標記:

這裡要注意,intervalstack是官方支持的,但是文檔沒有提到,在閱讀G2的API文檔的時候,我也發現文檔講的不是很清楚,有很多地方沒有講清楚如何使用API。這也是開源軟體值得改進的地方。

圖形屬性 Attributes

圖形屬性對應視覺編碼中的不同元素,大家可以參考我的另一博客 數據可視化中的視覺屬性 。

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

position:位置,二維坐標系內映射至 x 軸、y 軸;

color:顏色,包含了色調、飽和度和亮度;

size:大小,不同的幾何標記對大小的定義有差異;

shape:形狀,幾何標記的形狀決定了某個具體圖表類型的表現形式,例如點圖,可以使用圓點、三角形、圖片表示;線圖可以有折線、曲線、點線等表現形式;

opacity:透明度,圖形的透明度,這個屬性從某種意義上來說可以使用顏色代替,需要使用 『rgba』 的形式,所以在 G2 中我們獨立出來。

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

坐標系 Coordinates

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

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

分面 Facet

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

按照指定的維度劃分數據集;

對圖表進行排版。

G2支持以下的分面類型:

注意,在我的代碼中,為了簡化使用,只支持list和rect,當綁定一個欄位的時候用list,綁定兩個欄位的時候用rect。

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

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

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

functiongetFacet(faced,grammarScript){

letfacedType="list";

letfacedScript=""

grammarScript=grammarScript.replace(chartScriptName,"view");

if(faced.length==2){

facedType="rect";

}

letfacedFields=faced.join("", "")

facedScript=facedScript+`${chartScriptName}.facet("${ facedType }",{n`;

facedScript=facedScript+`fields:["${ facedFields }"],n`;

facedScript=facedScript+`eachView(view){n`;

facedScript=facedScript+`${grammarScript};n`;

facedScript=facedScript+`}n`;

facedScript=facedScript+`});n`;

returnfacedScript

}

functiongetGrammar(){

letgrammar={},grammarScript=chartScriptName+".";

grammar.geom=$("#geomSelect").val();

grammar.coord=$("#coordSelect").val();

grammar.faced=$("#facetSelect").val();

geom_attributes.map(function(attr){

grammar[attr]=$("#"+attr+"attr").val();

});

grammarScript=grammarScript+grammar.geom+"()";

geom_attributes.map(function(attr){

if(grammar[attr].length>){

grammarScript=grammarScript+"."+attr+"(""+grammar[attr].join("*")+"")";

}

});

if(grammar.coord){

grammarScript=grammarScript+";n "+chartScriptName+"."+"coord(""+grammar.coord+"");";

}else{

rammarScript=grammarScript+";";

}

if(grammar.faced){

grammarScript=getFacet(grammar.faced,grammarScript);

}

}

console.log(grammarScript)

returngrammarScript;

}

這裡有幾點要注意:

使用JS的模版字元串可以有效的構造代碼片段

使用eval執行構造好的語法驅動的代碼來響應select的change事件,以獲得良好的交互性。在生產環境,要注意該方法的安全性隱患,因為純前端,eval能帶來的威脅比較小,生產中,可以把這個執行放在安全的沙箱中運行

你需要理解圖形語法,並不是任意的組合都能驅動出有效的圖形。

這裡對於select2的多選,有一個小的提示,在預設情況下,多選的順序是固定的順序,並不依賴選擇的順序,然而許多圖形語法和欄位的順序有關,所以我們使用如下的方法來相應select的選擇事件。

functionupdateSelect2Order(evt){

let$element=$(element);

$element.detach();

$(this).append($element);

$(this).trigger("change");

}

這樣做就是每次選中後,把當前選中的項目移到數據最後的位置。

一些例子

好了,下面我們就來看一些例子,了解一下如何使用圖形語法來分析和探索數據。

Iris數據集散點圖

圖形語法:

g2chart.point().position("Sepal.Length*Petal.Length").color("Species").size("Sepal.Width")

Car數據集折線圖

圖形語法:

g2chart.line().position("id*speed");

切換到極坐標:

圖形語法:

g2chart.line().position("id*speed");

g2chart.coord("polar");

Berkeley數據柱狀圖

數據處理:

SELECT SUM(Freq)asf,Gender FROM table GROUP BYGender

圖形語法:

g2chart.interval().position("Gender*f").color("Gender").label("f");

Berkeley數據堆疊柱狀圖

數據處理:

SELECT SUM(Freq) as f , Gender , Admit FROM table GROUP BY Gender, Admit

圖形語法:

g2chart.intervalStack().position("Gender*f").color("Admit")

Berkeley數據餅圖

數據處理:

SELECT SUM(Freq) as f , Gender FROM table GROUP BY Gender

圖形語法:

g2chart.intervalStack().position("f").color("Gender").label("f");

g2chart.coord("theta")

Berkeley數據分面的應用

圖形語法:

g2chart.facet("rect",{

fields:["Dept","Admit"],

eachView(view){

view.coord("theta");

view.intervalStack().position("Freq").color("Gender");

}

});

更多的分析圖形留給大家去嘗試

總結

本文分享了一個利用純前端技術構建一個類似Tableau的BI應用的例子,整個代碼統計:

JS 370 行 JS6

HTML 69 + 9 + 5 = 83 行

CSS 21 行

總計474 行,用這麼少的代碼就能完成一個看上去還不錯的BI工具,還算不錯吧。當然這裡主要是由於開源社區提供了這麼多好的前端庫以供應用,我要做的僅僅是讓它們有效的工作在一起。這個只能算是個原型,從功能和質量上來說都不成熟,但是能在瀏覽器中不藉助任何的伺服器來實現BI的數據分析功能,應該會有很多人想要在自己的應用中嵌一個吧?

結合我之前分享的TensorflowJS的文章,下面一步可能是加入預測功能,為數據分析加入智能,前端應用的前景,不可限量!

參考

axios 基於Promise的HTTP客戶端

alasql 基於JS的開源SQL資料庫

jquery datatable JQuery的數據表格插件

select2 JQuery的選擇控制項插件

相關博客 使用開源軟體快速搭建數據分析平台

相關博客 數據可視化中的視覺屬性

覺得本文對你有幫助?請分享給更多人

關注「前端大全」,提升前端技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端大全 的精彩文章:

主流瀏覽器圖片反防盜鏈方法總結

TAG:前端大全 |