d3-beeswarm
Version:
D3 plugin which computes a 'beeswarm' arrangement
126 lines (92 loc) • 7.76 kB
Markdown
# d3-beeswarm
This d3 plugin produces a _beeswarm_ arrangement, thanks to a dedicated algorithm and without the use a the d3.force layout.
Available only for __d3 v3.x__ and __d3 v4__.
## Context
Beeswarm is a one-dimensional scatter plot with closely-packed, non-overlapping points. The beeswarm plot is a useful technique when we wish to see not only the measured values of interest for each data point, but also the distribution of these values
Some beeswarm-like plot implementation uses force layout, but the force layout simulation has some drawbacks:
* it naturally tries to reach its equilibrium by rearranging data points in the 2D space, which can be disruptive to the ordering of the data
* it requires several iterations to reach its equilibrium
This _beeswarm_ plugin uses a dedicated one pass algorithm. By default, this plugin arranges data in an horizontal way, ie. along the x-axis. In this case, the final arrangement is constraint in _x_ and free in _y_. This means that the position of each data reflects its precise _x_ value, while _y_ position doesn't reflect any data-related value (and only serves the non-overlapping constraint). This plugin can also arrange data in a vertical way.
## Examples
* [White House Salaries - Obama '16 VS Trump '17](https://bl.ocks.org/Kcnarf/4608704a70fc24e2c06ca0116830de47).
* This [post](http://poly-graph.co/vocabulary.html) uses a beeswarm plot (but not this plugin).
* This [block](http://bl.ocks.org/Kcnarf/5c989173d0e0c74ab4b62161b33bb0a8) allows to play with the options of this __d3-beeswarm__ plugin.
## Installing
If you use NPM, ```npm install d3-beeswarm```. Otherwise, load ```https://rawgit.com/Kcnarf/d3.beeswarm/master/build/d3-beeswarm.js``` to make it available in AMD, CommonJS, or vanilla environments. In vanilla, a d3 global is exported:
```html
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://rawgit.com/Kcnarf/d3.beeswarm/master/build/d3-beeswarm.js"></script>
<script>
var swarm = d3.beeswarm();
</script>
```
## TL;DR;
In your javascript, in order to define the arrangement:
```javascript
var swarm = d3.beeswarm()
.data(data) // set the data to arrange
.distributeOn(function(d){ // set the value accessor to distribute on
return xScale(d.foo); // evaluated once on each element of data
}) // when starting the arrangement
.radius(4) // set the radius for overlapping detection
.orientation('horizontal') // set the orientation of the arrangement
// could also be 'vertical'
.side('symetric') // set the side(s) available for accumulation
// could also be 'positive' or 'negative'
.arrange(); // launch arrangement computation;
// return an array of {datum: , x: , y: }
// where datum refers to an element of data
// each element of data remains unchanged
```
Then, later in your javascript, in order to draw the swarm:
```javascript
d3.selectAll('circle')
.data(swarm)
.enter()
.append('circle')
.attr('cx', function(bee) { return bee.x; })
.attr('cy', function(bee) { return bee.y; })
.attr('r', 4)
.style('fill', function(bee) { return fillScale(bee.datum.bar); })
```
In the last line, ```bee.datum``` refers to the original datum.
## Reference
* R package: <a href=http://www.cbs.dtu.dk/~eklund/beeswarm/'>http://www.cbs.dtu.dk/~eklund/beeswarm/</a>
## API
<a name="beeswarm" href="#beeswarm">#</a> d3.<b>beeswarm</b>()
Creates a new beeswarm with the default settings:
```javascript
distributeOn = function(d) { return d.x; };
radius = 4;
orientation = 'horizontal';
side = 'symetric';
```
<a name="beeswarm_data" href="#beeswarm_data">#</a> <i>beeswarm.</i><b>data</b>data</b>([data])
If _data_ is specified, set the array of data to arrange and returns this beeswarm. If _data_ is not specified, returns the current array of data to arrange.
<a name="beeswarm_distributeOn" href="#beeswarm_distributeOn">#</a> <i>beeswarm.</i><b>distributeOn</b>([callback])
If _callback_ is specified, set the callback that evaluates the value to distribute on and returns this beeswarm. If _callback_ is not specified, return the current callback, which defaults to ```function(d) { return d.x; }```.
The callback is evaluated once, on each element to arrange, at the beginning of the arrangement computation. The callback must return the final x-coordinate for an horizontal arrangement (or the final y-coordinate for a vertical arrangement). So if you use a d3.scale, your code should look like:
```javascript
d3.beeswarm()
.data(data)
.distributeOn(function(d){
return xScale(d.foo);
})
```
<a name="beeswarm_radius" href="#beeswarm_radius">#</a> <i>beeswarm.</i><b>radius</b>([radius])
Without any argument, returns the current radius of the layout.
If _radius_ is specified, sets the radius of each datum to the specified number. If _radius_ is not specified, returns the current radius, which defaults to 4.
The arrangement uses this _radius_ as a constraint, and arranges each datum so that there is no overlapping. However, when its time to draw each datum, you can use another rendering radius:
* a lower rendering radius will add some padding between data
* a higher rendering radius will add some overlapping between data (making the final viz more compacted as if there wasn't any overlapping, but with the drawback to not meet the non-overlapping constraint of beeswarm arrangement)
<a name="beeswarm_orientation" href="#beeswarm_orientation">#</a> <i>beeswarm.</i><b>orientation</b>([orientation])
If _orientation_ is specified, set the orientation to the specified value (within ```'horizontal'``` or ```'vertical'```) and returns this beeswarm. If _orientation_ is not specified, returns the current orientation, which defaults to ```'horizontal'```.
A ```'horizontal'``` orientation will arrange data along the x-axis. A ```'vertical'``` arrangement will arrange data along the y-axis.
<a name="beeswarm_side" href="#beeswarm_side">#</a> <i>beeswarm.</i><b>side</b>([side])
If _side_ is specified, set the side to the specified value (within ```'symetric'```, ```'positive'``` or ```'negative'```) and returns this beeswarm. If _side_ is not specified, returns the current side, which defaults to ```'symetric'```.
A ```'symetric'``` side arranges data around the main axis, placing data above and below the axis. A ```'positive'```side arranges data only above the main axis. A ```'negative'```side arranges data only below the main axis.
<a name="beeswarm_arrange" href="#beeswarm_arrange">#</a> <i>beeswarm.</i><b>arrange</b>()
Launches the arrangement computation. Return an array of ```{x: , y: , datum: }```, where ```x``` and ```y``` are the computed coordinates, and ```datum``` refers to the original element of data.
## How To
* issue [option to arrange from minToMax, maxToMin, extremeToCenter, shuffled](https://github.com/Kcnarf/d3.beeswarm/issues/7) explains how to arrange in a particular order. It can be tested in [this block](http://bl.ocks.org/Kcnarf/5c989173d0e0c74ab4b62161b33bb0a8).
* issue [add a maximum size, and provide strategies if exceeded](https://github.com/Kcnarf/d3.beeswarm/issues/2) explains how to handle too large viz due to extreme accumulations. It can be tested in [this block](http://bl.ocks.org/Kcnarf/5c989173d0e0c74ab4b62161b33bb0a8).