本文首发于 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