You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue+D3开发环形图:如何避免遍历未定义/空数据集报错

问题:Vue中使用D3实现环形图时出现“尝试遍历空数据集”错误

我正在用D3.js开发环形图Vue组件,把D3代码封装成独立JS函数导入到组件后,运行时出现“尝试遍历空数据集”错误。后续计划用SQL查询获取数据,相关代码如下:

我的JS文件

// index.js
import * as d3 from "d3";

function PieChart(data, {
    name = ([x]) => x,  // 从数据中获取分类标签
    value = ([, y]) => y, // 从数据中获取数值
    title, // 鼠标悬浮时的提示文本
    width = 500, // 图表宽度(像素)
    height = 500, // 图表高度(像素)
    innerRadius = 0.25*Math.min(height,width), // 环形图内半径
    outerRadius = Math.min(width, height) / 2, // 环形图外半径
    labelRadius = (innerRadius * 0.2 + outerRadius * 0.8), // 标签位置半径
    format = ",", // 数值格式化规则
    names, // 分类名称集合
    colors, // 分类颜色集合
    stroke = innerRadius > 0 ? "none" : "white", // 扇区间隔描边
    strokeWidth = 1, // 描边宽度
    strokeLinejoin = "round", // 描边连接方式
    padAngle = stroke === "none" ? 1 / outerRadius : 0, // 扇区间角度间隔
  } = {}) {
    // 处理数据
    const N = d3.map(data, name);
    const V = d3.map(data, value);
    const I = d3.range(N.length).filter(i => !isNaN(V[i]));
  
    // 去重分类名称
    if (names === undefined) names = N;
    names = new d3.InternSet(names);
  
    // 自动选择配色方案
    if (colors === undefined) colors = d3.schemeSpectral[names.size];
    if (colors === undefined) colors = d3.quantize(t => d3.interpolateSpectral(t * 0.8 + 0.1), names.size);
  
    // 创建颜色映射比例尺
    const color = d3.scaleOrdinal(names, colors);
  
    // 生成提示文本
    if (title === undefined) {
      const formatValue = d3.format(format);
      title = i => `${N[i]}\n${formatValue(V[i])}`;
    } else {
      const O = d3.map(data, d => d);
      const T = title;
      title = i => T(O[i], i, data);
    }
  
    // 生成饼图数据与弧生成器
    const arcs = d3.pie().padAngle(padAngle).sort(null).value(i => V[i])(I);
    const arc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius);
    const arcLabel = d3.arc().innerRadius(labelRadius).outerRadius(labelRadius);
    
    // 创建SVG容器
    const svg = d3.create("svg")
        .attr("width", width)
        .attr("height", height)
        .attr("viewBox", [-width / 2, -height / 2, width, height])
        .attr("style", "max-width: 100%; height: auto; height: intrinsic;");
  
    // 绘制扇区
    svg.append("g")
        .attr("stroke", stroke)
        .attr("stroke-width", strokeWidth)
        .attr("stroke-linejoin", strokeLinejoin)
      .selectAll("path")
      .data(arcs)
      .join("path")
        .attr("fill", d => color(N[d.data]))
        .attr("d", arc)
      .append("title")
        .text(d => title(d.data));
  
    // 绘制标签
    svg.append("g")
        .attr("font-family", "sans-serif")
        .attr("font-size", 10)
        .attr("text-anchor", "middle")
      .selectAll("text")
      .data(arcs)
      .join("text")
        .attr("transform", d => `translate(${arcLabel.centroid(d)})`)
      .selectAll("tspan")
      .data(d => {
        const lines = `${title(d.data)}`.split(/\n/);
        return (d.endAngle - d.startAngle) > 0.25 ? lines : lines.slice(0, 1);
      })
      .join("tspan")
        .attr("x", 0)
        .attr("y", (_, i) => `${i * 1.1}em`)
        .attr("font-weight", (_, i) => i ? null : "bold")
        .text(d => d);
  
    return Object.assign(svg.node(), {scales: {color}});
  }

export default PieChart;

我的DoughnutChart组件

<template>
  <div id="chart">
    {{ PieChart()  }}
  </div>
</template>

<script>
const data = [{
    name : "toto",
    value: 10
  },
  {
    name : "tato",
    value: 10
  },
  {
    name : "tota",
    value: 10
  }];
  PieChart( data, {
  name: d => d.name,
  value: d => Math.round(100*d.value/30),
  width: 400,
  height: 400
  })
export default  {
  name: 'DoughnutChart',
  methods : {
    PieChart,
  }
}

import PieChart from './index.js';
</script>

我的视图组件

<template>
    <DoughnutChart></DoughnutChart>
</template>
  
<script>
import DoughnutChart from '@/components/Charts/DoughnutChart/DoughnutChart.vue';
export default {
  name: 'myBudgetView',
  components: {
    DoughnutChart
  },
}
</script>

<style scoped>
#chart{
  background-color: white;
  border-radius: 10px;
  padding: 20px;
  margin: 20px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  width: 75%;
  height: 75%;
}
</style>

我预期实现的环形图如下:
预期环形图


问题原因与修复方案

1. 核心错误点

  • 组件中代码执行顺序错误:先调用PieChart函数,后导入该函数,导致调用时函数未定义
  • 模板中直接渲染{{ PieChart() }}会把DOM对象转成字符串显示,无法正确挂载SVG
  • 未处理空数据场景,导致数据集为空时触发遍历错误

2. 修复后的DoughnutChart组件

<template>
  <div ref="chart"></div>
</template>

<script>
import PieChart from './index.js';

export default {
  name: 'DoughnutChart',
  data() {
    return {
      chartData: []
    };
  },
  mounted() {
    // 模拟从SQL获取数据,后续替换为真实接口请求
    this.chartData = [
      { name: "toto", value: 10 },
      { name: "tato", value: 10 },
      { name: "tota", value: 10 }
    ];
    this.renderChart();
  },
  watch: {
    // 监听数据变化,自动重绘图表
    chartData: {
      deep: true,
      handler() {
        this.renderChart();
      }
    }
  },
  methods: {
    renderChart() {
      // 清空容器,避免重复渲染
      this.$refs.chart.innerHTML = '';
      // 空数据时直接返回
      if (this.chartData.length === 0) return;
      // 生成图表并挂载到容器
      const svg = PieChart(this.chartData, {
        name: d => d.name,
        value: d => Math.round(100 * d.value / 30),
        width: 400,
        height: 400
      });
      this.$refs.chart.appendChild(svg);
    }
  }
}
</script>

3. D3函数的空数据兼容优化

在PieChart函数开头添加空数据判断,避免遍历空数据集:

function PieChart(data, {
  // 原有配置项
} = {}) {
  // 新增空数据处理
  if (!data || data.length === 0) {
    const svg = d3.create("svg")
      .attr("width", width)
      .attr("height", height)
      .attr("viewBox", [-width / 2, -height / 2, width, height])
      .attr("style", "max-width: 100%; height: auto; height: intrinsic;");
    svg.append("text")
      .attr("text-anchor", "middle")
      .attr("dy", "0.35em")
      .text("暂无数据");
    return svg.node();
  }

  // 原有逻辑代码
}

内容的提问来源于stack exchange,提问作者KarljoY973

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 02:17:31