我有一个简单的数据结构,我想用d3来渲染svg。
const data = [
{
project: "One",
stages: [
{ name: "foo", items: [1, 2, 3] },
{ name: "bar", items: [2, 3] }
]
},
{
project: "Two",
stages: [
{ name: "bar", items: [2, 3] },
{ name: "baz", items: [3, 4] }
]
}
];
我想将其呈现为一对嵌套的<g>
元素—一个用于项目,一个用于舞台。在每个阶段组中,我希望有一个<rect>
代表该阶段的所有items
。最后,每项再加一个矩形。最终的结构应该是这样的:
<svg>
<g class="project" data-project="One">
<g class="stage" data-stage="foo">
<rect class="range" /> <-- this doesn't get added -- why?
<rect class="item" data-item="1" />
<rect class="item" data-item="2" />
<rect class="item" data-item="3" />
</g>
<g class="stage" data-stage="bar">
<rect class="range" /> <-- this doesn't get added -- why?
<rect class="item" data-item="2" />
<rect class="item" data-item="3" />
</g>
</g>
<g class="project" data-project="Two">
<g class="stage" data-stage="bar">
<rect class="range" /> <-- this doesn't get added -- why?
<rect class="item" data-item="2" />
<rect class="item" data-item="3" />
</g>
<g class="stage" data-stage="baz">
<rect class="range" /> <-- this doesn't get added -- why?
<rect class="item" data-item="3" />
<rect class="item" data-item="4" />
</g>
</g>
</svg>
我试着这样建模:
// Project group elements
const projects = d3.select(svg.current).selectAll("g.project").data(data);
const projectsEnter = projects
.enter()
.append("g")
.classed("project", true)
.attr("data-project", (d) => d.project);
// Stage groups within each project
const stages = projects
.merge(projectsEnter)
.selectAll("g.stage")
.data((d) => d.stages);
const stagesEnter = stages
.enter()
.append("g")
.classed("stage", true)
.attr("data-stage", (d) => d.name);
// "range" representing the whole stage
// !!!!!
// These rect.range elements don't get added
// !!!!!
const range = stages.merge(stagesEnter).select("rect.range");
const rangeEnter = range.enter().append("rect").classed("range", true);
// Items within each stage
const items = stages
.merge(stagesEnter)
.selectAll("rect.item")
.data((d) => d.items);
const itemsEnter = items.enter().append("rect").classed("item", true);
itemsEnter.merge(items).attr("data-item", (d) => d);
这大部分工作,除了rect.range
元素不被添加。我似乎错误地理解了select
在这里的作用:
const range = stages.merge(stagesEnter).select("rect.range");
const rangeEnter = range.enter().append("rect").classed("range", true);
在这里将单个rect
添加到每个g.stage
的正确方法是什么?
这里有一个代码盒来演示我的问题:https://codesandbox.io/s/sweet-tdd-8tdoh?file=/src/App.js
如果使用join而不是enter/append/merge,代码会更简单:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
const width = 500;
const height = 500;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
const data = [
{
project: "One",
stages: [
{ name: "foo", items: [1, 2, 3] },
{ name: "bar", items: [2, 3] }
]
},
{
project: "Two",
stages: [
{ name: "bar", items: [2, 3] },
{ name: "baz", items: [3, 4] }
]
}
];
const projects = svg.selectAll('.project')
.data(data)
.join('g')
.attr('class', 'project')
.attr('data-project', d => d.project);
const stages = projects.selectAll('.stage')
.data(d => d.stages)
.join('g')
.attr('class', 'stage')
.attr('data-stage', d => d.name);
const range = stages.append('rect')
.attr('class', 'range');
const items = stages.selectAll('.item')
.data(d => d.items)
.join('rect')
.attr('class', 'item')
.attr('data-item', d => d);
</script>
</body>
</html>
生成的SVG:
<svg width="500" height="500">
<g class="project" data-project="One">
<g class="stage" data-stage="foo">
<rect class="range"></rect>
<rect class="item" data-item="1"></rect>
<rect class="item" data-item="2"></rect>
<rect class="item" data-item="3"></rect>
</g>
<g class="stage" data-stage="bar">
<rect class="range"></rect>
<rect class="item" data-item="2"></rect>
<rect class="item" data-item="3"></rect>
</g>
</g>
<g class="project" data-project="Two">
<g class="stage" data-stage="bar">
<rect class="range"></rect>
<rect class="item" data-item="2"></rect>
<rect class="item" data-item="3"></rect>
</g>
<g class="stage" data-stage="baz">
<rect class="range"></rect>
<rect class="item" data-item="3"></rect>
<rect class="item" data-item="4"></rect>
</g>
</g>
</svg>