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

如何用D3.js生成单SVG多列布局的Actigram(活动图)

问题描述

我需要绘制多列布局的Actigram(活动图),目前用D3.js实现了多SVG折线图(测试代码如下),但希望把这些独立SVG合并成单个SVG;同时想了解绘制时间序列数据的巧妙方法,也愿意尝试更简便的其他可视化库。


现有测试代码

JavaScript

const signalData = [{
    name: "Signal 1",
    data: [1, 2, 3, 4, 5, 6, 7],
  },
  {
    name: "Signal 2",
    data: [2, 3, 1, 4, 5, 1, 3],
  },
  {
    name: "Signal 3",
    data: [1, 7, 2, 6, 3, 5, 4],
  },
];

// This is a line generator. Normally, you pass the x- and y-scales in,
// see also d3-scale on github
const line = d3.line()
  // The first argument is always the datum object, the second is the index
  // Note that I use the index to determine the x coordinate of each point here
  .x((d, i) => i * 50)
  // And the datum object for the y-coordinate
  .y(d => 150 - (15 * d));

// The term `datum` is unrelated to date, but is the singular value of `data`.
// One datum, many data.

d3.select("#plotsHere")
  .selectAll("svg")
  // Append one svg per array entry, look up the so-called enter, update, exit
  // cycle for this. It's the most complex part of d3
  .data(signalData)
  .enter()
  .append("svg")
  // Execute a custom function for each element. People who are new to d3.js
  // over-use this function, very often you don't need it!
  .each(function(d, i) {
    // I pass the current svg element, the datum object, and the index for convenience
    draw(d3.select(this), d, i);
  });

function draw(svg, data, index) {
  // Append a label, set the text to the name
  svg.append("text")
    .attr("x", 20)
    .attr("y", 20)
    .text(d => d.name);

  // Append a path, take the datum of the svg, pick it's `data` property,
  // which is the array of numbers, and set that as the datum of the path,
  // then call the line generator
  svg.append("path")
    .datum(d => d.data)
    .attr("d", line);
}

CSS

path {
  stroke: darkblue;
  stroke-width: 2px;
  fill: none;
}

svg {
  border: solid 2px red;
}

#plotsHere {
  display: grid;
  grid-auto-flow: row;
  grid-gap: 0px;
  grid-template-rows: 150px 150px;
  grid-template-columns: 300px 300px;
}

HTML

<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
  <div id="plotsHere">
  </div>
</body>

解决方案

一、用D3.js合并为单个SVG

核心思路是在单个SVG内用<g>元素分组每个信号的折线和标签,通过计算分组位置实现多列布局,替代多SVG方案。

修改后的代码

JavaScript

const signalData = [{
    name: "Signal 1",
    data: [1, 2, 3, 4, 5, 6, 7],
  },
  {
    name: "Signal 2",
    data: [2, 3, 1, 4, 5, 1, 3],
  },
  {
    name: "Signal 3",
    data: [1, 7, 2, 6, 3, 5, 4],
  },
];

// 布局参数统一配置
const svgWidth = 600;
const svgHeight = 300;
const panelWidth = 300;
const panelHeight = 150;
const padding = 20;

// 用比例尺替代硬编码坐标,适配数据变化
const xScale = d3.scaleLinear()
  .domain([0, signalData[0].data.length - 1])
  .range([padding, panelWidth - padding]);

const yScale = d3.scaleLinear()
  .domain([0, d3.max(signalData.flatMap(d => d.data))])
  .range([panelHeight - padding, padding]);

const line = d3.line()
  .x((d, i) => xScale(i))
  .y(d => yScale(d));

// 创建单个SVG容器
const svg = d3.select("#plotsHere")
  .append("svg")
  .attr("width", svgWidth)
  .attr("height", svgHeight);

// 为每个信号创建分组,计算行列位置
const groups = svg.selectAll("g")
  .data(signalData)
  .enter()
  .append("g")
  .attr("transform", (d, i) => {
    const col = i % 2;
    const row = Math.floor(i / 2);
    return `translate(${col * panelWidth}, ${row * panelHeight})`;
  });

// 绘制信号名称标签
groups.append("text")
  .attr("x", padding)
  .attr("y", padding)
  .text(d => d.name);

// 绘制折线
groups.append("path")
  .datum(d => d.data)
  .attr("d", line)
  .style("stroke", "darkblue")
  .style("stroke-width", 2)
  .style("fill", "none");

// 可选:添加分组边框
groups.append("rect")
  .attr("x", 0)
  .attr("y", 0)
  .attr("width", panelWidth)
  .attr("height", panelHeight)
  .style("stroke", "red")
  .style("fill", "none");

CSS

#plotsHere {
  margin: 20px;
}

HTML

<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
  <div id="plotsHere">
  </div>
</body>

关键改进点

  • 用单个SVG替代多SVG,通过<g>元素分组管理每个子图,减少DOM节点数量
  • 引入D3比例尺,避免硬编码坐标,让图表能自适应数据范围变化
  • 统一配置布局参数,后续调整列数、行高、间距只需修改参数即可

二、时间序列绘制技巧

  1. 时间比例尺适配:如果数据是时间戳,用d3.scaleTime()替代线性比例尺,自动处理时间轴的刻度、格式和间隔
  2. 交互增强:添加鼠标悬停显示详情的功能,可通过自定义事件或D3的事件监听实现
  3. 高效更新:利用D3的enter/update/exit模式,数据变化时仅更新需要修改的元素,避免重绘整个SVG
  4. 坐标轴优化:通过d3.axisBottom()和d3.axisLeft()生成x/y坐标轴,提升图表可读性

三、简便可视化库推荐

如果觉得D3.js成本较高,可尝试以下开箱即用的库:

  • Chart.js:轻量级,API简洁,支持多系列折线图,内置布局能力,适合快速开发
  • Plotly.js:功能丰富,支持交互式图表,自带多子图布局配置,适合复杂可视化需求
  • ECharts:配置式开发,内置大量图表类型,多列布局只需配置grid参数,文档完善

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 06:30:10