如何在D3.js中实现同类别条形堆叠效果
实现攻击战术堆叠条形图方案
核心思路
常规d3.stack()适用于按系列跨类别堆叠的场景,但你的需求是同战术下的多个子条目堆叠,所以需要先对数据做定向预处理:
- 按
attack tactic分组,确保同战术的条目聚在一起 - 为每个战术组内的条目手动计算堆叠的起始位置(y0)和结束位置(y1)
- 保持X轴的指定排序,每个战术占据独立的x轴位置,组内堆叠多个条形
代码修改示例
假设你的原始数据结构如下:
const data = [ { tactic: "Phishing", value: 15, subType: "Generic Email" }, { tactic: "Phishing", value: 8, subType: "Spear Phishing" }, { tactic: "Credential Access", value: 22, subType: "Brute Force" }, { tactic: "Credential Access", value: 14, subType: "Password Spray" }, // ... 更多攻击战术数据 ];
1. 数据预处理与堆叠位置计算
// 提前定义好X轴需要的战术排序 const sortedTactics = ["Phishing", "Credential Access", "Initial Access", "Lateral Movement"]; // 按战术分组 const groupedByTactic = d3.group(data, d => d.tactic); // 计算每个条目的堆叠起始/结束位置 const stackedData = sortedTactics.flatMap(tactic => { const tacticItems = groupedByTactic.get(tactic) || []; let currentBase = 0; return tacticItems.map(item => { const top = currentBase + item.value; const stackedItem = { ...item, y0: currentBase, y1: top }; currentBase = top; return stackedItem; }); });
2. 绘制堆叠条形与轴
// 基础画布与边距设置 const margin = { top: 20, right: 30, bottom: 40, left: 60 }; const width = 800 - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; const svg = d3.select("#chart") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); // X轴比例尺:每个战术对应一个固定宽度的区块 const xScale = d3.scaleBand() .domain(sortedTactics) .range([0, width]) .padding(0.2); // Y轴比例尺:基于所有战术的最大总数值 const maxTotalValue = d3.max(sortedTactics, tactic => { const items = groupedByTactic.get(tactic) || []; return d3.sum(items, d => d.value); }); const yScale = d3.scaleLinear() .domain([0, maxTotalValue]) .range([height, 0]); // 绘制堆叠条形 const colorScale = d3.scaleOrdinal() .domain([...new Set(data.map(d => d.subType))]) .range(d3.schemeCategory10); svg.selectAll(".stack-bar") .data(stackedData) .enter() .append("rect") .attr("class", "stack-bar") .attr("x", d => xScale(d.tactic)) .attr("y", d => yScale(d.y1)) .attr("width", xScale.bandwidth()) .attr("height", d => yScale(d.y0) - yScale(d.y1)) .attr("fill", d => colorScale(d.subType)); // 添加轴 svg.append("g") .attr("transform", `translate(0,${height})`) .call(d3.axisBottom(xScale)); svg.append("g") .call(d3.axisLeft(yScale));
关键说明
- 放弃
d3.stack()是合理的,因为它的设计逻辑是跨类别堆叠同系列数据,和你的需求恰好相反 - 手动计算
y0和y1能精准控制每个战术组内的堆叠顺序,也方便后续调整堆叠层级 - 确保
sortedTactics数组是你需要的X轴排序,直接决定了战术在图表中的展示顺序
内容的提问来源于stack exchange,提问作者Bailey
相关产品推荐
相关产品推荐

