Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
.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 預先定義的顏色*/
}
.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模型-視圖框架的組成部分,它實現了一個表格視圖。在一個應用需要和一批數據進行交互,需要以表格形式輸出這些信息的時候,QTableView是最合適的選擇。
QTableView實現了QAbstractItemView類定義的接口,因此它能夠顯示從QAbstractItemModel類派生的模型提供的數據。
我們可以通過使用鼠標單擊某個單元格或者使用箭頭來導航表格視圖中的單元格。QTableView擁有一個水平表頭和垂直標表頭。表格視圖中顯示的條目與其他視圖中的條目一樣,使用標準委托類來渲染和編輯。
QTableView常用方法:
QTableView類繼承關系:
在測試代碼中,我們使用QStandardItemModel作為QTableView的模型類, 對視圖的屬性做了一些限制(只能選中一行,只允許行選中模式,不可編輯等等)。當我們選中某一行是,在狀態條上添加信息顯示,在菜單欄實現了添加行和刪除行的功能。完整代碼如下:
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)
# 設置窗口標題
self.setWindowTitle('實戰PyQt5: QTableView 演示')
# 設置窗口大小
self.resize(520, 360)
self.initUi()
def initUi(self):
#行數和列數
self.rows = 8
self.cols = 4
#設置水平表頭信息
model = QStandardItemModel(self.rows, self.cols, self)
hTitle=[]
for col in range(self.cols):
hTitle.append('第{}列'.format(col+1))
model.setHorizontalHeaderLabels(hTitle)
#設置垂直表頭信息
vTitle=[]
for row in range(self.rows):
vTitle.append('第{}行'.format(row+1))
model.setVerticalHeaderLabels(vTitle)
#設置Item里的內容
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)
#設置只能選中一行
tableView.setSelectionMode(QAbstractItemView.SingleSelection)
#不可編輯
tableView.setEditTriggers(QTableView.NoEditTriggers)
#設置只有行選中
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('編輯')
#退出應用
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:
#下面刪除時,選中多行中的最后一行,會被刪掉;不選中,則默認第一行刪掉
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())
運行結果如下圖:
測試QTableView
喜歡本文內容就關注, 收藏,點贊,評論和轉發。
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的數據分析功能。
第一步是加載數據:
數據加載主要用到了三個庫:
數據通過我存放在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()方法發送REST請求,獲取csv文件。
let csv = await request.get(url);
這一步可能會遇到跨域請求的問題,github上的文件支持跨域。
把數據存儲在一個SQL數據庫中,這樣做的好處是為了下一步做數據準備的時候,可以方便的利用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是一個對數據表的封裝,把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來展示。
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數據集中在datatable中的列名都不顯示點,但實際數據并沒有改變。
數據加載完畢,我們來到第二步的數據準備階段。數據準備是數據科學項目最花時間的一步,通常需要對數據進行大量的清洗,變形,抽取等工作,使得數據變得可用。
在這一步我們做了兩件事:
一是顯示數據的一個摘要,讓我們初步了解數據的概貌,為進一步的數據變形和處理做好準備。
這個是Iris數據集的摘要:
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; }
這里我們利用數據的類型判斷出每一個字段是數值型還是字符型。對于字符型的字段,我們利用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現在支持如下這些幾何標記:
geom 類型描述point點,用于繪制各種點圖。path路徑,無序的點連接而成的一條線,常用于路徑圖的繪制。line線,點按照 x 軸連接成一條線,構成線圖。area填充線圖跟坐標系之間構成區域圖,也可以指定上下范圍。interval使用矩形或者弧形,用面積來表示大小關系的圖形,一般構成柱狀圖、餅圖等圖表。polygon多邊形,可以用于構建色塊圖、地圖等圖表類型。edge兩個點之間的鏈接,用于構建樹圖和關系圖中的邊、流程圖中的連接線。schema自定義圖形,用于構建箱型圖(或者稱箱須圖)、蠟燭圖(或者稱 K 線圖、股票圖)等圖表。heatmap用于熱力圖的繪制。
這里要注意,intervalstack是官方支持的,但是文檔沒有提到,在閱讀G2的API文檔的時候,我也發現文檔講的不是很清楚,有很多地方沒有講清楚如何使用API。這也是開源軟件值得改進的地方。
圖形屬性 Attributes
圖形屬性對應視覺編碼中的不同元素,大家可以參考我的另一博客 數據可視化中的視覺屬性 。
圖形屬性主要有以下幾種。
在構建語法的時候,我們把圖形屬性綁定一個或者多個數據字段。
坐標系 Coordinates
坐標系是將兩種位置標度結合在一起組成的 2 維定位系統,描述了數據是如何映射到圖形所在的平面。
G2提供了以下幾種坐標系:
coordType說明rect直角坐標系,目前僅支持二維,由 x, y 兩個互相垂直的坐標軸構成。polar極坐標系,由角度和半徑 2 個維度構成。theta一種特殊的極坐標系,半徑長度固定,僅僅將數據映射到角度,常用于餅圖的繪制。helix螺旋坐標系,基于阿基米德螺旋線。
分面 Facet
分面,將一份數據按照某個維度分隔成若干子集,然后創建一個圖表的矩陣,將每一個數據子集繪制到圖形矩陣的窗格中。分面其實提供了兩個功能:
G2支持以下的分面類型:
分面類型說明rect默認類型,指定 2 個維度作為行列,形成圖表的矩陣。list指定一個維度,可以指定一行有幾列,超出自動換行。circle指定一個維度,沿著圓分布。tree指定多個維度,每個維度作為樹的一級,展開多層圖表。mirror指定一個維度,形成鏡像圖表。matrix指定一個維度,形成矩陣分面。
注意,在我的代碼中,為了簡化使用,只支持list和rect,當綁定一個字段的時候用list,綁定兩個字段的時候用rect。
除了上面提到的元素,當然還有許多其它的元素我們沒有包含和支持,例如:坐標軸,圖例,提示等等。
關于圖形的語法的更多內容,請參考這里。
生成圖形語法的核心代碼如下:
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的多選,有一個小的提示,在缺省情況下,多選的順序是固定的順序,并不依賴選擇的順序,然而許多圖形語法和字段的順序有關,所以我們使用如下的方法來相應select的選擇事件。
function updateSelect2Order(evt) { let element = evt.params.data.element; 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) as f , Gender FROM table GROUP BY Gender
圖形語法:
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應用的例子,整個代碼統計:
總計474 行,用這么少的代碼就能完成一個看上去還不錯的BI工具,還算不錯吧。當然這里主要是由于開源社區提供了這么多好的前端庫以供應用,我要做的僅僅是讓它們有效的工作在一起。這個只能算是個原型,從功能和質量上來說都不成熟,但是能在瀏覽器中不借助任何的服務器來實現BI的數據分析功能,應該會有很多人想要在自己的應用中嵌一個吧?
結合我之前分享的TensorflowJS的文章,下面一步可能是加入預測功能,為數據分析加入智能,前端應用的前景,不可限量!
參考
*請認真填寫需求信息,我們會在24小時內與您取得聯系。