Built with blockbuilder.org
forked from ericsoco's block: contour blob
license: mit |
Built with blockbuilder.org
forked from ericsoco's block: contour blob
<!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: 900px; height: 450px"></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.setInterval(tick, 50); | |
} | |
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> |