Skip to content

Instantly share code, notes, and snippets.

@ericsoco
Last active August 30, 2017 04:01
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 ericsoco/1b5d498ddb1bb48fbc9b099e2ed931be to your computer and use it in GitHub Desktop.
Save ericsoco/1b5d498ddb1bb48fbc9b099e2ed931be to your computer and use it in GitHub Desktop.
contour blob
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-contour.v1.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<div id="app" style="width: 1050px; height: 1500px"></div>
<script>
var MODE_SVG = 'svg',
MODE_CANVAS = 'canvas',
MODE = MODE_CANVAS;
var SHOW_NODES = false;
var BLEND_MULTIPLY = true;
var app = void 0,
outerwidth = void 0,
outerheight = void 0,
width = void 0,
height = void 0,
nodes = void 0,
links = void 0;
var contourGenerator = void 0,
contours = void 0,
colorScale = void 0;
var graph = void 0,
circles = void 0;
var canvas = void 0,
ctx = void 0,
path = void 0;
var attractParams = {
strength: {
base: 16,
value: 16,
mod: 16,
speed: 0.01,
counter: 0.5 * Math.PI
}
};
var collisionParams = {
strength: {
base: 4,
value: 4,
mod: 4,
speed: 0.01,
counter: Math.PI
},
radius: {
base: 4,
value: 4,
mod: 4,
speed: 0.01,
counter: Math.PI
}
};
var simulation = void 0;
function init(params) {
initGraph();
};
function initGraph() {
app = d3.select('#app').attr('class', 'contour-01');
var margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
outerWidth = app.node().offsetWidth,
outerHeight = app.node().offsetHeight;
width = outerWidth - margin.left - margin.right, height = outerHeight - margin.top - margin.bottom;
// dummy data
var RADIUS_MIN = 8;
var RADIUS_VAR = 16;
var NUM_NODES = 50;
nodes = [];
for (var i = 0; i < NUM_NODES; i++) {
nodes.push({
// x: (0.3 + 0.4 * Math.random()) * width,
// y: (0.3 + 0.4 * Math.random()) * height,
// r: RADIUS_MIN + Math.random() * RADIUS_VAR,
r: RADIUS_MIN,
id: i
});
}
/*
const NODE_LINK_RATIO = 5;
const NUM_LINKS = Math.floor(NUM_NODES / NODE_LINK_RATIO);
let source, target;
links = [];
for (let i=0; i<NUM_LINKS; i++) {
source = Math.floor(Math.random() * NUM_NODES);
target = (source + Math.floor(Math.random() * 10)) % NUM_NODES;
links.push({
source,
target
});
}
*/
simulation = d3.forceSimulation(nodes).force('collision', d3.forceCollide().strength(collisionParams.strength.value).radius(function (d) {
return 1.5 * d.r;
}))
/*
.force('link', d3.forceLink()
.id(d => d.id)
.links(links)
.distance(30)
)
*/
.force('attract', d3.forceManyBody().strength(attractParams.strength.value).distanceMax(600)).force('center', d3.forceCenter(width / 2, height / 2)).alphaDecay(0).stop();
//.on('tick', layoutTick);
contourGenerator = (0, d3.contourDensity)().x(function (d) {
return d.x;
}).y(function (d) {
return d.y;
}).size([width, height]).bandwidth(40).thresholds(16);
// .cellSize(64);
if (MODE === MODE_CANVAS) {
canvas = app.append('canvas').attr('width', outerWidth).attr('height', outerHeight);
ctx = canvas.node().getContext('2d');
if (BLEND_MULTIPLY) ctx.globalCompositeOperation = 'multiply';
ctx.translate(margin.left, margin.top);
path = d3.geoPath().context(ctx);
// colorScale = d3.scaleSequential(d3ScaleChromatic.interpolateYlGnBu);
colorScale = d3.scaleSequential(d3.interpolateCubehelixDefault);
// colorScale = d3.scaleSequential(d3ScaleChromatic.interpolatePuBu);
} else {
graph = app.append('svg').attr('width', outerWidth).attr('height', outerHeight).append('g').attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
circles = graph.selectAll('circle').data(nodes).enter().append('circle').attr('cx', function (d) {
return d.x;
}).attr('cy', function (d) {
return d.y;
}).attr('r', function (d) {
return d.r;
});
contours = graph.selectAll('path').data(contourGenerator(nodes)).enter().append('path').classed('contour', true).attr('d', d3.geoPath());
}
function tick () {
simulation.tick();
layoutTick();
}
window.setTimeout(function () {
window.setInterval(tick, 500);
}, 2000);
}
function layoutTick() {
updateParams(collisionParams.strength);
updateParams(collisionParams.radius);
updateParams(attractParams.strength);
simulation.force('collision').strength(collisionParams.strength.value).radius(function (d) {
return collisionParams.radius.value * d.r;
});
simulation.force('attract').strength(attractParams.strength.value);
draw();
}
function updateParams(params) {
params.counter += params.speed;
params.value = params.base + Math.sin(params.counter) * params.mod;
}
function draw() {
if (MODE === MODE_CANVAS) {
ctx.clearRect(0, 0, width, height);
ctx.translate(outerWidth/2, outerHeight/2);
ctx.rotate(0.1 * Math.PI);
ctx.translate(-outerWidth/2, -outerHeight/2);
contours = contourGenerator(nodes);
var numContours = contours.length;
// colorScale.domain([0, (BLEND_MULTIPLY ? 1.5 : 1) * numContours]);
colorScale.domain([(BLEND_MULTIPLY ? 1.5 : 1) * numContours, 0]); // reverse for cubehelix
contours.forEach(function (d, i) {
var color = colorScale(i);
if (BLEND_MULTIPLY) {
color = d3.color(colorScale(i));
color.opacity = 1 - 0.75 * Math.pow(i / numContours, 0.75);
}
// console.log(i, color);
ctx.fillStyle = color;
ctx.beginPath();
path(d);
ctx.fill();
});
if (SHOW_NODES) {
nodes.forEach(function (d) {
ctx.strokeStyle = 'rgba(0, 0, 0, 0.85)';
ctx.beginPath();
ctx.arc(d.x, d.y, d.r, 0, 2 * Math.PI);
ctx.stroke();
});
}
} else {
circles.attr('cx', function (d) {
return d.x;
}).attr('cy', function (d) {
return d.y;
}).attr('r', function (d) {
return d.r;
});
contours.data(contourGenerator(nodes)).attr('d', d3.geoPath());
}
}
init();
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment