Skip to content

Instantly share code, notes, and snippets.

@lsbardel
Last active January 17, 2017 19:52
Show Gist options
  • Save lsbardel/0a8c629d78e691d8efb4b58ae9806bfc to your computer and use it in GitHub Desktop.
Save lsbardel/0a8c629d78e691d8efb4b58ae9806bfc to your computer and use it in GitHub Desktop.
Maximum rectangular area
license: bsd-3-clause

A visualisation of the maximum rectangular area in a histogram. Check this useful video for good explanation of the O(N) algorithm.

Single code base visualisations on svg and canvas thanks to d3-canvas-transition module.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Maximum rectangular area on histogram</title>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://giottojs.org/d3-canvas-transition/0.3.4/d3-canvas-transition.js"></script>
<style>
#panel {
background-color: rgba(245,245,245,0.9);
padding: 5px;
position: absolute;
display: block;
}
</style>
</head>
<body>
<div id="panel">
<div id="paper">
<label>
<input id='svg' name="type" type="radio" checked>
<span>svg</span>
</label>
<label>
<input id='canvas' name="type" type="radio">
<span>canvas</span>
</label>
</div>
</div>
<div id="example" style="max-width: 960px"></div>
<script>
(function () {
d3.select('#svg').on('click', function () {
draw('svg');
});
d3.select('#canvas').on('click', function () {
draw('canvas');
});
if (d3.resolution() > 1) {
d3.select('#paper').append('label').html(
"<input id='canvas-low' name='type' type='radio'><span>canvas low resolution</span>"
);
d3.select('#canvas-low').on('click', function () {
draw('canvas', 1);
});
}
var N = 20,
H = 15,
generator = d3.randomUniform(1, H-2),
color = d3.scaleSequential(d3.interpolateViridis).domain([0, H+5]),
areaColor = color(H+5),
data = d3.range(0, N).map(function () {return generator();});
draw('svg');
function draw(type, r) {
var example = d3.select("#example"),
width = d3.getSize(example.style('width')),
height = Math.min(500, width),
barWidth = width / data.length,
y = d3.scaleLinear().domain([0, H]).range([height, 0]),
animation = {};
example.select('.paper').remove();
var paper = example
.append(type)
.classed('paper', true)
.attr('width', width).attr('height', height).canvasResolution(r).canvas(true);
paper.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', '#fff');
paper.append('g')
.selectAll('rect')
.data(data)
.enter()
.append("rect")
.attr("transform", function (d, i) {return "translate(" + i*barWidth + ")";})
.attr("y", function (d) {return y(d);})
.attr("height", function(d) {return height - y(d); })
.attr("width", barWidth - 1)
.attr("fill", function (d) {return color(d);})
.attr("stroke", '#fff')
.attr("stroke-width", 1)
.attr("stroke-linejoin", "round");
var area = paper
.append('rect')
.style("fill", areaColor)
.style("fill-opacity", 0.6);
paper.select('g').selectAll('rect')
.call(update, data)
.call(animate, 2000);
function update (histogram, di) {
var ad = largest_rectangle(di);
histogram
.data(di)
.attr("y", function (d) {return y(d);})
.attr("height", function(d) {return height - y(d); })
.attr("fill", function (d) {return color(d);});
area
.attr("transform", "translate(" + ad[0]*barWidth + "," + y(ad[2]) + ")")
.attr('width', barWidth*(ad[1] - ad[0]) - 1)
.attr('height', height - y(ad[2]));
}
function animate (histogram, duration) {
var data1 = d3.range(0, N).map(function () {return generator();}),
Ht = d3.interpolateArray(data, data1);
d3.select(animation)
.transition()
.duration(duration)
.tween("attr:y", function () {
return function (t) {
update(histogram, Ht(t));
}
});
d3.timeout(function () {
data = data1;
animate(histogram, duration);
}, (Math.random() + 1) * duration);
}
}
function largest_rectangle (Height) {
var stack = [],
max_area = 0,
area, x1, x2, y;
Height.map(function (h, i) {
while (stack.length && h < Height[stack[stack.length-1]])
calc_area(i);
stack.push(i);
});
while (stack.length) calc_area(H.length);
return [x1, x2, y];
function calc_area (i) {
var j = stack.splice(stack.length-1);
var t = stack.length ? stack[stack.length-1] + 1 : 0;
area = Height[j] * (i - t);
if (area > max_area) {
max_area = area;
y = Height[j];
x1 = t;
x2 = i;
}
}
}
}());
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment