score:1

Accepted answer

The point of the d3 data pipeline is to avoid unnecessary explicit looping and procedural code. Looping still has a place.

But let's say you want to be idiomatic, and not loop. Then when you need is d3.range(). It turns an integer bound into a list. E.g. d3.range(4) == [0, 1, 2, 3]. If you're going through a pipeline, have an integer, and need elements for every value up to that integer, the key snippet is:

g.selectAll('circle')
 .data(d => d3.range(d.shapes.circle))
 .enter()
 .append('circle')

which converts the number of circles coming in from your data item to that many circles. Unfortunately, it's not quite that simple in practice. You're processing a multi-level structure, and you probably want to not just produce N different circles, but vary those circles based on some higher-level context. So in practice it's more like:

g.selectAll('circle')
 .data((d, i) => d3.range(d.shapes.circle)
                   .map(j => [i, j]))
 .enter()
 .append('circle')

That is, for the i'th record in your data array, the data fed to create circles combines i with the values of j coming from d3.range().

The full code's a bit longer than fits comfortably here, but here's a live example on Plunker.

Colors are chosen by the order in the input data; shape positioning and opacity are chosen randomly.


Related Query

More Query from same tag