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

如何通过D3.js向网格布局添加行?附单元格排序问题

解决方案:D3.js 添加网格行与单元格排列问题

核心问题分析

原代码的关键错误是把行级数据直接绑定到所有单元格div,导致每个行对象对应多个DOM元素,破坏了D3数据连接(data join)的一对一映射逻辑,进而引发添加行时的异常行为。


方案一:嵌套数据连接(直观层级结构)

通过先创建行容器,再为每行添加单元格的方式,明确行与单元格的层级关系,让数据连接逻辑更清晰。

修改后的JavaScript代码

const data = [
  {cell1: '1/1', cell2: '1/2', cell3: '1/3', id: 1},
  {cell1: '2/1', cell2: '2/2', cell3: '2/3', id: 2}
];

function update(data) {
  // 1. 绑定行数据到行容器元素
  const rows = d3.select('#grid')
    .selectAll('.row')
    .data(data, d => d.id); // 用id作为唯一标识,确保数据匹配准确

  // 2. 创建新行的容器
  const newRows = rows.enter()
    .append('div')
    .classed('row', true);

  // 3. 合并已有行和新行,统一处理单元格
  const allRows = rows.merge(newRows);

  // 嵌套数据连接:为每行生成单元格
  allRows.each(function(d) {
    // 从行对象中提取单元格数据
    const cellData = [
      {content: d.cell1, className: 'cell1'},
      {content: d.cell2, className: 'cell2'},
      {content: d.cell3, className: 'cell3'}
    ];

    // 绑定单元格数据到当前行内的div
    d3.select(this)
      .selectAll('div')
      .data(cellData)
      .enter()
      .append('div')
      .classed(d => d.className, true)
      .text(d => d.content);
  });
}

update(data);

function addRow() {
  const n = data.length + 1;
  const newRow = {cell1: `${n}/1`, cell2: `${n}/2`, cell3: `${n}/3`, id: n};
  data.push(newRow);
  update(data);
}

对应CSS代码(按行排列)

#grid {
  display: flex;
  flex-direction: column; /* 行垂直排列 */
  gap: 4px; /* 行之间的间距 */
}

.row {
  display: flex; /* 单元格横向排列成一行 */
}

.row > div {
  width: 200px;
}

.cell1 { background-color: purple; }
.cell2 { background-color: orange; }
.cell3 { background-color: forestgreen; }

方案二:扁平化数据+CSS Grid(原生网格布局)

将行数据转换成单个单元格的数据集,直接绑定到网格容器的子元素,利用CSS Grid的自动排列特性实现按行布局。

修改后的JavaScript代码

const data = [
  {cell1: '1/1', cell2: '1/2', cell3: '1/3', id: 1},
  {cell1: '2/1', cell2: '2/2', cell3: '2/3', id: 2}
];

// 把行数据扁平化为单个单元格的数组
function flattenRows(rows) {
  return rows.flatMap(row => [
    {content: row.cell1, id: `${row.id}-1`, className: 'cell1'},
    {content: row.cell2, id: `${row.id}-2`, className: 'cell2'},
    {content: row.cell3, id: `${row.id}-3`, className: 'cell3'}
  ]);
}

function update(data) {
  const cellData = flattenRows(data);

  d3.select('#grid')
    .selectAll('div')
    .data(cellData, d => d.id) // 用唯一id绑定每个单元格
    .join(enter => {
      enter.append('div')
        .classed(d => d.className, true)
        .text(d => d.content);
    });
}

update(data);

function addRow() {
  const n = data.length + 1;
  const newRow = {cell1: `${n}/1`, cell2: `${n}/2`, cell3: `${n}/3`, id: n};
  data.push(newRow);
  update(data);
}

对应CSS代码(保持原生Grid)

#grid {
  display: inline-grid;
  grid-template-columns: repeat(3, 200px);
  gap: 4px; /* 单元格间距 */
}

.cell1 { background-color: purple; }
.cell2 { background-color: orange; }
.cell3 { background-color: forestgreen; }

关键说明

  1. 数据连接的核心:每个数据项必须对应唯一的DOM元素,要么是行容器,要么是单个单元格,避免一对多的绑定。
  2. 按行排列的逻辑:
    • 嵌套方案通过Flex布局的层级结构强制行排列;
    • Grid方案通过扁平化数据的顺序(行1单元格1→行1单元格2→行1单元格3→行2单元格1...),配合Grid默认的grid-auto-flow: row实现自动按行填充。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 01:37:33