本文首發於 hzzly的博客原文連接:React數據大屏的應用實踐css
現現在大數據已無所不在,而且正被愈來愈普遍的被應用到歷史、政治、科學、經濟、商業甚至滲透到咱們生活的方方面面中,獲取的渠道也愈來愈便利。html
今天咱們就來聊一聊「大屏應用」,說到大屏就必定要聊到數據可視化,現現在,數據可視化因爲數據分析的火熱也變得火熱起來,不過數據可視化並非一個新技術,可視化數據就是用可視化的方式展示的數據。而數據大屏做爲大數據展現媒介的一種,普遍運用於各類展現廳、會展、發佈會及各類狂歡節中,其中不乏一些通用的處理方案:阿里的DataV、百度的Suger、騰訊RayData等等。vue
隨着物聯網、5G等各類跟鏈接有關的技術的出現與發展,每一個人手中掌握的數據量都呈指數級增加,光看這些數是看不過來也看不懂的,「數據可視化」就是一種簡化,讓艱難的數據理解過程,變成——看顏色,辨長短,分高低。從而大大縮短理解數據所需的時間。react
因公司的自研產品涉及到BI模塊,所以數據大屏展現的需求孕育而生(數據大屏需求已經完成)。webpack
下面是本人針對這個數據大屏需求前期作的一些探索實踐,數據也是mock的。git
六種基本圖表涵蓋了大部分圖表使用場景,也是作數據可視化最經常使用的圖表類型:github
基本圖表類型都有通用的樣式,不過多的展開講解。咱們更多的考慮如何選擇經常使用圖表來呈現數據,達到數據可視化的目標。基本方法:明確目標 —> 選擇圖形 —> 梳理維度 —> 突出關鍵信息。web
當信息一旦準備就緒,咱們就須要從服務器獲取它們。這裏咱們須要一種基於推送的方法,例如 WebSocket 協議、輪詢、服務器推送事件(SSE)以及最近的 HTTP2 服務器推送。這裏咱們簡單比較一下 WebSocket 與輪詢。ajax
輪詢須要客戶端定時向服務器發送ajax請求,服務器接到請求後返回響應信息。這就須要大量的佔據服務器資源。同時在HTTP1.x協議中也存在一些好比線頭阻塞、頭部冗餘等問題。因此這種方案直接pass了。服務器
再來講說 WebSocket,創建在 TCP 協議之上,數據格式比較輕量,性能開銷小,通訊高效,能夠發送文本,也能夠發送二進制數據。同時它尚未同源限制,客戶端能夠與任意服務器通訊。還有一點 WebSocket 一般不使用 XMLHttpRequest,所以,當咱們每次須要從服務器獲取更多的信息時,無需發送頭部數據。反過來講,這又減小了數據發送到服務器時須要付出的高昂的數據負載代價。對於數據大屏須要實時獲取數據,這無疑是最高效的。
數據大屏的核心就是數據的拼接,具體到展現層能夠概括成數據塊的拼接。這裏咱們採用通用的尺寸1920*108(16:9)。尺寸確立後,接下來要對展現層進行佈局和頁面的劃分。這裏的劃分,主要根據咱們以前定好的業務指標進行,核心業務指標安排在中間位置、佔較大面積;其他的指標按優先級依次在覈心指標周圍展開。通常把有關聯的指標讓其相鄰或靠近,把圖表類型相近的指標放一塊兒,這樣能減小觀者認知上的負擔並提升信息傳遞的效率。
對於這種塊狀(網格)佈局,咱們就可使用咱們強大的 CSS 佈局方案 -- Grid。它將網頁劃分紅一個個網格,能夠任意組合不一樣的網格,作出各類各樣的佈局。
安利一個grid 佈局可視化設計工具 -- CSS Grid Generator。可使用它生成對應的代碼,幫助我們快速佈局。
聊完這些通用知識咱們就能夠上手開發了。
我這裏使用了我本身開發的腳手架(hzzly-cli)來生成react項目環境。
有興趣瞭解腳手架開發的能夠看我這篇文章 動手開發一個本身的項目腳手架
項目結構以下:
├── src │ ├── assets // 資源目錄 │ ├── components // 公共組件目錄 │ │ ├── Card // Card組件 │ │ ├── Charts // 圖表組件目錄 │ │ │ ├── Bar // 柱狀圖 │ │ │ ├── ChinaMap // 中國地圖 │ │ │ ├── Funnel // 漏斗圖 │ │ │ ├── Line // 折線圖 │ │ │ ├── Pie // 餅圖 │ │ │ └── lib // 基礎圖表組件 │ │ ├── ScrollNumber // 滾動數字組件 │ │ └── SvgIcon // Icon組件 │ ├── global.scss │ ├── index.js │ ├── pages // 分塊結構目錄 │ ├── router // 路由 │ ├── store │ │ ├── actions │ │ ├── index.js │ │ ├── reducers │ │ ├── sagas │ │ └── types.js │ └── utils │ ├── genChartData.js │ ├── genMapData.js │ ├── socket.js │ └── util.js
這裏對echarts-for-react
進一步封裝,其它圖表組件能夠直接繼承使用。
// Charts/lib/BaseChart.js import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import Echarts from 'echarts-for-react'; export default class BaseChart extends PureComponent { static propTypes = { option: PropTypes.object.isRequired, data: PropTypes.object.isRequired, getOption: PropTypes.func.isRequired, style: PropTypes.object, }; static defaultProps = { style: {}, }; componentDidMount() { const { runAction } = this.props; if (this.chartRef && runAction) { const chartIns = this.chartRef.getEchartsInstance(); window.setTimeout(() => { runAction(chartIns); }, 300); } } render() { const { option, data, getOption, style } = this.props; const finalOption = getOption(option, data); const finalStyle = getStyle(style); return ( <Echarts ref={ref => { this.chartRef = ref; }} style={finalStyle} option={finalOption} notMerge lazyUpdate /> ); } } function getStyle(style) { return Object.assign({ position: 'relative' }, style ); }
使用:
// line.js import BaseChart from '../lib/BaseChart'; import option from './option'; import getOption from './getOption'; export default class Line extends BaseChart { static defaultProps = { option, getOption, }; } // option.js 基礎配置 export default { // ... }; // getOption.js 計算配置文件 function seriesCreator(series) { return series.map(e => ({ type: 'line', symbol: 'circle', smooth: true, lineStyle: { normal: { width: 3, }, }, ...e, })); } export default function(option, data) { const { tooltip, xAxis, yAxis, yCategory, series = [], ...rest } = data; return { ...option, xAxis: { ...option.xAxis, ...xAxis, }, tooltip: { ...option.tooltip, ...tooltip, }, yAxis: { ...option.yAxis, ...yAxis, data: yCategory || [], }, series: seriesCreator(series), ...rest, }; }
這裏對socket.io-client
封裝成SDK,方便使用。
import io from 'socket.io-client'; const socket = { wsConn: null, config: { wsHost: '/', // wesocket host onConn() {}, onDisconn() {}, onError() {}, onReceiveMsg() {}, }, init(opt) { socket.config = { ...socket.config, ...opt }; }, getWs() { if (socket.wsConn) { return socket.wsConn; } else { socket.initWs(); } }, getWsStatus() { return socket.wsConn ? socket.wsConn.connected : false; }, initWs() { if (socket.getWsStatus()) { return socket.wsConn; } const wsUrl = socket.config.wsHost; socket.wsConn = io.connect(wsUrl); socket.wsConn.on('connect', () => { socket.config.onConn(socket.wsConn); }); socket.wsConn.on('message', (...param) => { socket.config.onReceiveMsg(...param); }); socket.wsConn.on('disconnect', () => { socket.config.onDisconn(); }); return socket.wsConn; }, reconnect() { if (socket.wsConn) { if (socket.wsConn.disconnected) { // reconnect ws } else { // do nothing } } else { socket.initWs(); } }, disconnect() { if (socket.wsConn) { if (socket.wsConn.connected) { socket.wsConn.disconnect(); } } }, wsEmit(params) { if (socket.wsConn) { socket.wsConn.emit(params.name, params.data); } }, }; (function(global) { global.socket = socket; })(window); export { socket };
該數據經過socket推送實時更新。
數字過渡的動態效果爲對應數位的新數字從下至上替換舊數字,若是該位數的數字沒有發生變化,則沒有過渡效果。
一、對數據進行完善並格式化
針對數字少於9位數進行前位補零並進行千分位格式化
const MAX_LEN = 9; function toThousands(val) { let num = (val || 0).toString(); while (num.length < MAX_LEN) { num = `0${num}`; } let result = ''; while (num.length > 3) { result = `,${num.slice(-3)}${result}`; num = num.slice(0, num.length - 3); } if (num) { result = num + result; } return result.toString().split(''); }
二、過渡動畫
利用樣式控制過渡動畫,在第一步中咱們對數字進行了格式化,而後咱們針對每一位數字進行比較,當數字不相等的時候添加active
類,最後對active
類添加動畫。
// 循環渲染每一位數字 <li className={`${oldNumber[i] !== newNumber[i] ? 'active' : ''}`}> <span className="num">{oldNumber[i]}</span> <span className="num">{newNumber[i]}</span> </li>
.active { .num { animation: move 1.5s; animation-fill-mode: forwards; // 讓動畫結束後保持最後一幀 } } @keyframes move { from { transform: translateY(0); } to { transform: translateY(-100%); } }
這裏我使用了我本身封裝的組件,能夠對應框架來安裝引用:
一、項目框架目錄結構採用筆者本身搭建的webpack環境:webpack-template
二、關於適配和兼容性暫時還未完善,若是後期有時間會慢慢去完善
三、此項目爲筆者調研時的實踐,由於時間有限,一些功能還不善,設計和佈局都是本身的一些想象與參考
四、此項目做爲開源學習使用,謝絕用於商業應用
代碼已上傳至個人GitHub,歡迎 Star、Fork