Dotplot distribution (frequency count)

Viewed 135

I am trying to replicate a histogram that is made up of dots. Here is a good reference:

enter image description here

My data has the following format:

{'name':'Company1', 'aum':42, 'growth':16},
{'name':'Company2', 'aum':36, 'growth':24},
{'name':'Company3', 'aum':34, 'growth':19},
...

In my case I'm binning by aum and color coding by growth. Everything seemed fine up until I went to set the cy attribute.

graphGroup.selectAll('circ')
    .data(data)
    .enter()
    .append('circ')
    .attr('r',8)
    .attr('cx', function(d) { return xScale(d.aum);})
    .attr('cy', ??????)
    .style('fill', function(d) { return colorScale(d.growth);});

The only way I can think of to handle this is to re-engineer the data to give it an explicit y index value or something. However, this is not supported in my statistics suite and the n is too large for me to manually do it.

Question

Given my current data structure, can d3 help me with anything ad hoc to get the correct y value? I was thinking a counter might work, but I was unable to devise a bin-specific counter. Maybe d3 has a more elegant solution than a counter.

1 Answers

...can d3 help me with anything ad hoc to get the correct y value?

No, there is no native method for this. But here is where most people get D3 wrong: D3 is not a charting tool (except for the axis module, D3 doesn't paint anything!), it's just a collection of JavaScript methods. This fact simultaneously gives D3 its weakness (steep learning curve) and its advantage (you can do almost anything with D3).

In your case, for instance, all I'd do is separate the data in bins (using a histogram generator, for instance, or just manipulating the data directly) and then appending the circles in each bin/group using their indices.

It can be just something like this:

circle.attr("cy", function(_, i) {
    return maxValue - circleRadius * i;
});

This will append the first circle of each bin (group) at the base of the SVG, and then each subsequent one in a smaller y coordinate, according to its index.

Check this basic demo:

const data = d3.range(100).map(() => Math.random());
const svg = d3.select("svg");
const xScale = d3.scalePoint(d3.range(10).map(d => d / 10), [20, 480])
  .padding(0.5);
const binData = d3.histogram()
  .domain([0, 1])(data);
const g = svg.selectAll(null)
  .data(binData)
  .enter()
  .append("g")
  .attr("transform", d => "translate(" + xScale(~~(100 * d.x0) / 100) + ",0)");
g.selectAll(null)
  .data(d => d)
  .enter()
  .append("circle")
  .attr("r", 5)
  .attr("cy", function(_, i) {
    return 220 - 13 * i;
  })
  .style("fill", "gray")
  .style("stroke", "black");
const axisGroup = svg.append("g")
  .attr("transform", "translate(0,230)")
  .call(d3.axisBottom(xScale));
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="250"></svg>

Related