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

如何在D3.js中实现同类别条形堆叠效果

实现攻击战术堆叠条形图方案

核心思路

常规d3.stack()适用于按系列跨类别堆叠的场景,但你的需求是同战术下的多个子条目堆叠,所以需要先对数据做定向预处理:

  1. 按attack tactic分组,确保同战术的条目聚在一起
  2. 为每个战术组内的条目手动计算堆叠的起始位置(y0)和结束位置(y1)
  3. 保持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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 05:47:12