d3_v7 基于 d3.arc()生成径向柱状图

发布于:2025-03-14 ⋅ 阅读:(19) ⋅ 点赞:(0)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>双层径向柱状图</title>
<!--    <script src="d3.js"></script>-->
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        .bar-inner:hover, .bar-outer:hover { opacity: 0.7; }
        .tooltip {
            position: absolute;
            padding: 8px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            border-radius: 4px;
            pointer-events: none;
        }
    </style>
</head>
<body>
<div id="chart"></div>

<script>
    // 1. 定义参数
    const width = 800,
        height = 800,
        innerRadius = 80,      // 最内层圆半径
        middleRadius = 200,    // 内层柱子外半径
        outerRadius = 350;     // 外层柱子外半径

    // 2. 创建SVG容器
    const svg = d3.select("#chart")
        .append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g")
        .attr("transform", `translate(${width/2}, ${height/2})`);

    // 3. 生成双层数据(示例:12个月份,内外层各一组值)
    const data = d3.range(12).map(i => ({
        month: i + 1,
        innerValue: Math.random() * 80 + 20,  // 内层数据
        outerValue: Math.random() * 120 + 30   // 外层数据
    }));

    // 4. 角度比例尺(共用)
    const x = d3.scaleBand()
        .domain(data.map(d => d.month))
        .range([-3*Math.PI/4, 3*Math.PI/4])
        // .padding(0.2);

    // 5. 定义内外层径向比例尺
    const yInner = d3.scaleRadial()
        .domain([0, d3.max(data, d => d.innerValue)])
        .range([innerRadius, middleRadius]);

    const yOuter = d3.scaleRadial()
        .domain([0, d3.max(data, d => d.outerValue)])
        .range([middleRadius + 20, outerRadius]); // 中间留20px空隙

    // 6. 绘制中心圆
    svg.append("circle")
        .attr("r", innerRadius - 5)
        .attr("fill", "#f0f0f0")
        .attr("stroke", "#999");

    // 7. 创建弧形生成器(内层和外层)
    const arcInner = d3.arc()
        .innerRadius(innerRadius)
        .outerRadius(d => yInner(d.innerValue))
        .startAngle(d => x(d.month))
        .endAngle(d => x(d.month) + x.bandwidth())
        .padAngle(0.01);

    const arcOuter = d3.arc()
        .innerRadius(middleRadius + 20) // 与外层比例尺的range起始值对齐
        .outerRadius(d => yOuter(d.outerValue))
        .startAngle(d => x(d.month))
        .endAngle(d => x(d.month) + x.bandwidth())
        .padAngle(0.01);

    // 8. 绘制内层柱子
    svg.selectAll(".bar-inner")
        .data(data)
        .join("path")
        .attr("class", "bar-inner")
        .attr("d", arcInner)
        .attr("fill", (d, i) => d3.interpolateBlues(i/12 * 0.8));
    svg.append('path').attr("d",d3.arc()
        .innerRadius(innerRadius) // 与外层比例尺的range起始值对齐
        .outerRadius(d => yInner(40))
        .startAngle(d => -4*Math.PI/5)
        .endAngle(d => -99*Math.PI/100)).attr("fill", "grey");
    svg.append('path').attr("d",d3.arc()
        .innerRadius(innerRadius) // 与外层比例尺的range起始值对齐
        .outerRadius(d => yInner(30))
        .startAngle(d => 4*Math.PI/5)
        .endAngle(d => 99*Math.PI/100)).attr("fill", "green");
    // 9. 绘制外层柱子
    svg.selectAll(".bar-outer")
        .data(data)
        .join("path")
        .attr("class", "bar-outer")
        .attr("d", arcOuter)
        .attr("fill", (d, i) => d3.interpolateReds(i/12 * 0.8));


    // 11. 工具提示
    const tooltip = d3.select("body").append("div").attr("class", "tooltip");

    function showTooltip(event, d, layer) {
        tooltip
            .style("opacity", 1)
            .html(`${layer} Layer<br>Month ${d.month}<br>Value: ${d[layer === 'Inner' ? 'innerValue' : 'outerValue'].toFixed(1)}`)
            .style("left", (event.pageX + 10) + "px")
            .style("top", (event.pageY - 28) + "px");
    }

    d3.selectAll(".bar-inner")
        .on("mouseover", function(event, d) { showTooltip(event, d, 'Inner'); })
        .on("mouseout", () => tooltip.style("opacity", 0));

    d3.selectAll(".bar-outer")
        .on("mouseover", function(event, d) { showTooltip(event, d, 'Outer'); })
        .on("mouseout", () => tooltip.style("opacity", 0));

</script>
</body>
</html>