ECharts 气泡图

2,432 阅读1分钟

ECharts 气泡图

在 ECharts 实现气泡图中,由于官网没现成的例子。中间折腾了一段时间,写篇文章记录一下

准备

先完成最简单的数据渲染,效果如下。

示例.jpg

基础代码如下:

<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>

方案一

效果预览

引力.gif

代码如下

<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>

会出现的问题:

我们去掉元素的尺寸可以发现,每个元素其实都是向中心靠拢,组成了一个圆圈。如果元素过多,经过斥力的叠加,元素就会超出画布的边界。另外一种就是如果画布是一个长方形,会发现元素基本排列在中间。

方案二

效果预览

散点.gif

代码如下

<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>

存在的问题:

算法不是很精妙,简单的从前铺到后,太多的话,也会重叠。上下空间上也没有处理