Reputation: 135
I am trying to draw 2 circles groups. First group contains 4 red circles. Second group contains 4 green circles.
I use D3 library and its DOM operators (its important). But screen displays only the first circles group.
js:
var svg = d3.select('svg');
var dataSet = [10, 20, 30, 40];
var circle = svg.selectAll('circle')
.data(dataSet)
.enter()
.append('circle')
.attr({
r:function(d){ return d },
cx:function(d, i){ return i * 100 + 50 },
cy:50,
fill: 'red'
})
.append('circle')
.attr({
r:function(d){ return d },
cx:function(d, i){ return i * 100 + 50 },
cy:350,
fill: 'lime'
})
Upvotes: 1
Views: 143
Reputation: 38151
The issue lies in how you append:
svg.selectAll("circle") // select any circles
.data(dataSet) // bind data to that selection
.enter().append("circle") // return a new selection of entered circles based on the data bind
.attr({...}) // Modify the selection of entered circles, returning that selection
.append("circle") // Append circles as children to the entered circles, returning these new circles as a new selection.
.attr({}) // Modify the child circles
This approach gives you circles as child elements of other circles, and SVG doesn't support this:
<circle>
<circle></circle>
</circle>
Instead there are a handful of options, one is append a g
with the initial enter and append a circle to that twice:
var svg = d3.select('svg');
var dataSet = [10, 20, 30, 40];
var g = svg.selectAll('g')
.data(dataSet)
.enter()
.append('g');
var upper = g.append("circle").attr({
r:function(d){ return d },
cx:function(d, i){ return i * 100 + 50 },
cy:50,
fill: 'red'
});
var lower = g.append('circle')
.attr({
r:function(d){ return d },
cx:function(d, i){ return i * 100 + 50 },
cy:150, // to fit better.
fill: 'lime'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg height="400" width="500"></svg>
Or you could do two enters, one for each row of circles:
var svg = d3.select('svg');
var dataSet = [10, 20, 30, 40];
var upper = svg.selectAll("circle")
.data(dataSet)
.enter()
.append("circle")
.attr({
r:function(d){ return d },
cx:function(d, i){ return i * 100 + 50 },
cy:50,
fill: 'red'
});
var lower = svg.selectAll(null) // don't select the existing circles
.data(dataSet)
.enter()
.append("circle")
.attr({
r:function(d){ return d },
cx:function(d, i){ return i * 100 + 50 },
cy:150, // to fit better.
fill: 'lime'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg height="400" width="500"></svg>
The first one is ideal if each item in the data array is represented by two paired circles that are based on that data array item (datum). The second one is ideal if the data between paired circles may differ (you may have two data arrays).
There are other methods of course, for example you could use the enter selection's placeholders twice (var enter = d3.selectAll().data(data).enter()
), or create a sibling method for D3, but the above two options will work fine.
Upvotes: 1