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

如何在D3.js环形径向图中实现VALUE1/VALUE2的条件填充?

解决D3.js v7环形径向图条件填充问题

问题背景

使用D3.js v7绘制基于CSV时序数据的环形径向图,核心需求:

  • 当VALUE1 > VALUE2时,对应环形区域填充蓝色
  • 当VALUE2 > VALUE1时,对应环形区域填充灰色
    最初代码仅显示灰色,拆分数据绘制后出现非环形的矩形状区域,需修正以保持环形结构并实现正确的条件填充。

完整CSV数据示例

DATE_TIME,VALUE1,VALUE2
2024-01-01 00:00,10,8
2024-01-01 01:00,12,15
2024-01-01 02:00,15,11
2024-01-01 03:00,9,10
2024-01-01 04:00,14,13
2024-01-01 05:00,7,9
2024-01-01 06:00,16,12
2024-01-01 07:00,11,14
2024-01-01 08:00,18,15
2024-01-01 09:00,13,16

修正后的完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>D3.js Radial Ring Chart with Conditional Fill</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
    <svg width="600" height="600"></svg>

    <script>
        // 初始化SVG容器
        const svg = d3.select("svg");
        const width = +svg.attr("width");
        const height = +svg.attr("height");
        const radius = Math.min(width, height) / 2 - 40;
        const g = svg.append("g").attr("transform", `translate(${width/2}, ${height/2})`);

        // 时间解析器
        const parseTime = d3.timeParse("%Y-%m-%d %H:%M");

        // 加载并处理CSV数据
        d3.csv("data.csv").then(data => {
            data.forEach(d => {
                d.date = parseTime(d.DATE_TIME);
                d.value1 = +d.VALUE1;
                d.value2 = +d.VALUE2;
            });

            // 定义角度比例尺(映射时间到环形角度)
            const angleScale = d3.scaleTime()
                .domain(d3.extent(data, d => d.date))
                .range([0, 2 * Math.PI]);

            // 定义半径比例尺(映射数值到环形半径)
            const radiusScale = d3.scaleLinear()
                .domain([0, d3.max(data, d => Math.max(d.value1, d.value2))])
                .range([0, radius]);

            // 环形弧生成器:核心修正角度计算逻辑
            const arc = d3.arc()
                .startAngle(d => angleScale(d.date))
                .endAngle((d, i) => {
                    // 每个弧段的结束角度取下一个时间点的角度,最后一个点循环回起始点
                    const nextDate = i === data.length - 1 ? data[0].date : data[i+1].date;
                    return angleScale(nextDate);
                })
                .innerRadius(0)
                .outerRadius(d => radiusScale(Math.max(d.value1, d.value2)));

            // 按条件拆分数据组
            const value1Greater = data.filter(d => d.value1 > d.value2);
            const value2Greater = data.filter(d => d.value2 > d.value1);

            // 绘制VALUE1>VALUE2的蓝色环形区域
            g.selectAll(".arc-blue")
                .data(value1Greater)
                .enter()
                .append("path")
                .attr("class", "arc-blue")
                .attr("d", arc)
                .attr("fill", "#1f77b4")
                .attr("opacity", 0.7);

            // 绘制VALUE2>VALUE1的灰色环形区域
            g.selectAll(".arc-gray")
                .data(value2Greater)
                .enter()
                .append("path")
                .attr("class", "arc-gray")
                .attr("d", arc)
                .attr("fill", "#7f7f7f")
                .attr("opacity", 0.7);

            // 添加时间刻度
            const timeTicks = angleScale.ticks(d3.timeHour.every(1));
            g.selectAll(".time-tick")
                .data(timeTicks)
                .enter()
                .append("g")
                .attr("class", "time-tick")
                .attr("transform", d => `rotate(${(angleScale(d) * 180 / Math.PI) - 90}) translate(${radius},0)`)
                .append("text")
                .attr("text-anchor", "middle")
                .attr("transform", "rotate(90)")
                .text(d => d3.timeFormat("%H:%M")(d));

            // 添加半径刻度环
            const radiusTicks = radiusScale.ticks(5);
            g.selectAll(".radius-tick")
                .data(radiusTicks)
                .enter()
                .append("circle")
                .attr("class", "radius-tick")
                .attr("cx", 0)
                .attr("cy", 0)
                .attr("r", d => radiusScale(d))
                .attr("fill", "none")
                .attr("stroke", "#ccc");
        });
    </script>
</body>
</html>

关键修正说明

  • 环形结构修复:通过让每个弧段的endAngle指向下一个时间点的角度,最后一个点循环回起始点,确保环形闭合,避免出现矩形状区域。
  • 条件分组绘制:将数据按VALUE1和VALUE2的大小关系拆分为两组,分别调用弧生成器绘制,保证不同条件的区域正确填充对应颜色。
  • 刻度适配优化:调整时间刻度的旋转和位置,使其贴合环形图的径向布局,提升可读性。

额外提示

  1. 若CSV文件路径不正确,可改用d3.csvParse()直接嵌入CSV字符串。
  2. 可根据需求调整颜色、透明度、刻度间隔等样式参数。
  3. 若存在VALUE1=VALUE2的场景,可添加额外逻辑处理(如填充白色或跳过绘制)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 10:42:38