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

Vue3 TypeScript组合式SFC中D3.js无法渲染图表求助

Vue3 + D3.js 组件无DOM渲染问题排查与修复

错误原因分析

  • DOM引用未就绪:组件顶层代码直接访问lineElement.value时,组件尚未完成挂载,ref的值为null。强制类型断言as SVGGElement会让D3选择空对象,所有后续DOM操作都不会生效。
  • Y轴方向错误:SVG坐标系默认向下为正,当前yScale的range([10, 950])会将数值大的y值映射到SVG底部之外,即使生成路径也无法在可视区域内显示。

修复方案

  1. 将D3的DOM操作逻辑移入onMounted钩子,确保组件挂载后再获取真实DOM元素。
  2. 反转Y轴的range,将[10, 950]改为[950, 10],适配SVG坐标系让数据正确显示。
  3. 增加stroke-width样式,提升线条和标记的可见性。

修复后的完整代码

<template>
    <div>
        <svg xmlns="http://www.w3.org/2000/svg" ref="svgElement" class="center-plot-graph">
            <g ref="lineElement" class="lineElements">
                <path class="splineElement" />
                <g class="symbolElement" />
            </g>
        </svg>
    </div>
</template>

<script setup lang="ts">
import {
    select,
    scaleLinear,
    symbol,
    symbolCircle,
    line,
    curveLinear,
    ValueFn,
    BaseType,
} from "d3";
import { ref, onMounted } from "vue";

type Vector1d = Array<number>;
type Vector2d = Array<Vector1d>;

const svgElement = ref<SVGSVGElement | null>(null);
const lineElement = ref<SVGGElement | null>(null);

const lineData1: Vector2d = [
    [10.5, 0, -26.5, 1],
    [10.515418023668452, 0, -25.98615240945993, 0.9999999999999999],
    [10.54673612522916, 0, -25.4242010779106, 0.9999999999999998],
    [10.599262000092798, 0, -24.79828284532514, 0.9999999999999998],
    [10.682246533999162, 0, -24.080779736377877, 0.9999999999999999],
    [10.814880527142483, 0, -23.214463145842437, 0.9999999999999998],
    [11.054647461420542, 0, -22.028119146822945, 0.9999999999999999],
    [11.671811400028488, 0, -19.86283988201097, 0.9999999999999998],
    [12.613086386434938, 0, -17.545566003251505, 0.9999999999999999],
    [13.813926262485333, 0, -15.363942341711933, 1],
    [15.263842352648265, 0, -13.35830776532418, 1.0000000000000004],
    [16.93791350677736, 0, -11.540160490114632, 1.0000000000000007],
    [18.800820600423645, 0, -9.900220288676735, 1.0000000000000004],
    [20.817015127495804, 0, -8.428748556487237, 1.0000000000000004],
    [22.9509485819025, 0, -7.116006689022895, 1.0000000000000002],
    [25.16710035838422, 0, -5.9522535772986, 1],
    [27.437849524060642, 0, -4.927039014178517, 0.9999999999999999],
    [29.7542920863866, 0, -4.028232704145715, 0.9999999999999999],
    [32.11023490194244, 0, -3.243461017781614, 0.9999999999999998],
    [34.499484827308535, 0, -2.56035032566763, 0.9999999999999997],
    [36.91584871906518, 0, -1.9665269983851892, 0.9999999999999996],
    [39.35328336132695, 0, -1.4504051633262336, 0.9999999999999997],
    [41.8071311282343, 0, -1.0076791848683684, 0.9999999999999999],
    [44.273483525476955, 0, -0.6379795521514762, 1],
    [46.499497521985354, 0, -0.3675404908536285, 0.9999999999999998],
    [47.70003443876498, 0, -0.24619654497075863, 1],
    [48.571814387462624, 0, -0.16874721429387524, 0.9999999999999999],
    [49.292111379398605, 0, -0.11149361650523448, 1],
    [49.91998747855483, 0, -0.06654412557289584, 1],
    [50.483859148305946, 0, -0.03010369508701564, 1.0000000000000002],
    [51, 0, 0, 1],
];
const lineData2: Vector2d = [
    [10.5, 0, -26.5, 1],
    [10.573820291076342, 0, -22.314239546778368, 0.9999999999999995],
    [13.359019071367536, 0, -13.451591871333579, 1.0000000000000007],
    [24.504724058628779, 0, -5.452588866468975, 1.0000000000000003],
    [37.471579656269877, 0, -1.4571818281579628, 0.9999999999999992],
    [46.497084577993259, 0, -0.24908378543712748, 1.0000000000000003],
    [51, 0, 0, 1],
];

onMounted(() => {
    if (!lineElement.value) return;

    const xScale = scaleLinear()
        .domain([-26, 0])
        .range([10, 920]);
    // 反转Y轴range,适配SVG坐标系
    const yScale = scaleLinear()
        .domain([10, 52])
        .range([950, 10]);

    // 绘制曲线
    select(lineElement.value)
        .select(".splineElement")
        .datum(lineData1)
        .attr(
            "d",
            (d: Vector2d) => {
                return line<Vector1d>()
                    .x((di) => xScale(di[2]))
                    .y((di) => yScale(di[0]))
                    .curve(curveLinear)(d);
            }
        )
        .style("stroke", "black")
        .style("fill", "none")
        .style("stroke-width", 2);

    // 绘制标记点
    const SymbolTransform: ValueFn<SVGPathElement | BaseType, Vector1d, string> = (d) => {
        const x = xScale(d[2]);
        const y = yScale(d[0]);
        return `translate(${x},${y})`;
    };

    select(lineElement.value)
        .select(".symbolElement")
        .selectAll<SVGPathElement, Vector1d>("path")
        .data(lineData2)
        .join("path")
        .attr("d", symbol(symbolCircle, 20))
        .style("stroke", "red")
        .style("fill", "none")
        .style("stroke-width", 2)
        .attr("transform", SymbolTransform);
});
</script>

<style scoped lang="less">
.center-plot-graph {
    position: absolute;
    width: 936px;
    height: 960px;
    border: 1px solid #eee; /* 可选:添加边框方便观察SVG范围 */
}
</style>

额外说明

  • 添加了空值判断if (!lineElement.value) return,避免DOM引用获取失败时出现错误。
  • 简化了D3回调函数的参数,移除未使用的i和a参数,让代码更简洁。
  • 可选添加SVG边框,方便确认SVG的可视范围。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 00:54:49