Skip to content

Instantly share code, notes, and snippets.

@domoritz
Last active June 30, 2021 20:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domoritz/03716dffb70acdccc7fed240e931d664 to your computer and use it in GitHub Desktop.
Save domoritz/03716dffb70acdccc7fed240e931d664 to your computer and use it in GitHub Desktop.
Histograms
scrolling: true
license: gpl-3.0

Histograms don't always have to be equi-width. Here are a few examples of histograms with unequal bin widths. In this example we draw the bin thresholds from different distributions or construct them in a way that the number of elements in each bin are roughly equal.

Based on the histogram example.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: sans-serif
}
h2 {
text-align: center;
}
.bar rect {
fill: steelblue;
stroke: darkblue;
}
.bar text {
fill: #fff;
font: 10px sans-serif;
}
</style>
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
//var data = d3.range(1000).map(d3.randomBates(10));
var data = d3.range(10000).map(d3.randomNormal()).sort(d3.ascending);
var formatCount = d3.format(",.0f");
var margin = {top: 10, right: 30, bottom: 30, left: 30},
width = 940 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var dataDomain = [d3.min(data), d3.max(data)];
var x = d3.scaleLinear()
.domain([d3.min(data), d3.max(data)])
.rangeRound([0, width])
// make x domain start and end nice
.nice();
var numBins = 42;
// histogram thresholds for (roughly) equi depth histogram
function equiDepthThresholds() {
var binBounds = [];
var depth = Math.floor(data.length / numBins);
for(var j=0; j<data.length; j+=depth) {
binBounds.push(data[j]);
}
return binBounds;
}
var binBounds = [
{
thresholds: d3.range(numBins).map(d3.randomUniform(x.domain()[0], x.domain()[1])),
name: 'uniform random'
},
{
thresholds: d3.range(numBins).map(d3.randomNormal(x.domain()[0], x.domain()[1])),
name: 'normal'
},
{
thresholds: x.ticks(numBins),
name: 'equi width'
},
{
thresholds: equiDepthThresholds(),
name: 'equi depth'
}
]
binBounds.forEach(function(bounds) {
var thresholds = bounds.thresholds;
// add the data domain bounds to the thresholds
thresholds.concat(dataDomain)
thresholds.sort(d3.ascending);
var bins = d3.histogram()
.domain(x.domain())
.thresholds(thresholds)
(data);
// the value is the height
bins = bins.map(function(d) {
d.value = d.length / (d.x1 - d.x0);
return d;
})
var y = d3.scaleLinear()
.domain([0, d3.max(bins, function(d) { return d.value; })])
.range([height, 0]);
var body = d3.select("body");
body.append("h2").html(bounds.name)
var svg = body.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bar = svg.selectAll(".bar")
.data(bins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x0) + "," + y(d.value) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", function(d) { return x(d.x1) - x(d.x0); })
.attr("height", function(d) { return height - (y(d.value)); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", function(d) { return (x(d.x1) - x(d.x0)) / 2; })
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.length); });
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment