<!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>