.table-view .table-row-cell {
-fx-background-insets: 0;
}
table-row-cell:empty .table-cell {
-fx-border-width: 0px;
}
table-row-cell .table-cell {
-fx-border-width: 0px;
}
.table-view .column-header{
-fx-border-color:white lightgray white white;
}
.table-view .filler{
-fx-background-color: white;
}
.table-view .column-header-background{
-fx-background-color: white;
}
.table-view > .virtual-flow > .scroll-bar:vertical{
-fx-background-insets: 0, 0 0 0 1;
-fx-padding: -1 -1 -1 0;
}
.table-view > .virtual-flow > .scroll-bar:horizontal{
-fx-background-insets: 0, 1 0 0 0;
-fx-padding: 0 -1 -1 -1;
}
.table-view > .virtual-flow > .corner {
-fx-background-color: derive(-fx-base,-1%); /*-fx-base 是modena 預(yù)先定義的顏色*/
}
.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected{
-fx-background: -fx-selection-bar;
-fx-table-cell-border-color: derive(-fx-selection-bar, 20%);
}
.table-row-cell:filled > .table-cell:selected{
-fx-background: -fx-selection-bar-non-focused;
-fx-table-cell-border-color: derive(-fx-selection-bar-non-focused, 20%);
}
.table-view:focused:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell > .table-cell:focused{
-fx-background-color: -fx-background, -fx-cell-focus-inner-border, -fx-background;
-fx-background-insets: 0, 1, 2;
}
.table-view{
/* Constants used throughout the tableview. */
-fx-table-header-border-color: -fx-box-border;
-fx-table-cell-border-color: derive(-fx-color,5%);
}
/* Each row in the table is a table-row-cell. Inside a table-row-cell is any number of table-cell. */
.table-row-cell {
-fx-background: -fx-control-inner-background;
-fx-background-color: -fx-table-cell-border-color, -fx-background;
-fx-background-insets: 0, 0 0 1 0;
-fx-padding: 0;
-fx-text-fill: -fx-text-background-color;
}
.table-row-cell:odd {
-fx-background: -fx-control-inner-background-alt;
}
.table-cell {
-fx-padding: 0.166667em; /* 2px, plus border adds 1px */
-fx-background-color: null;
-fx-border-color: transparent -fx-table-cell-border-color transparent transparent;
-fx-cell-size: 2.0em; /* 24 */
-fx-text-fill: -fx-text-background-color;
}
.table-view > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected {
-fx-background-color: -fx-table-cell-border-color, -fx-background;
-fx-background-insets: 0, 0 0 1 0;
}
/* When in constrained resize mode, the right-most visible cell should not have
a right-border, as it is not possible to get this cleanly out of view without
introducing horizontal scrollbars (see RT-14886). */
.table-view:constrained-resize > .virtual-flow > .clipped-container > .sheet > .table-row-cell > .table-cell:last-visible {
-fx-border-color: transparent;
}
/* The column-resize-line is shown when the user is attempting to resize a column. */
.table-view .column-resize-line {
-fx-background: -fx-accent;
-fx-background-color: -fx-background;
-fx-padding: 0.0em 0.0416667em 0.0em 0.0416667em; /* 0 0.571429 0 0.571429 */
}
/* This is the area behind the column headers. An ideal place to specify background
and border colors for the whole area (not individual column-header's). */
.table-view .column-header-background{
-fx-background-color: -fx-inner-border, -fx-body-color;
-fx-background-insets: 0, 1;
}
/* The column header row is made up of a number of column-header, one for each
TableColumn, and a 'filler' area that extends from the right-most column
to the edge of the tableview, or up to the 'column control' button. */
.table-view .column-header,
.table-view .filler,
.table-view > .column-header-background > .show-hide-columns-button,
.table-view:constrained-resize .filler{
-fx-background-color: -fx-box-border, -fx-inner-border, -fx-body-color;
-fx-background-insets: 0, 0 1 1 0, 1 2 2 1;
-fx-font-weight: bold;
-fx-size: 2em;
-fx-text-fill: -fx-selection-bar-text;
-fx-padding: 0.166667em;
}
.table-view .filler,
.table-view:constrained-resize .filler{
-fx-background-insets: 0, 0 0 1 0, 1 1 2 1;
}
.table-view > .column-header-background > .show-hide-columns-button {
-fx-background-insets: 0, 0 0 1 1, 1 1 2 2;
}
.table-view .column-header .sort-order-dots-container{
-fx-padding: 2 0 2 0;
}
.table-view .column-header .sort-order{
-fx-font-size: 0.916667em; /* 11pt - 1 less than the default font */
}
.table-view .column-header .sort-order-dot {
-fx-background-color: -fx-mark-color;
-fx-padding: 0.115em;
-fx-background-radius: 0.115em;
}
.table-view .column-header .label{
-fx-alignment: center;
}
/* Plus Symbol */
.table-view .show-hide-column-image,
{
-fx-background-color: -fx-mark-color;
-fx-padding: 0.25em; /* 3px */
-fx-shape: "M398.902,298.045c0.667,0,1.333,0,2,0c0,0.667,0,1.333,0,2c0.667,0,1.333,0,2,0c0,0.667,0,1.333,0,2c-0.667,0-1.333,0-2,0c0,0.666,0,1.332,0,1.999c-0.667,0-1.333,0-2,0c0-0.667,0-1.333,0-1.999c-0.666,0-1.333,0-1.999,0c0-0.667,0-1.334,0-2c0.666,0,1.333,0,1.999,0C398.902,299.378,398.902,298.711,398.902,298.045z";
}
/* When a column is being 'dragged' to be placed in a different position, there
is a region that follows along the column header area to indicate where the
column will be dropped. This region can be styled using the .column-drag-header
name. */
.table-view .column-drag-header {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-border-color: transparent;
-fx-opacity: 0.6;
}
/* Semi-transparent overlay to indicate the column that is currently being moved */
.table-view .column-overlay{
-fx-background-color: darkgray;
-fx-opacity: 0.3;
}
/* Header Sort Arrows */
.table-view /*> column-header-background > nested-column-header >*/ .arrow{
-fx-background-color: -fx-mark-color;
-fx-padding: 0.25em 0.3125em 0.25em 0.3125em; /* 3 3.75 3 3.75 */
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
}
QTableView是模型-視圖(Model-View)框架類之一,是Qt模型-視圖框架的組成部分,它實現(xiàn)了一個表格視圖。在一個應(yīng)用需要和一批數(shù)據(jù)進(jìn)行交互,需要以表格形式輸出這些信息的時候,QTableView是最合適的選擇。
QTableView實現(xiàn)了QAbstractItemView類定義的接口,因此它能夠顯示從QAbstractItemModel類派生的模型提供的數(shù)據(jù)。
我們可以通過使用鼠標(biāo)單擊某個單元格或者使用箭頭來導(dǎo)航表格視圖中的單元格。QTableView擁有一個水平表頭和垂直標(biāo)表頭。表格視圖中顯示的條目與其他視圖中的條目一樣,使用標(biāo)準(zhǔn)委托類來渲染和編輯。
QTableView常用方法:
QTableView類繼承關(guān)系:
在測試代碼中,我們使用QStandardItemModel作為QTableView的模型類, 對視圖的屬性做了一些限制(只能選中一行,只允許行選中模式,不可編輯等等)。當(dāng)我們選中某一行是,在狀態(tài)條上添加信息顯示,在菜單欄實現(xiàn)了添加行和刪除行的功能。完整代碼如下:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt,QItemSelection, QItemSelectionModel, QModelIndex
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView,
QAbstractItemView, QHeaderView, QMenu, QMenuBar, QAction)
class DemoTableView(QMainWindow):
def __init__(self, parent=None):
super(DemoTableView, self).__init__(parent)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('實戰(zhàn)PyQt5: QTableView 演示')
# 設(shè)置窗口大小
self.resize(520, 360)
self.initUi()
def initUi(self):
#行數(shù)和列數(shù)
self.rows = 8
self.cols = 4
#設(shè)置水平表頭信息
model = QStandardItemModel(self.rows, self.cols, self)
hTitle=[]
for col in range(self.cols):
hTitle.append('第{}列'.format(col+1))
model.setHorizontalHeaderLabels(hTitle)
#設(shè)置垂直表頭信息
vTitle=[]
for row in range(self.rows):
vTitle.append('第{}行'.format(row+1))
model.setVerticalHeaderLabels(vTitle)
#設(shè)置Item里的內(nèi)容
for row in range (self.rows):
for column in range (self.cols):
item = QStandardItem('(row %s, column %s)'%(row, column))
model.setItem(row, column, item)
tableView = QTableView(self)
tableView.setModel(model)
#設(shè)置只能選中一行
tableView.setSelectionMode(QAbstractItemView.SingleSelection)
#不可編輯
tableView.setEditTriggers(QTableView.NoEditTriggers)
#設(shè)置只有行選中
tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
#所有列自動拉伸,充滿界面
tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
#顯示選中行的信息
tableView.selectionModel().currentChanged.connect(self.onCurrentChanged)
self.model = model
self.tableView = tableView
self.setCentralWidget(tableView)
#添加菜單項
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('文件')
editMenu = menuBar.addMenu('編輯')
#退出應(yīng)用
appExit = QAction('退出', self)
appExit.triggered.connect(self.close)
fileMenu.addAction(appExit)
#添加一行
editAppend = QAction('添加', self)
editAppend.triggered.connect(self.appendRow)
#刪除一行
editRemove = QAction('刪除', self)
editRemove.triggered.connect(self.removeRow)
editMenu.addAction(editAppend)
editMenu.addAction(editRemove)
def appendRow(self):
row = self.model.rowCount()
items = list()
for col in range (self.cols):
items.append(QStandardItem('(row %s, column %s)'%(row, col)))
#添加一行
self.model.appendRow(items)
#更新頭信息
self.model.setVerticalHeaderItem(row, QStandardItem('第{}行'.format(row+1)))
def removeRow(self):
#獲取選中的行
sel = self.tableView.selectionModel().selectedRows()
print(sel)
if sel:
#下面刪除時,選中多行中的最后一行,會被刪掉;不選中,則默認(rèn)第一行刪掉
index=self.tableView.currentIndex()
print(index.row())
self.model.removeRow(index.row())
def onCurrentChanged(self,current, previous):
#初始化時,previous.row() = -1,不顯示信息
if int(previous.row() < 0):
return
self.statusBar().showMessage('選中第{}行'.format(current.row()+1))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = DemoTableView()
window.show()
sys.exit(app.exec())
運(yùn)行結(jié)果如下圖:
測試QTableView
喜歡本文內(nèi)容就關(guān)注, 收藏,點贊,評論和轉(zhuǎn)發(fā)。
Gartner最新的對商務(wù)智能軟件的專業(yè)分析報告中,Tableau持續(xù)領(lǐng)跑。Microsoft因為PowerBI表現(xiàn)出色也處于領(lǐng)導(dǎo)者象限。而昔日的領(lǐng)導(dǎo)者像SAP,SAS,IBM,MicroStrategy等逐漸被拉開了差距。
Tableau因為其靈活,出色的數(shù)據(jù)表現(xiàn)已經(jīng)成為BI領(lǐng)域里無可爭議的領(lǐng)頭羊。而其數(shù)據(jù)驅(qū)動的可視化和核心思想是來自于Leland Wilkinson的The Grammar Of Graphics ,同樣受到該思想影響的還有R的圖形庫ggplot。
在數(shù)據(jù)可視化開源領(lǐng)域里,大家對百度開發(fā)的echarts可謂耳熟能詳,echarts經(jīng)過多年的發(fā)展,其功能確實非常強(qiáng)大,可用出色來形容。但是螞蟻金服開源的基于The Grammar Of Graphics的語法驅(qū)動的可視化庫G2,讓人眼前一亮。那我們就看看如何利用G2和500行左右的純前端代碼來實現(xiàn)一個的類似Tableau的數(shù)據(jù)分析功能。
第一步是加載數(shù)據(jù):
數(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ù)準(zhǔn)備的時候,可以方便的利用SQL來進(jìn)行查詢和分析。
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的安全性限制,運(yùn)行前向查找的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 }); }
這一步有兩點要注意:
這里要注意,Iris數(shù)據(jù)集中在datatable中的列名都不顯示點,但實際數(shù)據(jù)并沒有改變。
數(shù)據(jù)加載完畢,我們來到第二步的數(shù)據(jù)準(zhǔn)備階段。數(shù)據(jù)準(zhǔn)備是數(shù)據(jù)科學(xué)項目最花時間的一步,通常需要對數(shù)據(jù)進(jìn)行大量的清洗,變形,抽取等工作,使得數(shù)據(jù)變得可用。
在這一步我們做了兩件事:
一是顯示數(shù)據(jù)的一個摘要,讓我們初步了解數(shù)據(jù)的概貌,為進(jìn)一步的數(shù)據(jù)變形和處理做好準(zhǔn)備。
這個是Iris數(shù)據(jù)集的摘要:
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方法計算出對應(yīng)的最大值,最小值,平均數(shù),中位數(shù)和偏差。
另一個就是利用SQL查詢來對數(shù)據(jù)進(jìn)行進(jìn)一步的加工。
上圖的例子中我們利用限制條件得到一個Iris數(shù)據(jù)的子集。
另外G2還提供了Dataset的功能:
源數(shù)據(jù)的解析,將csv, dsv,geojson 轉(zhuǎn)成標(biāo)準(zhǔn)的JSON,查看Connector加工數(shù)據(jù),包括 filter,map,fold(補(bǔ)數(shù)據(jù)) 等操作,查看 Transform統(tǒng)計函數(shù),匯總統(tǒng)計、百分比、封箱 等統(tǒng)計函數(shù),查看 Transform特殊數(shù)據(jù)處理,包括 地理數(shù)據(jù)、矩形樹圖、桑基圖、文字云 的數(shù)據(jù)處理,查看 Transform
數(shù)據(jù)處理是一個比較大的話題,我們的目標(biāo)是利用盡可能少的代碼完成一個數(shù)據(jù)分析的工具,所以這一步僅僅是利用alasql提供的SQL查詢來處理數(shù)據(jù)。
數(shù)據(jù)處理好后就是我們的核心內(nèi)容,數(shù)據(jù)展示了。
這一步主要是利用select2提供的選擇控件構(gòu)建圖形語法來驅(qū)動數(shù)據(jù)展示。如上圖所示,對應(yīng)的G2代碼圖形語法為:
g2chart.facet('rect', { fields: [ 'Admit', 'Dept' ], eachView(view) { view.interval().position('Gender*Freq').color('Gender').label('Freq'); } });
圖形語法主要包含以下幾個主要的元素:
幾何標(biāo)記 Geometry
幾何標(biāo)記定義了使用什么樣的幾何圖形來表征數(shù)據(jù)。G2現(xiàn)在支持如下這些幾何標(biāo)記:
geom 類型描述point點,用于繪制各種點圖。path路徑,無序的點連接而成的一條線,常用于路徑圖的繪制。line線,點按照 x 軸連接成一條線,構(gòu)成線圖。area填充線圖跟坐標(biāo)系之間構(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。這也是開源軟件值得改進(jìn)的地方。
圖形屬性 Attributes
圖形屬性對應(yīng)視覺編碼中的不同元素,大家可以參考我的另一博客 數(shù)據(jù)可視化中的視覺屬性 。
圖形屬性主要有以下幾種。
在構(gòu)建語法的時候,我們把圖形屬性綁定一個或者多個數(shù)據(jù)字段。
坐標(biāo)系 Coordinates
坐標(biāo)系是將兩種位置標(biāo)度結(jié)合在一起組成的 2 維定位系統(tǒng),描述了數(shù)據(jù)是如何映射到圖形所在的平面。
G2提供了以下幾種坐標(biāo)系:
coordType說明rect直角坐標(biāo)系,目前僅支持二維,由 x, y 兩個互相垂直的坐標(biāo)軸構(gòu)成。polar極坐標(biāo)系,由角度和半徑 2 個維度構(gòu)成。theta一種特殊的極坐標(biāo)系,半徑長度固定,僅僅將數(shù)據(jù)映射到角度,常用于餅圖的繪制。helix螺旋坐標(biāo)系,基于阿基米德螺旋線。
分面 Facet
分面,將一份數(shù)據(jù)按照某個維度分隔成若干子集,然后創(chuàng)建一個圖表的矩陣,將每一個數(shù)據(jù)子集繪制到圖形矩陣的窗格中。分面其實提供了兩個功能:
G2支持以下的分面類型:
分面類型說明rect默認(rèn)類型,指定 2 個維度作為行列,形成圖表的矩陣。list指定一個維度,可以指定一行有幾列,超出自動換行。circle指定一個維度,沿著圓分布。tree指定多個維度,每個維度作為樹的一級,展開多層圖表。mirror指定一個維度,形成鏡像圖表。matrix指定一個維度,形成矩陣分面。
注意,在我的代碼中,為了簡化使用,只支持list和rect,當(dāng)綁定一個字段的時候用list,綁定兩個字段的時候用rect。
除了上面提到的元素,當(dāng)然還有許多其它的元素我們沒有包含和支持,例如:坐標(biāo)軸,圖例,提示等等。
關(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; }
這里有幾點要注意:
這里對于select2的多選,有一個小的提示,在缺省情況下,多選的順序是固定的順序,并不依賴選擇的順序,然而許多圖形語法和字段的順序有關(guān),所以我們使用如下的方法來相應(yīng)select的選擇事件。
function updateSelect2Order(evt) { let element = evt.params.data.element; let $element = $(element); $element.detach(); $(this).append($element); $(this).trigger("change"); }
這樣做就是每次選中后,把當(dāng)前選中的項目移到數(shù)據(jù)最后的位置。
一些例子
好了,下面我們就來看一些例子,了解一下如何使用圖形語法來分析和探索數(shù)據(jù)。
Iris數(shù)據(jù)集散點圖
圖形語法:
g2chart.point().position('Sepal.Length*Petal.Length').color('Species').size('Sepal.Width')
Car數(shù)據(jù)集折線圖
圖形語法:
g2chart.line().position('id*speed');
切換到極坐標(biāo):
圖形語法:
g2chart.line().position('id*speed'); g2chart.coord('polar');
Berkeley數(shù)據(jù)柱狀圖
數(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ù)堆疊柱狀圖
數(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ù)餅圖
數(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ù)分面的應(yīng)用
圖形語法:
g2chart.facet('rect', { fields: [ 'Dept', 'Admit' ], eachView(view) { view.coord('theta'); view.intervalStack().position('Freq').color('Gender'); } });
更多的分析圖形留給大家去嘗試
本文分享了一個利用純前端技術(shù)構(gòu)建一個類似Tableau的BI應(yīng)用的例子,整個代碼統(tǒng)計:
總計474 行,用這么少的代碼就能完成一個看上去還不錯的BI工具,還算不錯吧。當(dāng)然這里主要是由于開源社區(qū)提供了這么多好的前端庫以供應(yīng)用,我要做的僅僅是讓它們有效的工作在一起。這個只能算是個原型,從功能和質(zhì)量上來說都不成熟,但是能在瀏覽器中不借助任何的服務(wù)器來實現(xiàn)BI的數(shù)據(jù)分析功能,應(yīng)該會有很多人想要在自己的應(yīng)用中嵌一個吧?
結(jié)合我之前分享的TensorflowJS的文章,下面一步可能是加入預(yù)測功能,為數(shù)據(jù)分析加入智能,前端應(yīng)用的前景,不可限量!
參考
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。