Reputation: 421
I am using D3.js and I have a list of elements with an attribute describing categories like this one:
var nodes = [
{id:"a", cat:[0,1]},
{id:"b", cat:[0]},
{id:"c", cat:[0,1,2]}];
Each element is a multi-circle-node positioned by a force layout. The number of categories of an element defines the number of circles that will represent it.
The "solution" I have for now is to create layers for each category. The force layout handles the list of data. The layers handle the svg elements. Each layer is an element.
<g class="layer2">
I draw a circle for node c, with radius = 9;<g class="layer1">
I draw circles for nodes a and c, with radius = 6;<g class="layer0">
I draw circles for nodes a, b and c, with radius = 3;The problem is that each data element is being represented by separated circles in separated layers.
How can we represent each element by a group of circles like this:
<g class="node" id="a">
<circle>... <circle>...
</g>
<g class="node" id="b">
<circle>...
</g>
<g class="node" id="c">
<circle>... <circle>... <circle>...
</g>
Is it possible to use something hierarchically in D3 style of code like:
svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("id", function(d){return d.id;}
//for each category on this node
.append("circle")
.attr("r", function(){ return (cat+1)*3;});
A solution like this would be the best option.
Or do I have to create the elements <g class="node" id="...">
separately, selectAll(".node")
and then link them to the data (node list)?
Upvotes: 2
Views: 112
Reputation: 102218
It seems to me that you just need nested selections:
var groups = svg.selectAll(null)
.data(nodes)
.enter()
.append("g")
.attr("id", function(d) {
return d.id
});
var circles = groups.selectAll(null)
.data(function(d) {
return d.cat
})
.enter()
.append("circle")
//etc...
This is a demo showing it, with your nodes
data:
var nodes = [{
id: "a",
cat: [0, 1]
}, {
id: "b",
cat: [0]
}, {
id: "c",
cat: [0, 1, 2]
}];
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("svg");
var groups = svg.selectAll(null)
.data(nodes)
.enter()
.append("g")
.attr("id", function(d) {
return d.id
})
.attr("transform", function(d, i) {
return "translate(" + (50 + 100 * i) + ",75)";
});
var circles = groups.selectAll(null)
.data(function(d) {
return d.cat
})
.enter()
.append("circle")
.attr("r", function(d) {
return 10 + (d * 10)
})
.style("fill", function(d, i) {
return colors(i)
});
circles.sort(function(a, b) {
return d3.descending(a, b)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
If you inspect your SVG, you're gonna see this:
<svg>
<g id="a" transform="translate(50,75)">
<circle r="30" style="fill: rgb(31, 119, 180);"></circle>
<circle r="20" style="fill: rgb(255, 127, 14);"></circle>
</g>
<g id="b" transform="translate(150,75)">
<circle r="30" style="fill: rgb(31, 119, 180);"></circle>
</g>
<g id="c" transform="translate(250,75)">
<circle r="30" style="fill: rgb(31, 119, 180);"></circle>
<circle r="20" style="fill: rgb(255, 127, 14);"></circle>
<circle r="10" style="fill: rgb(44, 160, 44);"></circle>
</g>
</svg>
PS: I'm sorting the circles (have a look at the bottom of the code) to bring the smaller circles to the top, making them visible.
Upvotes: 3
Reputation: 8509
You can do it this way:
svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("id", function(d){return d.id;})
.selectAll("circle")
.data(function(d) {return d.cat}) // <-- bind cat data
.enter()
.append("circle")
.attr("r", function(d) {
//set radius
});
Upvotes: 0