大屏可视化:完美自适应的解决方案
你好,我是沐爸,欢迎点赞、收藏、评论和关注。
昨天我们聊到阿里 DataV 大屏的五种自适应方案,每一种多少都有些瑕疵,如果没有看过昨天的博客,回头可以了解下:链接
可视化大屏如何完美适配屏幕?有一种方案,或者说一种思想,那就是随着屏幕的缩放,页面元素(字体、宽高、间距等)也随之缩放。虽然称为完美方案,但也不能自适应特别极端的情况,如屏幕过宽或过长,我们尽量考虑正常尺寸。
今天的分享很简单,只提供代码和预览效果,需要你亲自去运行,去尝试。
一、效果预览
二、代码实现
使用的最容易上手的 Vue2 框架,安装了两个依赖 `echarts`和 `lodash`npm install echarts lodash
src/App.vue
<template><div id="app"><div class="bg"><div class="preview-main"><ChartComponent v-for="item in list" :key="item.id" :item="item" :scaleWidth="scaleWidth":scaleHeight="scaleHeight"></ChartComponent></div></div></div>
</template><script>
import ChartComponent from './components/ChartComponent.vue'
import { list } from './data'export default {components: {ChartComponent},data() {return {list,scaleWidth: window.innerWidth / 1600,scaleHeight: window.innerHeight / 900}},mounted() {window.addEventListener('resize', () => {this.scaleWidth = window.innerWidth / 1600this.scaleHeight = window.innerHeight / 900})}
}
</script><style scoped>
.bg {width: 100%;height: 100vh !important;min-width: 200px;min-height: 300px;background-color: rgb(242, 242, 242);padding: 5px;box-sizing: border-box;
}.preview-main {width: 100%;height: 100%;position: relative;
}
</style><style>
html {height: 100%;
}* {margin: 0;padding: 0;box-sizing: border-box;overflow: hidden;
}
</style>
src/data.js
export const list = [{id: 'c0',style: {width: 1590,height: 100,left: 0,top: 0},type: 'html',template: `<div style="font-size:28px;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;">2021年全国GDP数据</div>`},{id: 'c1',style: {width: 397,height: 395,left: 0,top: 100},type: 'chart',option: {title: {text: '今年上半年GDP产业分布(亿元)',left: 10,top: 10,textStyle: {color: '#303133',fontWeight: 'normal',fontSize: 18}},color: ['#EF8B07', '#FAE37C', '#F5C021',],tooltip: {trigger: 'item'},legend: {bottom: '10',left: 'center',icon: 'rect',itemWidth: 10,itemHeight: 10},series: [{name: 'GDP',type: 'pie',radius: ['41%', '55%'],avoidLabelOverlap: false,label: {show: true,position: 'inside',formatter: '{c}'},data: [{ value: 28402, name: '第一产业' },{ value: 207154, name: '第二产业' },{ value: 296611, name: '第三产业' }]}]},},{id: 'c2',style: {width: 397,height: 395,left: 0,top: 495},type: 'chart',option: {color: ['#F8B65E'],title: {text: 'GDP前十强城市',left: 10,top: 10,textStyle: {color: '#303133',fontWeight: 'normal',fontSize: 18}},grid: {left: '15',right: '15',containLabel: true},legend: {bottom: '10',left: 'center',icon: 'rect',itemWidth: 10,itemHeight: 10},xAxis: {type: 'value',splitLine: {show: false},axisLabel: {show: false}},yAxis: {type: 'category',axisLine: {show: false},axisTick: {show: false},data: ['南京市', '武汉市', '杭州市', '成都市', '苏州市', '重庆市', '广州市', '深圳市', '北京市', '上海市'].reverse()},series: [{name: 'GDP(亿元)',type: 'bar',label: {show: true,position: 'right',formatter: '{c}'},data: [7622.77, 8251.5, 8656.03, 9602.72, 10684.66, 12903, 13101.89, 14324.47, 19228, 20102].reverse()}]}},{id: 'c3',style: {width: 663,height: 698,left: 397,top: 190},type: 'map',option: {title: {text: '今年上半年GDP',left: 10,top: 10,textStyle: {color: '#303133',fontWeight: 'normal',fontSize: 18}},visualMap: {min: 0,max: 1000,left: 'right',top: 'bottom',text: ['Hign', 'Low'],calculable: true,color: ['#B77702', '#FCF4E5']},geo: {map: "china",zoom: 1,roam: true,top: "20%",label: {show: true},},series: [{type: "map",geoIndex: 0,data: [{ name: '上海市', value: 20102 },{ name: '云南省', value: 12680.22 },{ name: '内蒙古自治区', value: 8312.84 },{ name: '北京市', value: 19228 },{ name: '吉林省', value: 6075.01 },{ name: '四川省', value: 25232.36 },{ name: '天津市', value: 7309 },{ name: '安徽省', value: 20565.77 },{ name: '山东省', value: 38610.25 },{ name: '山西省', value: 9606.81 },{ name: '广东省', value: 57226.27 },{ name: '广西壮族自治区', value: 5525.09 },{ name: '新疆维吾尔自治区', value: 7329 },{ name: '江苏省', value: 54890.37 },{ name: '江西省', value: 13977.22 },{ name: '河北省', value: 18754.04 },{ name: '河南省', value: 22719.34 },{ name: '浙江省', value: 34514.84 },{ name: '海南省', value: 2884.31 },{ name: '湖北省', value: 22732.1 },{ name: '湖南省', value: 21671.12 },{ name: '甘肃省', value: 4748.17 },{ name: '福建省', value: 22899.31 },{ name: '西藏自治区', value: 926.05 },{ name: '贵州省', value: 9075.48 },{ name: '辽宁省', value: 12641.2 },{ name: '重庆市', value: 12903 },{ name: '陕西省', value: 13454.73 },{ name: '青海省', value: 1557 },{ name: '黑龙江省', value: 5990 }]}]}},{id: 'c4',style: {width: 530,height: 395,left: 1060,top: 100},type: 'chart',option: {color: ['#F6EA51'],title: {text: '全国百强县分布',left: 'center',top: 10,textStyle: {color: '#303133',fontWeight: 'normal',fontSize: 18}},grid: {left: '15',right: '15',containLabel: true},legend: {bottom: '10',left: 'center',icon: 'rect',itemWidth: 10,itemHeight: 10,data: ['GDP(亿元)']},xAxis: {data: ['内蒙古自治区', '安徽省', '山东省', '广东省', '新疆维吾尔自治区', '江苏省', '江西省', '河北省', '河南省', '浙江省', '湖北省', '湖南省', '福建省', '贵州省', '辽宁省', '陕西省']},yAxis: {name: '百强县个数'},tooltip: {type: 'category',formatter: function (value) {// GDP数据let gdpArr = [8312.84, 20565.77, 38610.25, 57226.27, 7329, 54890.37, 13977.22, 18754.04, 22719.34, 34514.84, 22732.1, 21671.12, 22899.31, 9075.48, 12641.2, 13454.73];return `${value.name},${value.value},${gdpArr[value.dataIndex]}`}},series: [{type: 'scatter',name: 'GDP(亿元)',data: [2, 2, 13, 4, 1, 26, 1, 2, 7, 18, 7, 5, 9, 1, 1, 1], // 百强县数量symbolSize: function (value, item) {// GDP数据let gdpArr = [8312.84, 20565.77, 38610.25, 57226.27, 7329, 54890.37, 13977.22, 18754.04, 22719.34, 34514.84, 22732.1, 21671.12, 22899.31, 9075.48, 12641.2, 13454.73];return gdpArr[item.dataIndex] / 1000}}]}},{id: 'c5',style: {width: 530,height: 395,left: 1060,top: 495},type: 'chart',option: {color: ['#EF8B07'],title: {text: '历年GDP数据',left: 10,top: 10,textStyle: {color: '#303133',fontWeight: 'normal',fontSize: 18}},grid: {left: '15',right: '15',containLabel: true},legend: {bottom: '10',left: 'center',icon: 'rect',itemWidth: 10,itemHeight: 10},xAxis: {type: 'category',boundaryGap: false,data: ['2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018', '2019', '2020']},yAxis: {type: 'value',splitLine: {show: false}},tooltip: {type: 'value'},series: [{name: 'GDP(亿元)',data: [48.94, 55.3, 62.04, 67.94, 71.7, 72.8, 79.8, 90.04, 92.57, 95.42],type: 'line',label: {show: true,position: 'top',formatter: '{c}'}}]}},{id: 'c6',style: {width: 220,height: 90,left: 397,top: 100},type: 'html',template: `<div style="width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: space-around;"><div style="font-size:22px;text-align:center;color:#FF0000;">532,167</div><div style="font-size:16px;text-align:center;color:#807F7F;">今年上半年GDP(亿元)</div></div>`},{id: 'c7',style: {width: 220,height: 90,left: 618,top: 100},type: 'html',template: `<div style="width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: space-around;"><div style="font-size:22px;text-align:center;color:#FF9900;">463,324.76</div><div style="font-size:16px;text-align:center;color:#807F7F;">去年上半年GDP(亿元)</div></div>`},{id: 'c8',style: {width: 220,height: 90,left: 839,top: 100},type: 'html',template: `<div style="width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: space-around;"><div style="font-size:22px;text-align:center;color:#00CC00;">12.7</div><div style="font-size:16px;text-align:center;color:#807F7F;">同比增长率(%)</div></div>`}
]
components/ChartComponent.vue
<template><div class="box" :style="styleObj"><div class="chart" :id="item.id"></div></div>
</template><script>
import * as echarts from 'echarts'
const geoJSON = require('./geojson.json')
import _ from 'lodash'export default {props: {item: Object,scaleWidth: Number,scaleHeight: Number},data() {return {itemData: this.item,myChart: null}},computed: {styleObj() {return {width: this.itemData.style.width * this.scaleWidth + 'px',height: this.itemData.style.height * this.scaleHeight + 'px',top: this.itemData.style.top * this.scaleHeight + 'px',left: this.itemData.style.left * this.scaleWidth + 'px'}}},watch: {scaleWidth() {this.renderAgain()},scaleHeight() {this.renderAgain()},},mounted() {this.renderChart()},methods: {renderAgain() {if (this.myChart) {this.myChart.resize()this.myChart.dispose()}this.renderChart()},changeChildNodeStyle(childNodes) {for (let i = 0; i < childNodes.length; i++) {let child = childNodes[i]if (child.childNodes.length > 0) {this.changeChildNodeStyle(child.childNodes)}if (child.nodeType === 1) {if (child.oldFontSize !== undefined) {child.style.fontSize = child.oldFontSize * this.scaleWidth + 'px'} else {if (!child.style.fontSize) {return}let fontSize = child.style.fontSize.replace('px', '')fontSize = Number(fontSize)child.oldFontSize = fontSizefontSize *= this.scaleWidthchild.style.fontSize = fontSize + 'px'}}}},renderChart() {let scale = this.scaleWidth // Math.min(this.scaleWidth, this.scaleHeight)if (this.itemData.type === 'html') {let el = document.getElementById(this.itemData.id)let childNodes = el.childNodesif (childNodes.length === 0) {el.innerHTML = this.itemData.template}childNodes = el.childNodesthis.changeChildNodeStyle(childNodes)} else if (this.itemData.type === 'chart') {this.myChart = echarts.init(document.getElementById(this.itemData.id))let option = _.cloneDeep(this.itemData.option)option.title.textStyle.fontSize *= scalethis.myChart.setOption(option)} else if (this.itemData.type === 'map') {// 中国地图数据来源:https://geo.datav.aliyun.com/areas_v3/bound/geojson?code=100000_fullecharts.registerMap('china', { geoJSON })this.myChart = echarts.init(document.getElementById(this.itemData.id))let arr = this.itemData.option.series[0].datalet values = arr.map(item => item.value)let min = Math.min(...values)let max = Math.max(...values)let option = _.cloneDeep(this.itemData.option)option.title.textStyle.fontSize *= scaleoption.visualMap.min = minoption.visualMap.max = maxthis.myChart.setOption(option)}}}
}
</script><style scoped>
.box {position: absolute;padding: 5px;box-sizing: border-box;
}.chart {width: 100%;height: 100%;background-color: rgb(255, 255, 255);border-radius: 4px;
}
</style>
components/geojson.json
中国地图经纬度相关数据,数据量太大,请自行下载:https://geo.datav.aliyun.com/areas_v3/bound/geojson?code=100000_full
说明
代码纯手写,时间紧,功能封装和容错等并没有很好地处理,有不足之处还请不要见怪,主要是理解自适应的理念。
三、DataEase
本文案例来自DataEase,今天介绍的理念也是受它启发,在此表示感谢!DataEase提供了开源的大屏可视化解决方案,包括前后台,开源项目地址:https://github.com/dataease/dataease,感兴趣的同学可以去看下源码。
本文案例的相关链接在这里:https://dataease.fit2cloud.com/#/preview/e29030f5-212f-462f-b43a-3ccdd983446b,看看一样吗?
四、总结
今天的分享的主题是完美适配的解决方案,其实有些名不符实,最多是一种理念或思想,但可操作性很强。
关于大屏可视化的六种自适应方式,已经分享完了,你学会了吗?欢迎评论区告诉我
好了,分享结束,谢谢点赞,下期再见。