ECharts 气泡图
在 ECharts 实现气泡图中,由于官网没现成的例子。中间折腾了一段时间,写篇文章记录一下
准备
先完成最简单的数据渲染,效果如下。
基础代码如下:
<template>
<div class="main"></div>
</template>
<script setup>
import { onMounted } from "vue";
import * as echarts from "echarts";
onMounted(() => {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.querySelector(".main"));
// 绘制图表
myChart.setOption({
title: {
text: "ECharts 入门示例",
},
tooltip: {},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
},
yAxis: {},
series: [
{
name: "销量",
type: "bar",
data: [5, 20, 36, 10, 10, 20],
},
],
});
});
</script>
<style scoped>
.main {
width: 400px;
height: 400px;
margin: 100px auto;
border: 1px solid;
}
</style>
方案一
效果预览
代码如下
<script setup>
import { onMounted } from "vue";
import * as echarts from "echarts";
onMounted(() => {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.querySelector(".main"));
let demoDataList = [
{
label: "苹果",
value: 10,
},
{
label: "橘子",
value: 1,
},
{
label: "西瓜",
value: 50,
},
{
label: "香蕉",
value: 100,
},
];
// 绘制图表
myChart.setOption(getOption(demoDataList));
});
function getOption(dataList) {
// 先找出数组的最大值,这样可以知道每个元素与最大元素的比例,方便调整尺寸的大小
let maxValue = 1; // 防止数据全都为 0,后面会除以 maxValue,为 0 会出错
let valueList = dataList.map((item) => item.value);
maxValue = Math.max(maxValue, ...valueList);
// 设定尺寸的大小
let minSymbolSize = 60; // 最小尺寸 防止太小的情况,看情况调整
let symbolSize = 70; // 大小
let repulsion = symbolSize * 2; // 斥力 为了防止重叠,斥力最好大于 symbolSize
// 可以根据数据的多少,动态调整 symbolSize 的大小
let valueListLen = valueList.length;
if (valueListLen < 3) {
symbolSize = 120;
} else if (valueListLen < 5) {
symbolSize = 100;
}
repulsion = symbolSize * 2;
// 获取要渲染的数据
let data = dataList.map((item) => {
// 根据比例与最小尺寸,算出每个元素的大小
let size = Math.max(symbolSize * (item.value / maxValue), minSymbolSize);
return {
name: item.label,
value: item.value,
symbolSize: size,
};
});
return {
xAxis: {
show: false,
},
yAxis: {
show: false,
},
series: [
{
data,
type: "graph", // 关系图
layout: "force", // 采用力引导布局
force: {
// 力引导布局相关的配置项
repulsion, // 值越大则斥力越大 每个元素间隔越大
},
// 高亮状态的图形样式
emphasis: {
scale: 2,
},
// 设置 label
label: {
show: true,
position: "inside",
formatter: [`{title|{b}}`, `{num|{c}}`].join("\n"),
rich: {
title: {
align: "center",
fontSize: 13,
lineHeight: 18,
color: "#FFF",
},
num: {
align: "center",
fontSize: "15",
lineHeight: 21,
fontWeight: 500,
color: "#FFF",
},
},
},
// 设置元素的样式
itemStyle: {
borderWidth: 1,
color: "pink",
},
},
],
};
}
</script>
会出现的问题:
我们去掉元素的尺寸可以发现,每个元素其实都是向中心靠拢,组成了一个圆圈。如果元素过多,经过斥力的叠加,元素就会超出画布的边界。另外一种就是如果画布是一个长方形,会发现元素基本排列在中间。
方案二
效果预览
代码如下
<script setup>
import { onMounted } from "vue";
import * as echarts from "echarts";
onMounted(() => {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.querySelector(".main"));
let demoDataList = [
{
label: "苹果",
value: 10,
},
{
label: "橘子",
value: 1,
},
{
label: "西瓜",
value: 50,
},
{
label: "西瓜2",
value: 50,
},
{
label: "香蕉",
value: 100,
},
];
// 绘制图表
myChart.setOption(getOption(demoDataList));
});
// 获取 [ 0-maxValue ] 之间的随机数
function getRandomValues(maxValue) {
return parseInt(Math.random() * (maxValue + 1), 10);
}
function getOption(dataList) {
// 先找出数组的最大值,这样可以知道每个元素与最大元素的比例,方便调整尺寸的大小
let maxValue = 1; // 防止数据全都为 0,后面会除以 maxValue,为 0 会出错
let valueList = dataList.map((item) => item.value);
maxValue = Math.max(maxValue, ...valueList);
let minValue = Math.min(...valueList); // 最小值
// 设定尺寸的大小
let minSymbolSize = 60; // 最小尺寸 防止太小的情况,看情况调整
let symbolSize = 70; // 大小
let gap = symbolSize / 2; // 间隔 为了防止重叠,间隔大于 0 即可
// 可以根据数据的多少,动态调整 symbolSize 的大小
let valueListLen = dataList.length;
if (valueListLen < 3) {
symbolSize = 120;
} else if (valueListLen < 5) {
symbolSize = 100;
}
gap = symbolSize / 2;
// 按最大尺寸算出最大值与平均值
// 思路就是 让元素 y轴数据保持在平均值附近,并且间隔上移或下移。x轴数据一直往后叠加,保证不重叠
let len = dataList.length;
let averageValue = parseInt((maxValue - minValue) / 2);
let upRoundList = ["-20%", "-22%", "-25%", "-28%"]; // 上移动位置
let downRoundList = ["70%", "75%", "80%", "85%", "90%", "100%"]; // 下移位置
let randomIndex = getRandomValues(len - 1);
// 获取要渲染的数据
let data = dataList.map((item, index) => {
// 根据比例与最小尺寸,算出每个元素的大小
let size = Math.max(symbolSize * (item.value / maxValue), minSymbolSize);
// x轴,一直往后叠加即可
let xValue = index * symbolSize + gap;
// 偏移量
let symbolOffset = [];
// 上移下移间隔开即可
if (index % 2) {
symbolOffset = [0, upRoundList[getRandomValues(upRoundList.length - 1)]];
} else {
symbolOffset = [
0,
downRoundList[getRandomValues(downRoundList.length - 1)],
];
}
return {
name: item.label,
// 保证在 y 轴附近浮动,防止数据差距过大
value: [xValue, index === randomIndex ? maxValue : averageValue], // 由于 value 数据被调整了,所以需要另外一个字段存储真值
reallyValue: item.value, // 保留真实的值
symbolOffset,
symbolSize: size,
};
});
return {
xAxis: {
show: false,
},
yAxis: {
show: false,
},
series: [
{
data,
type: "scatter", // 散点(气泡)图
// 高亮状态的图形样式
emphasis: {
scale: 2,
},
// 设置 label
label: {
show: true,
position: "inside",
formatter({ data }) {
return [
`{title|` + `${data.name}}`,
`{num|` + `${data.reallyValue}}`,
].join("\n");
},
rich: {
title: {
align: "center",
fontSize: 13,
lineHeight: 18,
color: "#FFF",
},
num: {
align: "center",
fontSize: "15",
lineHeight: 21,
fontWeight: 500,
color: "#FFF",
},
},
},
// 设置元素的样式
itemStyle: {
borderWidth: 1,
color: "pink",
},
},
],
};
}
</script>
存在的问题:
算法不是很精妙,简单的从前铺到后,太多的话,也会重叠。上下空间上也没有处理