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

D3.js点击图表弹出模态框时图表渲染为纯文本问题排查

问题根因

一共三个核心问题导致渲染异常:

  • SVG标签名写错:创建模态框图表容器时,代码写的是.append("svg_modal"),这是不存在的自定义HTML标签,不是标准SVG根元素。所有SVG子元素(坐标轴、路径、文本)必须嵌套在标准<svg>标签下才会按SVG命名空间渲染,否则浏览器会把<text>当普通文本节点直接展示内容,<path>这类图形标签完全不生效。
  • DOM渲染时序不匹配:Wavelet()函数在组件首次加载的useEffect里就执行,但模态框默认是关闭状态,#contour-modal这个DOM节点根本还没挂载到页面上,D3选不到目标容器,自然没法往里面插入图表内容。
  • 等值线数据计算用错比例尺:模态框图表的密度等值线计算时,调用的是小图的x/y比例尺,尺寸参数也用的小图的width/height,就算前两个问题修复,模态框里的图也会出现错位、变形。
修复方案

按以下步骤改代码即可:

  1. 修正模态框SVG的标签名,把.append("svg_modal")改成.append("svg")。
  2. 拆分图表渲染逻辑,不要在首次加载时就渲染模态框图表:页面内嵌的小图还是在首次加载的useEffect里渲染,模态框的图表改成模态框打开后、DOM节点挂载完成再渲染。
  3. 模态框的密度等值线数据单独计算,使用模态框自己的比例尺和对应尺寸参数。
  4. 给渲染小图的useEffect加上空依赖数组,避免组件每次重渲染都重复执行D3绘图造成DOM冗余。
修正后的核心代码参考

首先修改Wavelet函数,支持传入容器ID和尺寸标识,不用重复写两套逻辑:

function Wavelet(containerId, isModal = false) {
  const baseWidth = isModal ? window.innerWidth/3 : window.innerWidth /5;
  const baseHeight = isModal ? 400 : 200;
  const margin = {top: 20, right: 30, bottom: 30, left: 20};
  const width = baseWidth - margin.left - margin.right;
  const height = baseHeight - margin.top - margin.bottom;

  // 清空目标容器
  const container = d3.select(`#${containerId}`);
  container.selectAll("*").remove();

  // 必须append标准svg标签,不能使用自定义标签名
  const svg = container.append("svg")
    .attr("width", baseWidth + margin.left + margin.right)
    .attr("height", baseHeight + margin.top + margin.bottom)
    .append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`);

  const color = d3.scaleLinear()
    .domain([0, 0.01])
    .range(["#c3d7e8", "#23527a"]);

  // 原有数据集保持不变
  const data = [
    { x: 3.4, y: 4.2 },{ x: -1, y: 4.2 },{ x: -1, y: 2.8 },{ x: 3.6, y: 4.3 },
    { x: -0.1, y: 3.7 },{ x: 4.7, y: 2.5 },{ x: 0.8, y: 3.6 },{ x: 4.7, y: 3.7 },
    { x: -0.4, y: 4.2 },{ x: 0.1, y: 2.2 },{ x: 0.5, y: 3 },{ x: 4.3, y: 4.5 },
    { x: 3.4, y: 2.7 },{ x: 4.4, y: 3.6 },{ x: 3.3, y: 0.6 },{ x: 3, y: 3.4 },
    { x: 4.7, y: 0 },{ x: -0.7, y: 2.7 },{ x: 2.6, y: 2 },{ x: 0, y: -1 },
    { x: 3.4, y: 4.5 },{ x: 3.9, y: 4.6 },{ x: 0.7, y: 3.9 },{ x: 3, y: 0.2 }
  ];

  // 适配当前容器尺寸的坐标轴比例尺
  const xScale = d3.scaleLinear().domain([-2,6]).range([0, width]);
  const yScale = d3.scaleLinear().domain([-2,5]).range([height, 0]);

  // 绘制坐标轴
  svg.append("g")
    .attr("transform", `translate(0,${height})`)
    .call(d3.axisBottom(xScale));
  svg.append("g").call(d3.axisLeft(yScale));

  // 密度数据必须使用当前容器的比例尺和尺寸计算
  const densityData = d3.contourDensity()
    .x(d => xScale(d.x))
    .y(d => yScale(d.y))
    .size([width, height])
    .bandwidth(20)
    (data);

  // 绘制等值线
  svg.selectAll("path")
    .data(densityData)
    .enter()
    .append("path")
    .attr("d", d3.geoPath())
    .attr("fill", d => color(d.value))
    .attr("stroke", "#4285f4");
}

export default Wavelet

然后修改React侧的渲染逻辑:

// 首次加载只渲染页面内嵌的小图,加空依赖数组只执行一次
useEffect(() => {
  Wavelet("contour", false);
}, []);

// 监听模态框状态,打开且DOM挂载完成后再渲染模态框内的大图
useEffect(() => {
  if (isOpen && modalID === 4) {
    // setTimeout等待模态框DOM插入完成后再执行D3渲染,避免选不到容器
    setTimeout(() => {
      Wavelet("contour-modal", true);
    }, 0)
  }
}, [isOpen, modalID]);

function showModal(id) {
  setIsopen(prev => !prev); 
  setModalID(id);
}

function Graph() {
  return <div>
    <div id="contour" onClick={() => showModal(4)}></div>
    {isOpen && (modalID == 4) && (
      <ModalContent onClose={() => setIsopen(false)}>
        <div className="modal_c">
          <h3>{modalID}</h3>
          <div id="contour-modal"></div>
          <p> </p>
        </div>
      </ModalContent>
    )}
  </div>
}

模态框组件的原有逻辑不需要修改,保持原状即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 19:15:29