本文首发于 hzzly的博客css
原文连接:React数据大屏的应用实践html
现现在大数据已无所不在,而且正被愈来愈普遍的被应用到历史、政治、科学、经济、商业甚至渗透到咱们生活的方方面面中,获取的渠道也愈来愈便利。vue
今天咱们就来聊一聊“大屏应用”,说到大屏就必定要聊到数据可视化,现现在,数据可视化因为数据分析的火热也变得火热起来,不过数据可视化并非一个新技术,可视化数据就是用可视化的方式展示的数据。而数据大屏做为大数据展现媒介的一种,普遍运用于各类展现厅、会展、发布会及各类狂欢节中,其中不乏一些通用的处理方案:阿里的DataV、百度的Suger、腾讯RayData等等。react
随着物联网、5G等各类跟链接有关的技术的出现与发展,每一个人手中掌握的数据量都呈指数级增加,光看这些数是看不过来也看不懂的,“数据可视化”就是一种简化,让艰难的数据理解过程,变成——看颜色,辨长短,分高低。从而大大缩短理解数据所需的时间。webpack
因公司的自研产品涉及到BI模块,所以数据大屏展现的需求孕育而生(数据大屏需求已经完成)。git
下面是本人针对这个数据大屏需求前期作的一些探索实践,数据也是mock的。github
六种基本图表涵盖了大部分图表使用场景,也是作数据可视化最经常使用的图表类型:web
基本图表类型都有通用的样式,不过多的展开讲解。咱们更多的考虑如何选择经常使用图表来呈现数据,达到数据可视化的目标。基本方法:明确目标 —> 选择图形 —> 梳理维度 —> 突出关键信息。ajax
当信息一旦准备就绪,咱们就须要从服务器获取它们。这里咱们须要一种基于推送的方法,例如 WebSocket 协议、轮询、服务器推送事件(SSE)以及最近的 HTTP2 服务器推送。这里咱们简单比较一下 WebSocket 与轮询。服务器
轮询须要客户端定时向服务器发送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
二、关于适配和兼容性暂时还未完善,若是后期有时间会慢慢去完善
三、此项目为笔者调研时的实践,由于时间有限,一些功能还不善,设计和布局都是本身的一些想象与参考
四、此项目做为开源学习使用,谢绝用于商业应用