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

如何在D3.js环形图(Doughnut Chart)扇区上显示图标?

解决D3.js环形图(Doughnut Chart)中图片不显示的问题 + 新手优化建议

首先咱们先搞定图片不显示的核心问题,再一步步优化你的代码:

问题诊断

你的SVG里已经加载了image元素但看不到,主要是两个关键原因:

  • 错误的transform定位:你已经把整个图表的g容器平移到了(width/2, height/2),但给图片做transform时额外加了width/2和height,直接把图片推到了可视区域外。
  • 错误的元素嵌套:你把<image>直接追加到了<path>元素里,但SVG的路径元素(path)不能包含图形元素(image),这些元素需要同级放在g容器中。

修复后的完整代码

<!DOCTYPE html>
<html>
<head>
  <title>D3 Doughnut Chart with Icons</title>
  <script src="https://unpkg.com/vue"></script>
  <script src="https://d3js.org/d3.v6.js"></script>
  <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
  <div class="p-3 flex flex-col" id="one">
    <div class="w-full flex-1">
      <div id="my_dataviz"></div>
    </div>
  </div>

  <script>
    new Vue({
      el: '#one',
      data() {
        return {
          chartData: [
            { key: "One", value: 20, icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png" },
            { key: "Two", value: 30, icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png" },
            { key: "Three", value: 10, icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png" },
            { key: "Four", value: 15, icon: "http://files.gamebanana.com/img/ico/sprays/4f68c8d10306a.png" }
          ]
        }
      },
      mounted() {
        this.initDoughnutChart();
      },
      methods: {
        initDoughnutChart() {
          // 统一管理配置参数,方便后续修改
          const config = {
            width: 450,
            height: 450,
            margin: 1,
            imageSize: 32,
            outerRadius: 100,
            innerRadius: 50,
            hoverOuterRadius: 110
          };

          // 创建SVG容器并居中
          const svg = d3.select('#my_dataviz')
            .append('svg')
            .attr('width', config.width)
            .attr('height', config.height)
            .append('g')
            .attr('transform', `translate(${config.width/2}, ${config.height/2})`);

          // 颜色比例尺:给不同扇区分配不同颜色,提升可读性
          const colorScale = d3.scaleOrdinal()
            .domain(this.chartData.map(d => d.key))
            .range(["#206BF3", "#10B981", "#F59E0B", "#EF4444"]);

          // 饼图生成器:直接使用对象的value属性,无需转换数组
          const pieGenerator = d3.pie()
            .value(d => d.value)
            .sort(null); // 禁用排序,保留数据原有顺序

          const pieData = pieGenerator(this.chartData);

          // 弧生成器
          const arcGenerator = d3.arc()
            .outerRadius(config.outerRadius)
            .innerRadius(config.innerRadius);

          // 创建扇区路径
          const paths = svg.selectAll('.doughnut-segment')
            .data(pieData)
            .enter()
            .append('path')
            .attr('class', 'doughnut-segment')
            .attr('d', arcGenerator)
            .attr('fill', d => colorScale(d.data.key))
            .attr('stroke', '#2D3546')
            .style('stroke-width', '2px')
            .style('opacity', 0.7);

          // 创建扇区内的图标:直接追加到g容器,与path同级
          svg.selectAll('.segment-icon')
            .data(pieData)
            .enter()
            .append('image')
            .attr('class', 'segment-icon')
            .attr('xlink:href', d => d.data.icon)
            .attr('width', config.imageSize)
            .attr('height', config.imageSize)
            .attr('transform', d => {
              const [x, y] = arcGenerator.centroid(d);
              // 让图片中心对齐扇区中心
              return `translate(${x - config.imageSize/2}, ${y - config.imageSize/2})`;
            });

          // 鼠标交互事件:用箭头函数保证this指向Vue组件实例
          paths.on('mouseover', (e) => {
            this.pathAnim(config, d3.select(e.currentTarget), 1);
          });

          paths.on('mouseout', (e) => {
            const path = d3.select(e.currentTarget);
            if (!path.classed('clicked')) {
              this.pathAnim(config, path, 0);
            }
          });
        },
        pathAnim(config, path, dir) {
          const arcGenerator = d3.arc();
          switch(dir) {
            case 0:
              path.transition()
                .duration(500)
                .ease(d3.easeBounce)
                .attr('d', arcGenerator.innerRadius(config.innerRadius).outerRadius(config.outerRadius))
                .style('fill', colorScale(path.datum().data.key));
              break;
            case 1:
              path.transition()
                .duration(300)
                .attr('d', arcGenerator.innerRadius(config.innerRadius).outerRadius(config.hoverOuterRadius))
                .style('fill', 'white');
              break;
          }
        }
      }
    });
  </script>
</body>
</html>

给D3.js新手的优化建议

  • 参数统一管理:把宽度、半径、图片大小等配置项放到一个对象里,后期修改和维护更高效。
  • 避免不必要的数据转换:原代码把对象转成数组传给pie生成器,其实直接用d.value就能访问,减少数据转换的复杂度。
  • 规范元素类名:给SVG元素加上有意义的类名(比如.doughnut-segment),方便调试和后续样式控制。
  • 遵循Vue组件规范:Vue的data选项应该是返回对象的函数(防止组件复用时数据污染),把初始化逻辑抽成单独方法,代码结构更清晰。
  • 区分扇区颜色:原代码所有扇区都是同一种蓝色,给不同扇区分配不同颜色,图表可读性更强。
  • 注意事件回调的this指向:用箭头函数处理鼠标事件,保证回调里的this指向Vue组件实例,方便调用methods里的方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:39:11