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 国产三级在线观看免费,最近中文字幕免费版在线,视频一区在线

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Python入坑系列-Pyside6桌面編程之Lay

          Python入坑系列-Pyside6桌面編程之Layout設(shè)置精美布局效果

          過本文章,你可以掌握以下內(nèi)容:

          1. Pyside6 Layout介紹
          2. Pyside6 Layout布局解釋及示例
          3. 自定義Layout,實(shí)現(xiàn)部件自動(dòng)換行

          1、Pyside6 Layout介紹

          QtWidgets.QLayout是Qt中用于管理窗口或?qū)υ捒蛑行〔考丶┎季值幕悺K且粋€(gè)抽象基類,定義了所有布局類共有的接口和行為。QLayout及其子類的主要作用是自動(dòng)管理小部件的位置和大小,以便于創(chuàng)建整潔和靈活的圖形用戶界面(GUI),對(duì)于復(fù)雜界面布局是很重要的,主要作用包括:

          • 自動(dòng)管理小部件位置:QLayout自動(dòng)計(jì)算小部件的位置,使得小部件根據(jù)布局規(guī)則排列,無需手動(dòng)指定每個(gè)小部件的具體位置。
          • 自動(dòng)調(diào)整大小:當(dāng)窗口大小變化時(shí),QLayout能夠自動(dòng)調(diào)整其管理的小部件的大小和位置,確保布局的一致性和響應(yīng)性。
          • 簡化界面設(shè)計(jì):通過使用布局,開發(fā)者可以更加專注于界面的結(jié)構(gòu)設(shè)計(jì),而不是具體的位置和大小調(diào)整,從而簡化了界面設(shè)計(jì)過程。
          • 支持嵌套:布局可以嵌套使用,即一個(gè)布局可以包含其他布局,這允許創(chuàng)建復(fù)雜的界面結(jié)構(gòu)

          Layout繼承關(guān)系圖

          2、Pyside6 Layout布局解釋及示例

          以下是繼承自QtWidgets.QLayout的布局,每種布局有對(duì)應(yīng)的行為。

          布局

          行為

          對(duì)應(yīng)html

          QHBoxLayout

          線性水平布局

          類似于display: flex; flex-direction: row;的Flexbox布局

          QVBoxLayout

          線性垂直布局

          類似于display: flex; flex-direction: column;的Flexbox布局

          QGridLayout

          在可轉(zhuǎn)位網(wǎng)格 XxY 中

          類似于html的table行和列

          QStackedLayout

          堆疊 (z) 于彼此前面

          類似于css的z-index,并控制隱藏顯示效果

          QHBoxLayout

          QHBoxLayout是Qt中的一個(gè)布局管理器類,用于按水平方向排列小部件。它繼承自QLayout,提供了一種簡便的方式來自動(dòng)管理窗口或?qū)υ捒蛑行〔考奈恢煤痛笮 J褂肣HBoxLayout,可以將小部件從左到右依次排列,而不需要手動(dòng)指定每個(gè)小部件的具體位置。

          QHBoxLayout繼承關(guān)系圖

          簡單的示例如下:

          import sys
          from PySide6.QtWidgets import *
          class MainWindow(QMainWindow):
              def __init__(self):
                  super(MainWindow, self).__init__()
                  self.setWindowTitle("QHBoxLayout之水平布局")
                  self.setMinimumSize(400,300)
                  layout=QHBoxLayout()
                  layout.addWidget(QPushButton("1"))
                  layout.addWidget(QPushButton("2"))
                  layout.addWidget(QPushButton("3"))
                  widget=QWidget()
                  widget.setLayout(layout)
                  self.setCentralWidget(widget)
          
          if __name__=='__main__':
              app=QApplication(sys.argv)
              window=MainWindow()
              window.show()
              app.exec()

          QHBoxLayout示例效果圖

          QVBoxLayout

          QVBoxLayout是Qt中的一個(gè)布局管理器類,用于垂直方向排列小部件。它繼承自QLayout,提供了一種簡便的方式來自動(dòng)管理窗口或?qū)υ捒蛑行〔考奈恢煤痛笮 J褂肣VBoxLayout,可以將小部件從上到下依次排列,而不需要手動(dòng)指定每個(gè)小部件的具體位置。

          QVBoxLayout繼承關(guān)系圖

          簡單示例如下:

          import sys
          from PySide6.QtWidgets import *
          class MainWindow(QMainWindow):
              def __init__(self):
                  super(MainWindow, self).__init__()
                  self.setWindowTitle("QVBoxLayout之垂直布局")
                  self.setMinimumSize(400, 300)
                  layout=QVBoxLayout()
                  layout.addWidget(QPushButton("1"))
                  layout.addWidget(QPushButton("2"))
                  layout.addWidget(QPushButton("3"))
                  widget=QWidget()
                  widget.setLayout(layout)
                  self.setCentralWidget(widget)
          
          if __name__=='__main__':
              app=QApplication(sys.argv)
              window=MainWindow()
              window.show()
              app.exec()

          效果圖如下:

          QVBoxLayout示例效果圖

          QGridLayout

          QGridLayout是Qt中一個(gè)非常強(qiáng)大的布局管理器,它提供一種網(wǎng)格式布局,這種布局由行和列組成(類似table),每個(gè)小部件占據(jù)網(wǎng)格中的一個(gè)或多個(gè)單元格。QGridLayout提供了靈活的方式來創(chuàng)建復(fù)雜的用戶界面,使得小部件的布局可以精確控制,同時(shí)也能自動(dòng)適應(yīng)窗口大小的變化,主要特性有:

          • 行列管理:可以指定小部件應(yīng)該放在網(wǎng)格的哪一行哪一列,甚至可以跨越多行多列。
          • 自動(dòng)調(diào)整大小:當(dāng)窗口大小改變時(shí),QGridLayout會(huì)自動(dòng)調(diào)整小部件的大小和位置,保持布局的整潔和一致性。
          • 最小寬度和拉伸因子:每列(或行)可以有一個(gè)最小寬度和一個(gè)拉伸因子,這決定了在可用空間中它們將如何分配額外的空間。
          • 間距和邊距:可以設(shè)置小部件之間的間距(spacing())和布局邊緣的邊距(內(nèi)容邊距),以控制布局的外觀。

          QGridLayout繼承關(guān)系圖

          示例代碼如下:

          import sys
          from PySide6.QtWidgets import *
          class MainWindow(QMainWindow):
              def __init__(self):
                  super(MainWindow, self).__init__()
                  self.setWindowTitle("QGridLayout之網(wǎng)格布局")
                  self.setMinimumSize(400, 300)
                  layout=QGridLayout()
                  # 為窗口部件設(shè)置樣式表,添加邊框
                  self.setStyleSheet("QWidget { border: 2px solid black; }")
                  layout.addWidget(QLabel('第0行第0列'), 0, 0)
                  layout.addWidget(QLabel('第0行第1列'), 0, 1)
                  layout.addWidget(QLabel('第1行第0列'), 1, 0)
                  layout.addWidget(QLabel('第1行第1列'), 1, 1)
                  widget=QWidget()
                  widget.setLayout(layout)
                  self.setCentralWidget(widget)
          
          if __name__=='__main__':
              app=QApplication(sys.argv)
              window=MainWindow()
              window.show()
              app.exec()

          效果圖如下:

          QGridLayout示例效果圖

          QStackedLayout

          QStackedLayout是Qt中的一個(gè)布局管理器,它可以在相同的空間內(nèi)堆疊多個(gè)小部件,但一次只顯示一個(gè)小部件。這種布局非常適合用于實(shí)現(xiàn)向?qū)А⑦x項(xiàng)卡和其他需要在多個(gè)頁面之間切換的界面。

          QStackedLayout繼承關(guān)系圖

          主要特性:

          • 堆疊小部件:在同一個(gè)布局空間內(nèi)堆疊多個(gè)小部件。
          • 單一可見性:一次只有一個(gè)小部件可見。
          • 動(dòng)態(tài)切換:可以編程方式動(dòng)態(tài)切換當(dāng)前可見的小部件

          常用方法技巧:indexOf()函數(shù)返回小部件在該列表中的索引。可以使用addWidget()函數(shù)添加小部件到列表末尾,或者使用insertWidget()函數(shù)在給定索引處插入。removeWidget()函數(shù)從布局中移除給定索引的小部件。可以使用count()函數(shù)獲取布局中包含的小部件數(shù)量。widget()函數(shù)返回給定索引位置的小部件。當(dāng)前顯示在屏幕上的小部件的索引由currentIndex()給出,并且可以使用setCurrentIndex()進(jìn)行更改。以類似的方式,可以使用currentWidget()函數(shù)檢索當(dāng)前顯示的小部件,并使用setCurrentWidget()函數(shù)進(jìn)行更改。每當(dāng)布局中的當(dāng)前小部件發(fā)生變化或從布局中移除小部件時(shí),分別會(huì)發(fā)出currentChanged()和widgetRemoved()信號(hào)。

          示例代碼如下:

          import sys
          from PySide6.QtGui import QPalette, QColor
          from PySide6.QtWidgets import *
          class MainWindow(QMainWindow):
              def __init__(self):
                  super().__init__()
                  self.setWindowTitle("QStackedLayout之堆疊布局")
                  self.setMinimumSize(400, 300)
                  pagelayout=QVBoxLayout()
                  button_layout=QHBoxLayout()
                  self.stacklayout=QStackedLayout()
                  pagelayout.addLayout(button_layout)
                  pagelayout.addLayout(self.stacklayout)
                  btn=QPushButton("red")
                  btn.pressed.connect(self.activate_tab_1)
                  button_layout.addWidget(btn)
                  self.stacklayout.addWidget(Color("red"))
                  btn=QPushButton("green")
                  btn.pressed.connect(self.activate_tab_2)
                  button_layout.addWidget(btn)
                  self.stacklayout.addWidget(Color("green"))
                  btn=QPushButton("yellow")
                  btn.pressed.connect(self.activate_tab_3)
                  button_layout.addWidget(btn)
                  self.stacklayout.addWidget(Color("yellow"))
                  widget=QWidget()
                  widget.setLayout(pagelayout)
                  self.setCentralWidget(widget)
          
              def activate_tab_1(self):
                  self.stacklayout.setCurrentIndex(0)
          
              def activate_tab_2(self):
                  self.stacklayout.setCurrentIndex(1)
          
              def activate_tab_3(self):
                  self.stacklayout.setCurrentIndex(2)
          
          class Color(QWidget):
              def __init__(self, color):
                  super(Color, self).__init__()
                  self.setAutoFillBackground(True)
                  palette=self.palette()
                  palette.setColor(QPalette.Window, QColor(color))
                  self.setPalette(palette)
          
          if __name__=='__main__':
              app=QApplication(sys.argv)
              window=MainWindow()
              window.show()
              app.exec()

          效果圖如下:

          QStackedLayout示例效果圖


          混合布局

          使用QHBoxLayout、QVBoxLayout、QGridLayout、QStackedLayout這幾種布局組合使用,來控制界面的整體風(fēng)格視角,制作精美的布局效果

          示例代碼如下:

          import sys
          from PySide6.QtWidgets import *
          
          class MainWindow(QMainWindow):
              def __init__(self):
                  super(MainWindow, self).__init__()
                  self.setWindowTitle("混合布局")
                  self.setMinimumSize(400, 300)
                  self.setStyleSheet("QLabel { border: 1px solid blue; }")
                  layout1=QHBoxLayout()
                  layout2=QVBoxLayout()
                  layout3=QVBoxLayout()
                  layout1.setContentsMargins(0,0,0,0)
                  layout1.setSpacing(20)
                  layout2.addWidget(QLabel('hbox1-QVBoxLayout1'))
                  layout2.addWidget(QLabel('hbox1-QVBoxLayout2'))
                  layout2.addWidget(QLabel('hbox1-QVBoxLayout3'))
                  layout1.addLayout(layout2)
                  layout1.addWidget(QLabel('hbox2'))
                  layout3.addWidget(QLabel('hbox2-QVBoxLayout1'))
                  layout3.addWidget(QLabel('hbox2-QVBoxLayout2'))
                  layout1.addLayout(layout3)
                  widget=QWidget()
                  widget.setLayout(layout1)
                  self.setCentralWidget(widget)
          
          if __name__=='__main__':
              app=QApplication(sys.argv)
              window=MainWindow()
              window.show()
              app.exec()

          效果圖如下:


          混合布局效果圖

          注意:QLayout邊框及樣式不能通過這種方式來設(shè)置樣式效果,需要指定Widget的樣式

          #無效果
          self.setStyleSheet("QHBoxLayout { border: 1px solid black; }")
          #有效果
          self.setStyleSheet("QLabel { border: 1px solid blue; }")

          自定義Layout,實(shí)現(xiàn)部件自動(dòng)換行

          由于QHBoxLayout、QVBoxLayout、QGridLayout、QStackedLayout布局放置的控件,不增加任何處理,生成的控件會(huì)固定住窗口大小,可以通過下面來動(dòng)態(tài)排列控件。

          示例代碼如下:

          import sys
          from PySide6.QtCore import QRect, QSize, QPoint, Qt
          from PySide6.QtWidgets import *
          
          class FlowLayout(QLayout):
              def __init__(self, parent=None, margin=0, spacing=-1):
                  super(FlowLayout, self).__init__(parent)
                  if parent is not None:
                      self.setContentsMargins(margin, margin, margin, margin)
                  self.setSpacing(spacing)
                  self.items=[]
          
              def addItem(self, item):
                  self.items.append(item)
          
              def count(self):
                  return len(self.items)
          
              def itemAt(self, index):
                  if index >=0 and index < len(self.items):
                      return self.items[index]
                  return None
          
              def takeAt(self, index):
                  if index >=0 and index < len(self.items):
                      return self.items.pop(index)
                  return None
          
              def expandingDirections(self):
                  return 0
          
              def hasHeightForWidth(self):
                  return True
          
              def heightForWidth(self, width):
                  height=self.doLayout(QRect(0, 0, width, 0), True)
                  return height
          
              def setGeometry(self, rect):
                  super(FlowLayout, self).setGeometry(rect)
                  self.doLayout(rect, False)
          
              def sizeHint(self):
                  return QSize(self.doLayout(QRect(0, 0, 10000, 0), True), 10000)
          
              def doLayout(self, rect, testOnly):
                  x=rect.x()
                  y=rect.y()
                  lineHeight=0
                  for item in self.items:
                      wid=item.widget()
                      spaceX=self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton,Qt.Horizontal)
                      spaceY=self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton,Qt.Vertical)
                      nextX=x + item.sizeHint().width() + spaceX
                      if nextX - spaceX > rect.right() and lineHeight > 0:
                          x=rect.x()
                          y=y + lineHeight + spaceY
                          nextX=x + item.sizeHint().width() + spaceX
                          lineHeight=0
                      if not testOnly:
                          item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
                      x=nextX
                      lineHeight=max(lineHeight, item.sizeHint().height())
          
                  return y + lineHeight - rect.y()
          if __name__=='__main__':
              app=QApplication(sys.argv)
              mainWidget=QWidget()
              mainWidget.setMinimumSize(300, 200)
              layout=FlowLayout(mainWidget)
              for i in range(50):
                  layout.addWidget(QPushButton(f'Button {i}'))
              mainWidget.setLayout(layout)
              mainWidget.show()
              sys.exit(app.exec_())

          效果如下:

          自定義Layout效果圖

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

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

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

          • 演示參見 https://codepen.io/gangtao/full/OZvedx/
          • 代碼參見 https://gist.github.com/gangtao/e053cf9722b64ef8544afa371c2daaee

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

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

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

          • axios 基于Promise的HTTP客戶端
          • alasql 基于JS的開源SQL數(shù)據(jù)庫
          • jquery datatable JQuery的數(shù)據(jù)表格插件

          數(shù)據(jù)通過我存放在GitHub中的csv格式的文件,以REST請(qǐng)求的方式來加載。下面的代碼把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請(qǐng)求,獲取csv文件。

          let csv=await request.get(url);

          這一步可能會(huì)遇到跨域請(qǐng)求的問題,github上的文件支持跨域。

          把數(shù)據(jù)存儲(chǔ)在一個(gè)SQL數(shù)據(jù)庫中,這樣做的好處是為了下一步做數(shù)據(jù)準(zhǔn)備的時(shí)候,可以方便的利用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是一個(gè)對(duì)數(shù)據(jù)表的封裝,把csv數(shù)據(jù)存在SQL數(shù)據(jù)庫表中,提供一個(gè)query()方法。這里要做的是把SQL查詢個(gè)從 "SELECT * FROM table" 變成 "SELECT * FROM CSV(?)" 表示查詢參數(shù)是CSV數(shù)據(jù)。因?yàn)閏odepen的安全性限制,運(yùn)行前向查找的replace語句(這里的regex表示把前面是“FROM ”詞的替換為CSV(?)的)在full page view下是不能執(zhí)行的,所以我用了一個(gè)更簡單的假定,用戶的表名就是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
           });
          }

          這一步有兩點(diǎn)要注意:

          1. 數(shù)據(jù)中,如果列的名字中有包含點(diǎn),空格等字符,例如Iris數(shù)據(jù)集中的Sepal.Length,datatable是無法正常顯示的,這里要調(diào)用sanitizeData()方法把列名,也就是JsonArray中Json對(duì)象的屬性名中的點(diǎn)和空格去掉。
          2. sanitizeData()方法會(huì)改變輸入對(duì)象,所以在傳入之前做了一個(gè)深度拷貝,這里利用JSON的stringfy和parse方法可以對(duì)JSON兼容的對(duì)象有效的拷貝。

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

          數(shù)據(jù)準(zhǔn)備

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

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

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

          這個(gè)是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ù)的類型判斷出每一個(gè)字段是數(shù)值型還是字符型。對(duì)于字符型的字段,我們利用JS6的Set來獲得所有的Unique數(shù)據(jù)。對(duì)于數(shù)值型,我們利用d3的max,min,mean,median,deviation方法計(jì)算出對(duì)應(yīng)的最大值,最小值,平均數(shù),中位數(shù)和偏差。

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

          上圖的例子中我們利用限制條件得到一個(gè)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)計(jì)函數(shù),匯總統(tǒng)計(jì)、百分比、封箱 等統(tǒng)計(jì)函數(shù),查看 Transform特殊數(shù)據(jù)處理,包括 地理數(shù)據(jù)、矩形樹圖、桑基圖、文字云 的數(shù)據(jù)處理,查看 Transform

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

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

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

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

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

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

          幾何標(biāo)記 Geometry

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

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

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

          圖形屬性 Attributes

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

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

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

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

          坐標(biāo)系 Coordinates

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

          G2提供了以下幾種坐標(biāo)系:



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

          分面 Facet

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

          1. 按照指定的維度劃分?jǐn)?shù)據(jù)集;
          2. 對(duì)圖表進(jìn)行排版。

          G2支持以下的分面類型:



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

          注意,在我的代碼中,為了簡化使用,只支持list和rect,當(dāng)綁定一個(gè)字段的時(shí)候用list,綁定兩個(gè)字段的時(shí)候用rect。

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

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

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

          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;
          }

          這里有幾點(diǎn)要注意:

          • 使用JS的模版字符串可以有效的構(gòu)造代碼片段
          • 使用eval執(zhí)行構(gòu)造好的語法驅(qū)動(dòng)的代碼來響應(yīng)select的change事件,以獲得良好的交互性。在生產(chǎn)環(huán)境,要注意該方法的安全性隱患,因?yàn)榧兦岸耍琫val能帶來的威脅比較小,生產(chǎn)中,可以把這個(gè)執(zhí)行放在安全的沙箱中運(yùn)行
          • 你需要理解圖形語法,并不是任意的組合都能驅(qū)動(dòng)出有效的圖形。

          這里對(duì)于select2的多選,有一個(gè)小的提示,在缺省情況下,多選的順序是固定的順序,并不依賴選擇的順序,然而許多圖形語法和字段的順序有關(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)前選中的項(xiàng)目移到數(shù)據(jù)最后的位置。

          一些例子

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

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

          圖形語法:

          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');
           }
          });

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

          總結(jié)

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

          • JS 370 行 JS6
          • HTML 69 + 9 + 5=83
          • CSS 21

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

          結(jié)合我之前分享的TensorflowJS的文章,下面一步可能是加入預(yù)測(cè)功能,為數(shù)據(jù)分析加入智能,前端應(yīng)用的前景,不可限量!

          參考

          • axios 基于Promise的HTTP客戶端
          • alasql 基于JS的開源SQL數(shù)據(jù)庫
          • jquery datatable JQuery的數(shù)據(jù)表格插件
          • select2 JQuery的選擇控件插件
          • 相關(guān)文章 再談使用開源軟件搭建數(shù)據(jù)分析平臺(tái)
          • 相關(guān)文章 使用開源軟件快速搭建數(shù)據(jù)分析平臺(tái)
          • 相關(guān)文章 高維數(shù)據(jù)可視化圖形語法指南

          .先上最后效果圖:

          2.代碼跟上,重點(diǎn)在 2):

          1)

          //服務(wù)列表頁面動(dòng)態(tài)加載服務(wù)

          function ready() {

          var url=base_path+"console/cfg/getBaseLayers/"+configId;

          $.ajax({

          url:url,

          type:"get",

          dataType:"json",

          success:function (result) {

          //生成之前先清空tr,防止AJAX異步加載重復(fù)生成

          $("#lot tr").remove();

          var length=result.length;

          for (var i=0;i<length;i++){

          var name=result[i].name; //服務(wù)名稱

          var alias=result[i].alias;//服務(wù)別名

          var type=result[i].type;//服務(wù)類型

          var year=result[i].year;//年份

          var url=result[i].url;//服務(wù)地址

          var visible=result[i].visible;//是否可見

          var id=result[i].id;//id

          serviceIdArray[i]=id;//此處將id塞給serviceIdArray,用于判斷是否添加。

          var str="";

          if (visible==true){

          //生成tr

          str +='<tr id="';

          str +=id;

          str +='"';

          str +=' class="lot_box"> <td>';

          str +=i+1;

          str +='</td> <td>';

          str +=name;

          str +='</td> <td>';

          str +=alias;

          str +='</td> <td>';

          str +=type;

          str +='</td> <td>';

          str +=year;

          str +='</td> <td>';

          str +=url;

          str +='</td> <td>';

          str +='<input id="';

          str +=id;

          str +='"';

          str +='type="checkbox" checked="true" onchange="modifyService(this.id);"/>';

          str +='</td> <td> <input id="';

          str +=id;

          str +='"';

          str +='type="button" value="編輯" onclick="editTd(this.id)"/>';

          str +='</td> <td>';

          str +='<button class="rosy" id="';

          str +=id;

          str +='"';

          str +=' onclick="deleteService(this.id);">';

          str +='<img src="static/img/del14.png"></button>';

          str +='</td> </tr>';

          }else {

          //生成tr

          str +='<tr id="';

          str +=id;

          str +='"';

          str +=' class="lot_box"> <td>';

          str +=i+1;

          str +='</td> <td>';

          str +=name;

          str +='</td> <td>';

          str +=alias;

          str +='</td> <td>';

          str +=type;

          str +='</td> <td>';

          str +=year;

          str +='</td> <td>';

          str +=url;

          str +='</td> <td>';

          str +='<input id="';

          str +=id;

          str +='"';

          str +='type="checkbox" onchange="modifyService(this.id);"/>';

          str +='</td> <td> <input id="';

          str +=id;

          str +='"';

          str +='type="button" value="編輯" onclick="editTd(this.id)"/>';

          str +='</td> <td>';

          str +='<button class="rosy" id="';

          str +=id;

          str +='"';

          str +=' onclick="deleteService(this.id);">';

          str +='<img src="static/img/del14.png"></button>';

          str +='</td> </tr>';

          }

          var $tr=$(str);

          $("#lot").append($tr);

          }

          }

          });

          }


          主站蜘蛛池模板: 国产成人一区二区三区高清 | 亚洲日韩国产一区二区三区| 久久久久久综合一区中文字幕| 日韩免费无码视频一区二区三区 | 国产精久久一区二区三区| 亚洲狠狠狠一区二区三区| 国产综合无码一区二区辣椒| 影院无码人妻精品一区二区| 日韩精品一区二区三区毛片| 中文字幕一区二区三区人妻少妇 | 成人国内精品久久久久一区| 国产福利无码一区在线| 亚洲av无码一区二区三区天堂| 久久精品无码一区二区无码| 无码人妻一区二区三区在线| 日韩人妻无码一区二区三区| A国产一区二区免费入口| 国产福利电影一区二区三区| 日韩视频在线观看一区二区| 日本一区二区三区久久| 好吊妞视频一区二区| 在线精品一区二区三区电影| 人妻少妇精品视频三区二区一区| 亚洲一区免费视频| 亚洲乱色熟女一区二区三区蜜臀| 91午夜精品亚洲一区二区三区| 精品一区二区三区在线成人| 亚洲国产一区在线| 亚洲一区二区三区首页| 国产成人一区二区三区视频免费| 国产suv精品一区二区33| 久久久无码一区二区三区| 国产精品高清视亚洲一区二区| 亚洲AV日韩综合一区| 精品一区二区AV天堂| 一区二区三区在线观看视频| 国产av熟女一区二区三区| 午夜福利一区二区三区高清视频| 亚洲国产精品综合一区在线 | 亚洲一区在线视频观看| 天堂Av无码Av一区二区三区|