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

带交替标签的简易D3.js折线图优化:铺满SVG、锚定标签及添加标记

D3.js折线图优化实现

1. 折线铺满SVG画布

原代码使用固定偏移计算坐标,无法自适应SVG尺寸。改用D3的scaleLinear比例尺结合边距设置,让折线自动填充SVG的有效绘图区域:

  • 定义边距避免内容贴边
  • 计算绘图区域的实际宽高(SVG尺寸减去边距)
  • 为x轴创建基于数据索引的线性比例尺,y轴创建基于数据范围的线性比例尺
  • 更新折线生成函数,通过比例尺计算坐标

2. 百分比标签锚定折线路径

不再硬编码标签位置,通过折线路径的getPointAtLength方法获取线段中点坐标,让标签精准锚定在对应线段上:

  • 绘制折线后获取路径总长度,计算单段线段长度
  • 遍历数据获取每段线段的中点坐标
  • 根据涨跌方向调整标签的垂直偏移,保证标签不遮挡折线

3. 可选标记点功能

添加可选参数控制是否显示数据点标记,默认关闭,开启时在每个数据点位置绘制圆形标记:

  • 函数参数中加入showMarkers = false作为开关
  • 若参数为true,遍历数据创建圆形元素,通过比例尺定位

优化后完整代码

<html>
<head>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script>
        function createLineChart(data, showMarkers = false) {
            // SVG尺寸与边距设置
            const svgWidth = 800;
            const svgHeight = 600;
            const margin = { top: 20, right: 20, bottom: 20, left: 20 };
            const plotWidth = svgWidth - margin.left - margin.right;
            const plotHeight = svgHeight - margin.top - margin.bottom;

            // 创建SVG容器并偏移边距
            const svg = d3.select("body").append("svg")
                .attr("width", svgWidth)
                .attr("height", svgHeight)
                .append("g")
                .attr("transform", `translate(${margin.left}, ${margin.top})`);

            // 创建比例尺
            const xScale = d3.scaleLinear()
                .domain([0, data.length - 1])
                .range([0, plotWidth]);
            
            const yScale = d3.scaleLinear()
                .domain([0, d3.max(data)])
                .range([plotHeight, 0]);

            // 创建生成折线的函数
            const line = d3.line()
                .x((d, i) => xScale(i))
                .y(d => yScale(d));

            // 绘制折线
            const linePath = svg.append("path")
                .datum(data)
                .attr("d", line)
                .attr("stroke", "black")
                .attr("stroke-width", 2)
                .attr("fill", "none");

            // 绘制锚定到折线的百分比标签
            const pathLength = linePath.node().getTotalLength();
            const segmentLength = pathLength / (data.length - 1);

            data.forEach((d, i) => {
                if (i > 0) {
                    const percentChange = ((d - data[i - 1]) / data[i - 1]) * 100;
                    const color = percentChange >= 0 ? "green" : "red";
                    
                    // 获取线段中点坐标
                    const midPoint = linePath.node().getPointAtLength((i - 0.5) * segmentLength);
                    // 根据涨跌调整标签偏移
                    const yOffset = percentChange >= 0 ? -15 : 15;

                    svg.append("text")
                        .text(`${percentChange.toFixed(1)}%`)
                        .attr("x", midPoint.x)
                        .attr("y", midPoint.y + yOffset)
                        .attr("text-anchor", "middle")
                        .attr("fill", color)
                        .attr("font-size", "12px");
                }
            });

            // 可选:绘制数据标记点
            if (showMarkers) {
                svg.selectAll(".marker")
                    .data(data)
                    .enter()
                    .append("circle")
                    .attr("class", "marker")
                    .attr("cx", (d, i) => xScale(i))
                    .attr("cy", d => yScale(d))
                    .attr("r", 4)
                    .attr("fill", "steelblue")
                    .attr("stroke", "white")
                    .attr("stroke-width", 1.5);
            }
        }
    </script>
</head>
<body>
    <script>
        const data = [10, 20, 15, 40, 50, 60];
        // 调用时传入true开启标记点,不传则默认关闭
        createLineChart(data, true);
    </script>
</body>
</html>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 09:45:45