前言
效果图
主要问题点
1、柱状图重叠stack
2、数据过大显示处理log(对数坐标)
3、对数(极)坐标0数据处理
4、0数据项显示为灰色柱状条
5、窗口变化自适应
6、侧边导航展开收起对echarts图的影响
-
柱状图重叠stack
// 1.切换x、y轴 yAxis: { type: 'category', boundaryGap: true, show: true, axisLine: { lineStyle: { opacity: 0, }, }, axisTick: { show: false }, // 刻度线提示 data: ['网络风险', '应用风险', '攻击威胁', '数据泄漏', 'APP风险'], }, xAxis: { type: 'value', show: true, axisLine: { show: false, }, }, // 2.数据准备 series: [ { name: '已认证企业', data: ['1', '2', '700', '10000', '80'], type: 'bar', smooth: true, stack: 'total', emphasis: { focus: 'series', }, itemStyle: { color: 'rgba(15, 175, 255, 1)', // 柱状图颜色 borderRadius: [7, 0, 0, 7], // 柱状图圆角 borderWidth: 0, }, barWidth: 15, // 宽 barMinHeight: 10, // 高 }, { name: '三方供应商', data: ['100', '200000', '700', '200', '800000'], type: 'bar', stack: 'total', smooth: true, emphasis: { focus: 'series', }, itemStyle: { color: 'rgba(36, 207, 241, 1)', borderRadius: [0, 7, 7, 0], borderWidth: 0, }, barWidth: 15, // 柱状图的宽度 barMinHeight: 10, // 柱状图的最小高度 }, ],
以上数据展示效果图:发现超大数据影响柱状图大小
-
数据过大显示处理log(对数坐标轴)
xAxis: { ++ type: 'log', -- //type: 'value', show: true, axisLine: { show: false, }, },
数值坐标系改为:log对数坐标系 啥是对数坐标轴:n=log(a)(b);看下图,这样我们就能理解为什么图可以展示全了
现在效果图
发现:鼠标移上去其他数据被淡出
处理:series->emphasis:disabled: true,series: [ { name: '已认证企业', data: ['1', '2', '700', '10000', '80'], // data: data && [data[0]?.risk_network, data[0]?.risk_app, data[0]?.risk_attack, data[0]?.risk_data_leak, data[0]?.risk_mobile_app], type: 'bar', smooth: true, stack: 'total', emphasis: { focus: 'series', ++ disabled: true, }, startValue: 100, lineStyle: { color: '#2878FF', width: 1, }, itemStyle: { color: (params:any) => { if (params.value === 105.05) { // undefined的空数据处理 颜色灰色 return '#ccc' } return 'rgba(15, 175, 255, 1)' }, borderRadius: [7, 0, 0, 7], borderWidth: 0, }, barWidth: 15, barMinHeight: 10, },
-
极坐标0数据处理
根据对数坐标系n=log(a)(b) a>0;那么后台返回数据项如果有0的话,会显示如下
0项数据置为undefined这里的data是获取后台返回的接口数据,然后进行遍历操作 const arr = data.map((item:any) => ({ risk_network: item.risk_network || undefined, risk_app: item.risk_app || undefined, risk_attack: item.risk_attack || undefined, risk_data_leak: item.risk_data_leak || undefined, risk_mobile_app: item.risk_mobile_app || undefined, })) initContainer(arr)
提示判断是否有undefined
// 提示判断是否有undefined tooltip: { padding: 0, backgroundColor: 'rgba(255,255,255,0)', trigger: 'axis', axisPointer: { type: 'shadow', axis: 'y', }, formatter(params:any) { let tipHtml = ''; tipHtml = `${'<div style="box-sizing: border-box;box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.08), 0px 0px 12px rgba(0, 0, 0, 0.08);border-radius: 4px;width:160px;linear-gradient(180deg, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0) 100%);font-size: 9px;padding:1px 8px;">' + '<div style="width:86%;height:30px;line-height:30px;">' + '<span style="color:#181818;font-size:12px;">'}${params[0]?.axisValue}</span>` + '</div>' + '<div style="width: 144px;padding-bottom:4px">' + '<p style="color:#666666;border-radius: 2px;font-size:12px;line-height:30px;background: #FFFFFF;margin-bottom:6px;padding:0 6px">' + `<span style="width: 6px;height: 6px;display:inline-block;vertical-align: middle;border-radius: 6px; background:${params[0]?.color};margin:0 3px;"></span>` + `${params[0]?.seriesName}` // eslint-disable-next-line no-unsafe-optional-chaining + `<span style="color:#000;margin:0 6px;float: right;">${params[0]?.value === undefined ? 0 : params[0]?.value}</span>` + '</p>' + `<p style="color:#666666;border-radius: 2px;line-height:30px;background: #FFFFFF;font-size:12px; display:${params[1] ? 'block' : 'none'}">` + `<span style="width: 6px;height: 6px;display:inline-block;vertical-align: middle;border-radius: 6px; background:${params[1]?.color};margin:0 3px;"></span>` + `${params[1]?.seriesName}` // eslint-disable-next-line no-unsafe-optional-chaining + `<span style="color:#000;margin:0 6px;float: right;">${params[1]?.value === undefined ? 0 : params[1]?.value}</span>` + '</p>' + '</div>' return tipHtml; },
-
0数据项显示为灰色柱状条
如果好几项为空,页面显示就会很空荡
处理:因为数据都是整数,所以当数据返回为0时,我们给他一个小数105.5;在设置颜色的时候, 为其加一个灰色
1、undefined改成105.5
const arr = data.map((item:any) => ({ risk_network: item.risk_network || 105.05, risk_app: item.risk_app || 105.05, risk_attack: item.risk_attack || 105.05, risk_data_leak: item.risk_data_leak || 105.05, risk_mobile_app: item.risk_mobile_app || 105.05, })) initContainer(arr)
2、tooltip->formatter中的undefined改成105.5 ${params[0]?.value === undefined ? 0 : params[0]?.value} 3、series->itemStyle->color使用回调函数形式
series: [ { name: '已认证企业', data: ['0', '0', '0', '10000', '80'], // data: data && [data[0]?.risk_network, data[0]?.risk_app, data[0]?.risk_attack, data[0]?.risk_data_leak, data[0]?.risk_mobile_app], type: 'bar', smooth: true, stack: 'total', emphasis: { focus: 'series', disabled: true, }, startValue: 100, lineStyle: { color: '#2878FF', width: 1, }, itemStyle: { ++ color: (params:any) => { ++ if (params.value === 105.05) { // undefined的空数据处理 颜色灰色 ++ return '#ccc' ++ } ++ return 'rgba(15, 175, 255, 1)' ++ }, borderRadius: [7, 0, 0, 7], borderWidth: 0, }, barWidth: 15, barMinHeight: 10, },
-
窗口变化自适应
只需要在执行函数时进行窗口监听视图大小变化,进而重绘就可以了 resize()方法
import { ECharts } from 'echarts'; const scatterChart = ref<ECharts | null>(null); onMounted(() => { window.onresize = () => { // console.log('视图大小变化') scatterChart.value?.resize(); }; })
-
侧边导航展开收起对echarts图的影响
当侧边栏有展开收起时,echarts图不会自适应,这个时候需要重绘我们的echarts图, 我想的是既然窗口能监听,那么dom元素是否也可以监听
https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver/observe
<div id="riskTypeColumnar" ref="refriskTypeColumnar" /> const refriskTypeColumnar = ref<HTMLElement |null>(null) const resizeObserver = new ResizeObserver((entries:any) => { scatterChart.value?.resize(); }); onMounted(() => { ++ resizeObserver.observe(refriskTypeColumnar.value); window.onresize = () => { // console.log('视图大小变化') scatterChart.value?.resize(); }; }) 注意:需要给它父级dom设置宽度
-
代码
<template> <div style="position: absolute; left: 0px; top: 19.5px; font-weight: 500; font-size: 16px; line-height: 25px; color: #181818;" > 风险分布 </div> <div id="riskTypeColumnar" ref="refriskTypeColumnar" /> </template> <script setup lang="ts"> import { ref, onUnmounted, markRaw, watch, onMounted, } from 'vue'; import { ECharts } from 'echarts'; import { overviewRiskDistribution } from '@/api/user'; const scatterChart = ref<ECharts | null>(null); const refriskTypeColumnar = ref<HTMLElement |null>(null) const riskTotal = ref<any>(0); const assetsTotal = ref<any>(0); const getTableData = async () => { const params:any = { scope: 0, }; const { code, msg, data } = await overviewRiskDistribution(params); if (scatterChart.value) { scatterChart.value?.dispose(); } const arr = data.map((item:any) => ({ risk_network: item.risk_network || 105.05, risk_app: item.risk_app || 105.05, risk_attack: item.risk_attack || 105.05, risk_data_leak: item.risk_data_leak || 105.05, risk_mobile_app: item.risk_mobile_app || 105.05, // risk_network: item.risk_network || undefined, // risk_app: item.risk_app || undefined, // risk_attack: item.risk_attack || undefined, // risk_data_leak: item.risk_data_leak || undefined, // risk_mobile_app: item.risk_mobile_app || undefined, })) initContainer(arr) }; onUnmounted(() => { scatterChart.value?.dispose(); }); const initContainer = async (data:any) => { const echarts = await import('echarts'); scatterChart.value = markRaw(echarts.init((document as any).getElementById('riskTypeColumnar'))); renderMap(data) }; const renderMap = (data:any) => { const option = { legend: { type: 'scroll', pageButtonItemGap: 2, itemWidth: 10, itemHeight: 6, itemGap: 30, icon: 'rect', data: [{ name: '已认证企业', itemStyle: { color: '#0FAFFF' }, icon: 'roundRect', }, { name: '三方供应商', itemStyle: { color: '#24CFF1' }, icon: 'roundRect', }], pageTextStyle: { fontSize: 6, }, bottom: 0, }, itemStyle: { borderWidth: 2, borderColor: '#fff', }, tooltip: { padding: 0, backgroundColor: 'rgba(255,255,255,0)', trigger: 'axis', axisPointer: { type: 'shadow', axis: 'y', }, formatter(params:any) { let tipHtml = ''; tipHtml = `${'<div style="box-sizing: border-box;box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.08), 0px 0px 12px rgba(0, 0, 0, 0.08);border-radius: 4px;width:160px;linear-gradient(180deg, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0) 100%);font-size: 9px;padding:1px 8px;">' + '<div style="width:86%;height:30px;line-height:30px;">' + '<span style="color:#181818;font-size:12px;">'}${params[0]?.axisValue}</span>` + '</div>' + '<div style="width: 144px;padding-bottom:4px">' + '<p style="color:#666666;border-radius: 2px;font-size:12px;line-height:30px;background: #FFFFFF;margin-bottom:6px;padding:0 6px">' + `<span style="width: 6px;height: 6px;display:inline-block;vertical-align: middle;border-radius: 6px; background:${params[0]?.color};margin:0 3px;"></span>` + `${params[0]?.seriesName}` // eslint-disable-next-line no-unsafe-optional-chaining + `<span style="color:#000;margin:0 6px;float: right;">${params[0]?.value === 105.05 ? 0 : params[0]?.value}</span>` + '</p>' + `<p style="color:#666666;border-radius: 2px;line-height:30px;background: #FFFFFF;font-size:12px; display:${params[1] ? 'block' : 'none'}">` + `<span style="width: 6px;height: 6px;display:inline-block;vertical-align: middle;border-radius: 6px; background:${params[1]?.color};margin:0 3px;"></span>` + `${params[1]?.seriesName}` // eslint-disable-next-line no-unsafe-optional-chaining + `<span style="color:#000;margin:0 6px;float: right;">${params[1]?.value === 105.05 ? 0 : params[1]?.value}</span>` + '</p>' + '</div>' return tipHtml; }, }, grid: { left: '3%', right: '4%', top: '0%', bottom: '10%', containLabel: true, }, yAxis: { type: 'category', boundaryGap: true, show: true, axisLine: { lineStyle: { opacity: 0, }, }, axisTick: { show: false }, data: ['网络风险', '应用风险', '攻击威胁', '数据泄漏', 'APP风险'], }, xAxis: { type: 'log', // type: 'value', show: true, axisLine: { show: false, }, }, series: [ { name: '已认证企业', data: data && [data[0]?.risk_network, data[0]?.risk_app, data[0]?.risk_attack, data[0]?.risk_data_leak, data[0]?.risk_mobile_app], type: 'bar', smooth: true, stack: 'total', emphasis: { focus: 'series', disabled: true, }, startValue: 100, lineStyle: { color: '#2878FF', width: 1, }, itemStyle: { color: (params:any) => { if (params.value === 105.05) { // undefined的空数据处理 颜色灰色 return '#ccc' } return 'rgba(15, 175, 255, 1)' }, borderRadius: [7, 0, 0, 7], borderWidth: 0, }, barWidth: 15, barMinHeight: 10, }, { name: '三方供应商', // data: ['0', '0', '0', '200', '800000'], data: data && [data[1]?.risk_network, data[1]?.risk_app, data[1]?.risk_attack, data[1]?.risk_data_leak, data[1]?.risk_mobile_app], type: 'bar', stack: 'total', smooth: true, emphasis: { focus: 'series', disabled: true, }, startValue: 10, // endValue: 10, lineStyle: { color: '#FF5A5A', width: 1, }, itemStyle: { color: (params:any) => { if (params.value === 105.05) { return '#ccc' } return 'rgba(36, 207, 241, 1)' }, borderRadius: [0, 7, 7, 0], borderWidth: 0, }, barWidth: 15, // 柱状图的宽度 barMinHeight: 10, // 柱状图的最小高度 }, ], }; scatterChart.value?.setOption(option as any, true); }; const resizeObserver = new ResizeObserver((entries:any) => { scatterChart.value?.resize(); }); // initContainer() getTableData() defineExpose({ initContainer, }); onMounted(() => { console.log('refriskTypeColumnar.value', refriskTypeColumnar.value) resizeObserver.observe(refriskTypeColumnar.value); window.onresize = () => { // console.log('视图大小变化') scatterChart.value?.resize(); }; }) </script> <style scoped> #riskTypeColumnar { width: 100%; margin-top: 55px; height: calc(100% - 55px); justify-content: center; position: relative; } </style>