数据可视化之使用echarts搭建可视化大屏
什么是数据可视化?
数据可视化简单理解,就是将数据转换成易于人员辨识和理解的视觉表现形式,如各种 2D 图表、3D 图表、地图、矢量图等等,随着技术的不断进步,数据可视化的边界也在不断扩大
数据可视化的应用场景
-
数据大屏
-
数据报表
-
地图
数据可视化的解决方案
Skia
Skia 是 Chrome 和 Android 的底层 2D 绘图引擎,具体可参考百度百科,Skia 采用 C++ 编程,由于它位于浏览器的更底层,所以我们平常接触较少
OpenGL
OpenGL(Open Graphics Library)是2D、3D图形渲染库,它可以绘制从简单的2D图形到复杂的3D景象。OpenGL 常用于 CAD、VR、数据可视化和游戏等众多领域。
Chrome
Chrome 使用 Skia 作为绘图引擎,向上层开放了 canvas、svg、WebGL、HTML 等绘图能力。
对于前端来说,对于这些底层的不用了解太多,只需要关注上面红色区域的解决方案,目前使用最广泛的有ECharts、Highcharts等
ECharts VS Highcharts
Highcharts 和 ECharts 的争论非常多,整体来说,我个人的感受是:
- Highcharts 能够兼容 IE6+,ECharts 通过 VML 兼容低端浏览器
- Highcharts 文档体验略胜一筹
- Highcharts 收费,这是很多开发者转向 ECharts 的主要原因
- Highcharts 基于 svg 实现,ECharts 默认采用 canvas 渲染,4.0 支持 svg 渲染
- ECharts 国内知名度更高,国内企业认可度更高
ECharts VS AntV
- AntV 文档阅读体验更符合互联网产品使用习惯
- AntV 产品体系拆分更加清晰,但一定程度上提升了学习成本
- ECharts 社区更强大
- ECharts 使用更加广泛
ECharts 优势总结
- 简单易用
- 文档全面
- 社区强大
- 高知名度
如何使用ECharts
实现一个最简单的柱状图
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script>
<style>
#chart {
width: 800px;
height: 400px;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
const chartDom = document.getElementById('chart')
const chart = echarts.init(chartDom)
chart.setOption({
title: {
text: '快速入门ECharts开发'
},
xAxis: {
data: ['食品', '数码', '服饰', '箱包']
},
yAxis: {},
series: {
type: 'bar',
data: [100, 120, 90, 150]
}
})
</script>
</body>
</html>
ECharts适配方案
我查阅了很多的资料,收集了很多项目的大屏适配方案,主要来说,分为几种:
基于 flexible.js + rem 智能大屏适配
flexible.js是淘宝移动端自适应解决方案,源码含注解如下:
// 首先是一个立即执行函数,执行时传入的参数是window和document
(function flexible(window, document) {
// 返回文档的root元素
var docEl = document.documentElement;
// 获取设备的dpr,即当前设置下物理像素与虚拟像素的比值
var dpr = window.devicePixelRatio || 1;
// 设置默认字体大小,默认的字体大小继承自body
function setBodyFontSize() {
if (document.body) {
// 调整body标签的fontSize,fontSize = (12 * dpr) + 'px'
document.body.style.fontSize = 12 * dpr + 'px';
} else {
document.addEventListener('DOMContentLoaded', setBodyFontSize);
}
}
setBodyFontSize();
// set 1rem = viewWidth / 24
function setRemUnit() {
// 设置root元素的fontSize = 其clientWidth / 24 + 'px'
var rem = docEl.clientWidth / 24;
docEl.style.fontSize = rem + 'px';
}
setRemUnit();
// 当页面展示或重新设置大小的时候,触发重新
window.addEventListener('resize', setRemUnit);
window.addEventListener('pageshow', function(e) {
if (e.persisted) {
setRemUnit();
}
});
// 检测0.5px的支持,支持则root元素的class中有hairlines
if (dpr >= 2) {
var fakeBody = document.createElement('body');
var testElement = document.createElement('div');
testElement.style.border = '.5px solid transparent';
fakeBody.appendChild(testElement);
docEl.appendChild(fakeBody);
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines');
}
docEl.removeChild(fakeBody);
}
})(window, document);
以设计稿1920px为例,将其等分成24等份,那么1rem = 80px
显而易见,通过flexible.js的方式,不仅要引入这个外部js文件,通过读源码,还得了解关于dpr、rem等相关知识,还需要考虑document的fontsize的大小,然后在写css的时候需要将px转化为rem,这样就会有很多踩坑的地方,会花费大量的时间
具体方案可以参考这位大佬的文章和开源项目:juejin.cn/post/684516…
vw和vh适配方案
- 按照设计稿的尺寸,将
px
按比例计算转为vw
和vh
- 转换公式如下
假设设计稿尺寸为1920*1080(做之前一定问清楚UI设计稿的尺寸)
即:
网页宽度=1920px
网页高度=1080px
我们都知道
网页宽度=100vw
网页宽度=100vh
所以,在1920x*1080px的屏幕分辨率下
1920px = 100vw
1080px = 100vh
这样一来,以一个宽300px和200px的div来说,其作所占的宽高,以vw和vh为单位,计算方式如下:
vwDiv = (300px / 1920px ) * 100vw
vhDiv = (200px / 1080px ) * 100vh
所以,就在1920*1080的屏幕分辨率下,计算出了单个div的宽高
当屏幕放大或者缩小时,div还是以vw和vh作为宽高的,就会自动适应不同分辨率的屏幕
所以,我们每次写css时都需要把px转化为vw、vh,所以我们需要借助scss的函数来帮我们计算。或者是通过postcss-px-to-viewport包去转换,下面采用第一种方案:
首先我们得安装scss
npm install sass@1.26.5 sass-loader@8.0.2 --save
然后封装一个utils.css
//使用scss的math函数,https://sass-lang.com/documentation/breaking-changes/slash-div
@use "sass:math";
//默认设计稿的宽度
$designWidth:1920;
//默认设计稿的高度
$designHeight:1080;
//px转为vw的函数
@function vw($px) {
@return math.div($px , $designWidth) * 100vw;
}
//px转为vh的函数
@function vh($px) {
@return math.div($px , $designHeight) * 100vh;
}
我们还需要进行webpack的配置,不然无法识别到我们的scss代码
所以,我只需要在vue.config.js
里配置一下utils.scss
的路径,就可以全局使用了
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports={
publicPath: '',
configureWebpack: {
name: "app name",
resolve: {
alias: {
'@': resolve('src')
}
}
},
css:{
//全局配置utils.scss,详细配置参考vue-cli官网
loaderOptions:{
sass:{
prependData:`@import "@/styles/utils.scss";`
}
}
}
}
最后在我们的vue页面中去使用
<template>
<div class="box">
</div>
</template>
<script>
export default{
name: "Box",
}
</script>
<style lang="scss" scoped="scoped">
/*
直接使用vw和vh函数,将像素值传进去,得到的就是具体的vw vh单位
*/
.box{
width: vw(300);
height: vh(100);
font-size: vh(16);
background-color: black;
margin-left: vw(10);
margin-top: vh(10);
border: vh(2) solid red;
}
</style>
这个方案不仅要配置webpack、安装scss,封装vw/vh的方法,还需要再vue文件中去使用vw、vh的函数去替换px的值。
我们同时还得考虑到在echarts中使用width、height等属性,因为echarts只识别px,我们这时候还得去封装js函数去把echarts中的width、height等属性转换为vw、vh
具体源码可以参考这位大佬的方案:juejin.cn/post/700908…
这个时候,大家是不是会发现好麻烦啊,并且css和js里面耦合太多函数了,很容易vw和vh就写反了,不够优雅简洁,还得安装一些第三方包,还有没有更简洁的方案呢?
使用scale方案
这个是我对比了很多方案,比较优劣之后觉得最容易上手也最容易理解的方案了
以vue3为例
// dataScreenRef为数据大屏页面的最外层div
const dataScreenRef = ref<HTMLElement | null>(null);
onMounted(() => {
// 初始化时为外层盒子加上缩放属性,防止刷新界面时就已经缩放
if (dataScreenRef.value) {
dataScreenRef.value.style.transform = `scale(${getScale()}) translate(-50%, -50%)`;
dataScreenRef.value.style.width = `1920px`;
dataScreenRef.value.style.height = `1080px`;
}
// 初始化echarts
initCharts();
// 为浏览器绑定事件
window.addEventListener("resize", resize);
});
// 初始化 echarts,这个就是通过http或者websocket获取关于echarts的数据进行组装
const initCharts = () => {}
// 根据浏览器大小推断缩放比例
const getScale = (width = 1920, height = 1080) => {
let ww = window.innerWidth / width;
let wh = window.innerHeight / height;
return ww < wh ? ww : wh;
};
// 浏览器监听 resize 事件
const resize = () => {
if (dataScreenRef.value) {
dataScreenRef.value.style.transform = `scale(${getScale()}) translate(-50%, -50%)`;
}
// 使用了 scale 的echarts其实不需要需要重新计算缩放比例
Object.values(dataScreen).forEach(chart => {
chart && chart.resize();
});
};
// 销毁时触发
onBeforeUnmount(() => {
window.removeEventListener("resize", resize);\
// 每次离开页面时,清空echarts实例,不然会出现无法显示的问题
Object.values(dataScreen).forEach(val => {
val?.dispose();
});
});
通过这样几行代码就完成了一个数据化大屏的适配,比其他方案简单了很多倍,也不需要引用别的外部js文件,
这个方案可以具体参考这个开源项目:github.com/HalseySpicy…
这个是我从学习echarts到方案落地的这段时间,目前看过最满意的方案,暂时项目还没有出现什么问题。有兴趣的同学可以去看看源码!
总结:
如果大家有更好的方案,可以评论一起交流交流!!!
感谢大家!!!