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> |