Skip to content

Instantly share code, notes, and snippets.

@mtaptich
Last active January 8, 2016 18:47
Show Gist options
  • Save mtaptich/881a081e39fd043f3439 to your computer and use it in GitHub Desktop.
Save mtaptich/881a081e39fd043f3439 to your computer and use it in GitHub Desktop.
Bounded Exponential
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
body {
width: 1024px;
margin-top: 0;
margin: auto;
font-family: "Lato", "PT Serif", serif;
color: #222222;
padding: 0;
font-weight: 300;
line-height: 33px;
-webkit-font-smoothing: antialiased;
}
.line{
fill: none;
stroke: #000;
stroke-width: 4px;
}
.circle{
fill-opacity: 0.5;
}
.box{
fill:#ecf0f1;
stroke: #2c3e50;
stroke-dasharray: 2px,2px;
}
.axis path,
.axis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = { top: 60, right: 40, bottom: 60, left: 80 },
width = 960 - margin.left - margin.right,
height = 540 - margin.top - margin.bottom;
bbox = {h: height , w: width, mt: 0}
var duration = 50,
maxRadius = 80, // maximum radius of circle
padding = 5, // padding between circles; also minimum radius
k = 2, // initial number of candidates to consider per circle
m = 2, // initial number of circles to add per frame
n = 100, // remaining number of circles to add
step = 0,
count = 0,
data = d3.range(2).map(function(d,i) { return {x:0, y:0}; });
newCircle = bestCircleGenerator(maxRadius, padding, bbox.w, bbox.h, bbox.mt);
var x = d3.scale.linear().domain([0, n]).range([0, width]);
var y = d3.scale.linear().domain([0, 1]).range([height, 0]);
var color = d3.scale.threshold().domain([maxRadius*0.33, maxRadius*0.66, maxRadius]).range(['#2ecc71', '#f1c40f', '#e74c3c']);
var line = d3.svg.line().interpolate("basis")
.x(function(d, i) { return x(d.x); })
.y(function(d, i) { return y(d.y); });
var svg = d3.select("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 + ")");
svg.append("rect")
.attr("width", bbox.w)
.attr("height", bbox.h)
.attr('y', bbox.mt)
.attr('class', 'box');
var yaxis = svg.append("g")
.attr("class", "y axis")
.call(y.axis = d3.svg.axis().scale(y).orient("left").ticks(5))
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", -56)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Ratio of Total Circle Area to Total Graph Area")
var g = svg.append('g')
var path = svg.append("path")
.datum(data)
.attr("class", "line");
function tick(){
var interval = setInterval(function() {
for (var i = 0; i < m && --n >= 0; ++i) {
step+=1
var circle = newCircle(k, step);
if (circle[0]){ // if a new circle was returned
g.append("circle")
.attr('class', 'circle')
.attr("cx", circle[0])
.attr("cy", circle[1])
.attr("r", 0)
.style("fill", color(circle[2]))
.transition()
.attr("r", circle[2]);
// Update our count. Note: some of the area extends beyond the box, which is okay.
count += circle[2]*circle[2]*Math.PI
// update the line every 2 circles to limit DOM manipulation.
if (step % 2 == 0) {
// update data
data.push({x: step, y: count/ (bbox.w * bbox.h)});
// redraw the line
path.attr("d", line)
}
}
// As we add more circles, generate more candidates per circle.
// Since this takes more effort, gradually reduce circles per frame.
if (k < 500) k *= 1.1, m *= .998;
}
if(n < 0){
console.log('restart')
clearInterval(interval);
// Reset parameters
newCircle = bestCircleGenerator(maxRadius, padding, bbox.w, bbox.h, bbox.mt);
data = d3.range(2).map(function(d,i) { return {x:0, y:0}; }),
n = 100, step = 0, count = 0, k = 2, m = 2;
d3.selectAll('circle').transition().remove()
d3.selectAll('.line').transition().remove()
path = svg.append("path").datum(data).attr("class", "line");
tick();
}
}, duration);
}
// See, http://bl.ocks.org/mbostock/6224050
function bestCircleGenerator(maxRadius, padding, w, h, topmargin) {
var quadtree = d3.geom.quadtree().extent([[0, 0], [w, h]])([]),
searchRadius = maxRadius * 2,
maxRadius2 = maxRadius * maxRadius,
topmargin = topmargin || 0;
return function(k, step) {
var bestX, bestY, bestDistance = padding;
for (var i = 0; i < k; ++i) {
var x = Math.random() * w,
y = Math.random() * h + topmargin,
rx1 = x - searchRadius,
rx2 = x + searchRadius,
ry1 = y - searchRadius,
ry2 = y + searchRadius;
/*
Approximated bounded exponential growth.
*/
var minDistance = maxRadius*( 1 - Math.exp(-step));
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (p = quad.point) {
var p,
dx = x - p[0],
dy = y - p[1],
d2 = dx * dx + dy * dy,
r2 = p[2] * p[2];
if (d2 < r2) return minDistance = 0, true;
var d = Math.sqrt(d2) - p[2];
if (d < minDistance) minDistance = d;
}
return !minDistance || x1 > rx2 || x2 < rx1 || y1 > ry2 || y2 < ry1;
});
if (minDistance > bestDistance) bestX = x, bestY = y, bestDistance = minDistance;
}
var best = [bestX, bestY, bestDistance - padding];
quadtree.add(best);
return best;
};
}
tick()
d3.select(self.frameElement).style("height", (height + margin.top + margin.bottom) + "px");
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment