user3486622
user3486622

Reputation: 21

Can I force bubble size ratio in D3 Bubble charts?

I have created a webpage with up to 4 charts depending on specific criteria. Everything looks fantastic. Now the client wants the bubble-size ratio to be consistent across the 4 charts (ie. a value of 2000 is represented with the same sized bubble in every chart.)

Is it possible to configure the bubble.nodes() generator to perform this task?

I know I can provide a radius() calculator, but I don't have the mathematical background to calculate the optimal radius for a given dataset.

I am using a pack layout. After a good night's sleep, I think I came up with a viable solution. After the layout is generated for each chart, I can find the ratio of radius to value for the largest bubble in each chart and apply a transformation scale() to the other SVGs to make the ratios identical (or close enough).

Upvotes: 2

Views: 3263

Answers (2)

Pawel Kam
Pawel Kam

Reputation: 2134

It seems to me that since you are visualizing bubble charts and it’s radius (radii in plural?) you should be using d3.scaleSqrt instead of d3.scaleLinear (d3.scale.linear() in previous versions). Reasoning is simple: circle with a radius of 10 is not twice time the size of a circle with a radius of 5, since the area of a circle equals πr^2.

As noted by @craPkit, when making consistent bubble size across multiple charts one needs variables of minimal and maximal values of all datasets across those multiple charts. In the most common example, when comparing the population of countries the maximalValue would be population of the most populated county in a dataset and minimalValue would be population of the least populated one, even if neither of those countries are present in a particular chart.

In a second step, one must pass the maximalValue and minimalValue to the chart and use them as a domain to set radii for value of a given circle. This could look something like that:

let someFixedMaximalRadiusSize = 50;

someSelectedSVGElement.append('circle')
                      .attr('r', d => {
                        d3.scaleSqrt()
                        .domain([minimalValue < 0 ? minimalValue : 0,
                                 maximalValue])
                        .range([0, someFixedMaximalRadiusSize])(d.value)});

Where someSelectedSVGElement denotes some selection for example created with usage of d3.select(). Hope this answer will help someone.

Upvotes: 0

craPkit
craPkit

Reputation: 653

You can try using a d3.scale.linear() with a fixed, manually set input domain and output range, to map your arbitrary data to consistent, predictable values.

Example:

function getSize(d){ return d.size; }

var params = [
        this.aggregatedDataFromAllCharts_beforePacking, 
        getSize
], dataScale = d3.scale.linear()
        .domain([d3.min.apply(window, params), d3.max.apply(window, params)])
        .range([0, 1000]);

For sizing (the root circle of) your packs, you probably have to use a second scale, which maps the aggregated value of each dataset to concrete svg circle radii for the root circles. Something along the lines of:

var aggregatedSizes = [];
// for all datasets
aggregatedSizes.push(d3.sum(dataset, getSize));
var packSizeScale = d3.scale.linear()
        .domain(Math.min(aggregatedSizes), Math.max(aggregatedSizes))
        .range(this.minCircleRadius, this.maxCircleRadius );

...where min- and maxCircleRadius are the smallest and largest circle sizes you want to display.

// for each of your data sets
var packedCircles = d3.layout.pack().nodes(dataset).value(function(d) { 
        return dataScale(d.size);
}).size([packSizeScale(d3.sum(dataset, getSize) *2)]);

There's also a nice tutorial on D3 scales by Scott Murray

Upvotes: 1

Related Questions