Reputation: 25386
I have a simple data structure that I want to use to render svg with d3.
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] }
]
}
];
I want to render this as a couple nested <g>
elements -- one for the project and one for the stage. Within each stage group, I want to have one <rect>
representing all the items
in the stage. Finally, one more rect per item. The final structure should look like:
<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>
I tried to model this like so:
// 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);
This mostly works, except the rect.range
elements don't get added. I seem to be incorrectly understanding what select
does here:
const range = stages.merge(stagesEnter).select("rect.range");
const rangeEnter = range.enter().append("rect").classed("range", true);
What's the correct way to get a single rect
added to each g.stage
here?
Here's a codesandbox to demo my issue: https://codesandbox.io/s/sweet-tdd-8tdoh?file=/src/App.js
Upvotes: 0
Views: 64
Reputation: 1571
The code is simpler if you use join rather than 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>
The generated 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>
Upvotes: 1