如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

創(chuàng)新互聯(lián)主營臺前網(wǎng)站建設(shè)的網(wǎng)絡公司,主營網(wǎng)站建設(shè)方案,成都App制作,臺前h5小程序制作搭建,臺前網(wǎng)站營銷推廣歡迎臺前等地區(qū)企業(yè)咨詢

在Gartner最新的對商務智能軟件的專業(yè)分析報告中,Tableau持續(xù)領(lǐng)跑。Microsoft因為PowerBI表現(xiàn)出色也處于領(lǐng)導者象限。而昔日的領(lǐng)導者像SAP,SAS,IBM,MicroStrategy等逐漸被拉開了差距。

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

Tableau因為其靈活,出色的數(shù)據(jù)表現(xiàn)已經(jīng)成為BI領(lǐng)域里無可爭議的領(lǐng)頭羊。而其數(shù)據(jù)驅(qū)動的可視化和核心思想是來自于Leland Wilkinson的The Grammar Of Graphics ,同樣受到該思想影響的還有R的圖形庫ggplot。

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

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

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

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

數(shù)據(jù)加載

第一步是加載數(shù)據(jù):

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

數(shù)據(jù)加載主要用到了三個庫:

  • axios  基于Promise的HTTP客戶端

  • alasql 基于JS的開源SQL數(shù)據(jù)庫

  • jquery datatable JQuery的數(shù)據(jù)表格插件

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

// Ajax async request
const request = {
  get: url => {
    return new Promise((resolve, reject) => {
      axios
        .get(url)
        .then(response => {
          resolve({ data: response.data });
        })
        .catch(error => {
          resolve({ data: error });
        });
    });
  }
};

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

let csv = await request.get(url);

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

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

class SqlTable {
  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(?)");
    const query_str = sql.replace("table", "CSV(?)");
    return await alasql.promise(query_str, [this.data]);
  }
}

SqlTable是一個對數(shù)據(jù)表的封裝,把csv數(shù)據(jù)存在SQL數(shù)據(jù)庫表中,提供一個query()方法。這里要做的是把SQL查詢個從 "SELECT * FROM table" 變成 "SELECT * FROM CSV(?)" 表示查詢參數(shù)是CSV數(shù)據(jù)。因為codepen的安全性限制,運行前向查找的replace語句(這里的regex表示把前面是“FROM ”詞的替換為CSV(?)的)在full page view下是不能執(zhí)行的,所以我用了一個更簡單的假定,用戶的表名就是table,這樣做有很多問題,大家如果在codepen之外的環(huán)境,可以用注釋掉的代碼。

然后把"SELECT * FROM table"的查詢結(jié)果(JSON Array)用datatable來展示。

function sanitizeData(jsonArray) {
  let newKey;
  jsonArray.forEach(function(item) {
    for (key in item) {
      newKey = key.replace(/\s/g, "").replace(/\./g, "");
      if (key != newKey) {
        item[newKey] = item[key];
        delete item[key];
      }
    }
  });
  return jsonArray;
}

function displayData(tableId, data) {
  // tricky to clone array
  let display_data = JSON.parse(JSON.stringify(data));
  display_data = sanitizeData(display_data);
  let columns = [];
  for (let item in display_data[0]) {
    columns.push({ data: item, title: item });
  }
  $("#" + tableId).DataTable({
    data: display_data,
    columns: columns,
    destroy: true
  });
}

這一步有兩點要注意:

  1. 數(shù)據(jù)中,如果列的名字中有包含點,空格等字符,例如Iris數(shù)據(jù)集中的Sepal.Length,datatable是無法正常顯示的,這里要調(diào)用sanitizeData()方法把列名,也就是JsonArray中Json對象的屬性名中的點和空格去掉。

  2. sanitizeData()方法會改變輸入對象,所以在傳入之前做了一個深度拷貝,這里利用JSON的stringfy和parse方法可以對JSON兼容的對象有效的拷貝。

這里要注意,Iris數(shù)據(jù)集中在datatable中的列名都不顯示點,但實際數(shù)據(jù)并沒有改變。

數(shù)據(jù)準備

數(shù)據(jù)加載完畢,我們來到第二步的數(shù)據(jù)準備階段。數(shù)據(jù)準備是數(shù)據(jù)科學項目最花時間的一步,通常需要對數(shù)據(jù)進行大量的清洗,變形,抽取等工作,使得數(shù)據(jù)變得可用。

在這一步我們做了兩件事:

一是顯示數(shù)據(jù)的一個摘要,讓我們初步了解數(shù)據(jù)的概貌,為進一步的數(shù)據(jù)變形和處理做好準備。

這個是Iris數(shù)據(jù)集的摘要:

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

function isString(o) {
    return typeof o == "string" || (typeof o == "object" && o.constructor === String);
}

function summaryData(data) {
  let summary = {};
  summary.count = data.length;
  summary.fields = [];
  for (let p in data[0]) {
    let field = {};
    field.name = p;
    if ( isString(data[0][p]) ) {
      field.type = "string";
    } else {
      field.type = "number";
    }
    summary.fields.push(field);
  }
  
  for (let f of 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(new Set(data.map(x => x[f.name])));
      }
  }
  return summary;
}

這里我們利用數(shù)據(jù)的類型判斷出每一個字段是數(shù)值型還是字符型。對于字符型的字段,我們利用JS6的Set來獲得所有的Unique數(shù)據(jù)。對于數(shù)值型,我們利用d3的max,min,mean,median,deviation方法計算出對應的最大值,最小值,平均數(shù),中位數(shù)和偏差。

另一個就是利用SQL查詢來對數(shù)據(jù)進行進一步的加工。

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

上圖的例子中我們利用限制條件得到一個Iris數(shù)據(jù)的子集。

另外G2還提供了Dataset的功能:

  • 源數(shù)據(jù)的解析,將csv, dsv,geojson 轉(zhuǎn)成標準的JSON,查看Connector

  • 加工數(shù)據(jù),包括 filter,map,fold(補數(shù)據(jù)) 等操作,查看 Transform

  • 統(tǒng)計函數(shù),匯總統(tǒng)計、百分比、封箱 等統(tǒng)計函數(shù),查看 Transform

  • 特殊數(shù)據(jù)處理,包括 地理數(shù)據(jù)、矩形樹圖、?;鶊D、文字云 的數(shù)據(jù)處理,查看 Transform

數(shù)據(jù)處理是一個比較大的話題,我們的目標是利用盡可能少的代碼完成一個數(shù)據(jù)分析的工具,所以這一步僅僅是利用alasql提供的SQL查詢來處理數(shù)據(jù)。

數(shù)據(jù)展示

數(shù)據(jù)處理好后就是我們的核心內(nèi)容,數(shù)據(jù)展示了。

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

這一步主要是利用select2提供的選擇控件構(gòu)建圖形語法來驅(qū)動數(shù)據(jù)展示。如上圖所示,對應的G2代碼圖形語法為:

g2chart.facet('rect', {
  fields: [ 'Admit', 'Dept' ],
  eachView(view) {
    view.interval().position('Gender*Freq').color('Gender').label('Freq');
  }
});

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

幾何標記 Geometry

幾何標記定義了使用什么樣的幾何圖形來表征數(shù)據(jù)。G2現(xiàn)在支持如下這些幾何標記:

geom 類型描述
point點,用于繪制各種點圖。
path路徑,無序的點連接而成的一條線,常用于路徑圖的繪制。
line線,點按照 x 軸連接成一條線,構(gòu)成線圖。
area填充線圖跟坐標系之間構(gòu)成區(qū)域圖,也可以指定上下范圍。
interval使用矩形或者弧形,用面積來表示大小關(guān)系的圖形,一般構(gòu)成柱狀圖、餅圖等圖表。
polygon多邊形,可以用于構(gòu)建色塊圖、地圖等圖表類型。
edge兩個點之間的鏈接,用于構(gòu)建樹圖和關(guān)系圖中的邊、流程圖中的連接線。
schema自定義圖形,用于構(gòu)建箱型圖(或者稱箱須圖)、蠟燭圖(或者稱 K 線圖、股票圖)等圖表。
heatmap用于熱力圖的繪制。

這里要注意,intervalstack是官方支持的,但是文檔沒有提到,在閱讀G2的API文檔的時候,我也發(fā)現(xiàn)文檔講的不是很清楚,有很多地方?jīng)]有講清楚如何使用API。這也是開源軟件值得改進的地方。

圖形屬性 Attributes

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

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

  1. position:位置,二維坐標系內(nèi)映射至 x 軸、y 軸;

  2. color:顏色,包含了色調(diào)、飽和度和亮度;

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

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

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

在構(gòu)建語法的時候,我們把圖形屬性綁定一個或者多個數(shù)據(jù)字段。

坐標系 Coordinates

坐標系是將兩種位置標度結(jié)合在一起組成的 2 維定位系統(tǒng),描述了數(shù)據(jù)是如何映射到圖形所在的平面。

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

coordType說明
rect直角坐標系,目前僅支持二維,由 x, y 兩個互相垂直的坐標軸構(gòu)成。
polar極坐標系,由角度和半徑 2 個維度構(gòu)成。
theta一種特殊的極坐標系,半徑長度固定,僅僅將數(shù)據(jù)映射到角度,常用于餅圖的繪制。
helix螺旋坐標系,基于阿基米德螺旋線。
分面 Facet

分面,將一份數(shù)據(jù)按照某個維度分隔成若干子集,然后創(chuàng)建一個圖表的矩陣,將每一個數(shù)據(jù)子集繪制到圖形矩陣的窗格中。分面其實提供了兩個功能:

  1. 按照指定的維度劃分數(shù)據(jù)集;

  2. 對圖表進行排版。

G2支持以下的分面類型:

分面類型說明
rect默認類型,指定 2 個維度作為行列,形成圖表的矩陣。
list指定一個維度,可以指定一行有幾列,超出自動換行。
circle指定一個維度,沿著圓分布。
tree指定多個維度,每個維度作為樹的一級,展開多層圖表。
mirror指定一個維度,形成鏡像圖表。
matrix指定一個維度,形成矩陣分面。

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

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

關(guān)于圖形的語法的更多內(nèi)容,請參考這里。

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

function getFacet(faced, grammarScript) {
  let facedType = "list";
  let facedScript = ""
  grammarScript = grammarScript.replace(chartScriptName,"view");
  if ( faced.length == 2 ) {
      facedType = "rect";
  }
  let facedFields = 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`;
  return facedScript
}

function getGrammar() {
  let grammar = {}, 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 > 0) {
      grammarScript = grammarScript + "." + attr + "('" + grammar[attr].join("*") + "')"; 
    } 
  });
  
  if (grammar.coord) {
    grammarScript = grammarScript + ";\n " + chartScriptName + "." + "coord('" + grammar.coord + "');";
  } else {
    rammarScript = grammarScript + ";";
  }
  
  if ( grammar.faced ) {
    if ( grammar.faced.length == 1 || 
        grammar.faced.length == 2 ) {
      grammarScript = getFacet(grammar.faced, grammarScript);
    } 
  }
  
  console.log(grammarScript)
  return grammarScript;
}

這里有幾點要注意:

  • 使用JS的模版字符串可以有效的構(gòu)造代碼片段

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

  • 你需要理解圖形語法,并不是任意的組合都能驅(qū)動出有效的圖形。

這里對于select2的多選,有一個小的提示,在缺省情況下,多選的順序是固定的順序,并不依賴選擇的順序,然而許多圖形語法和字段的順序有關(guān),所以我們使用如下的方法來相應select的選擇事件。

function updateSelect2Order(evt) {
  let element = evt.params.data.element;
  let $element = $(element);
  $element.detach();
  $(this).append($element);
  $(this).trigger("change");
}

這樣做就是每次選中后,把當前選中的項目移到數(shù)據(jù)最后的位置。

一些例子

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

Iris數(shù)據(jù)集散點圖

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

圖形語法:

g2chart.point().position('Sepal.Length*Petal.Length').color('Species').size('Sepal.Width')
Car數(shù)據(jù)集折線圖

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

圖形語法:

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

切換到極坐標:

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

圖形語法:

g2chart.line().position('id*speed'); 
g2chart.coord('polar');
Berkeley數(shù)據(jù)柱狀圖

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

數(shù)據(jù)處理:

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

圖形語法:

g2chart.interval().position('Gender*f').color('Gender').label('f');
Berkeley數(shù)據(jù)堆疊柱狀圖

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

數(shù)據(jù)處理:

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

圖形語法:

g2chart.intervalStack().position('Gender*f').color('Admit')
Berkeley數(shù)據(jù)餅圖

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

數(shù)據(jù)處理:

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

圖形語法:

g2chart.intervalStack().position('f').color('Gender').label('f');
g2chart.coord('theta')
Berkeley數(shù)據(jù)分面的應用

如何用前端代碼在瀏覽器中構(gòu)建一個Tableau

圖形語法:

g2chart.facet('rect', {
  fields: [ 'Dept', 'Admit' ],
  eachView(view) {
    view.coord('theta');
    view.intervalStack().position('Freq').color('Gender');
  }
});

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

本文分享了一個利用純前端技術(shù)構(gòu)建一個類似Tableau的BI應用的例子,整個代碼統(tǒng)計:

  • JS 370 行 JS6

  • HTML 69 + 9 + 5 = 83

  • CSS 21

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

看完上述內(nèi)容,你們掌握如何用前端代碼在瀏覽器中構(gòu)建一個Tableau的方法了嗎?如果還想學到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

當前名稱:如何用前端代碼在瀏覽器中構(gòu)建一個Tableau
文章地址:http://bm7419.com/article18/gipddp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)軟件開發(fā)、網(wǎng)站制作網(wǎng)站設(shè)計、面包屑導航、企業(yè)網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護公司