Built with blockbuilder.org
forked from pbellon's block: Random pseudo-circles
forked from pbellon's block: Random pseudo-circles concave hull
forked from pbellon's block: Random pseudo-circles concave hull
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="bezier.js"></script>
<script src="d3-interpolate-path.js"></script>
<script src="https://cdn.jsdelivr.net/gh/mrdoob/stats.js/build/stats.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<script>
var DEBUG = true;
var simulation, stats, $nodes, $links;
var rand = (min , max)=>(Math.random()*max + min);
var randPick = (arr)=>(arr[Math.round(rand(0, arr.length-1))]);
// randomly return 1 or -1
var randSign = ()=>(Math.random()>=0.5 ? 1:-1);
if(DEBUG){
stats = new Stats();
stats.showPanel( 0 ); // 0: fps, 1: ms, 2: mb, 3+: custom
document.body.appendChild( stats.dom );
}
var width = 960;
var height = 500;
// links constants
var LINK_CURVE_DISTANCE = -15;
var LINK_KERNEL_SCALE = 1.33;
// circle constants
var SHOW_CIRCLE_POINTS = false;
var PHI = Math.PI * 4;
var POINTS_PER_CIRCLE = 20;
var CURVE = d3.curveBasisClosed;
var RADIUS_JITTER = 4;
// animations constants
var UPDATE_SIMULATION_INTERVAL = 500;
var animations = {
position: {
interval: 350,
duration: 2000
},
shape: {
interval:269,
duration: 1000
}
};
var nodes = [
{ id: 0, animating: false, x: 340, y: 150, radius: 14, color: 'cyan' },
{ id: 1, animating: false, x: 540, y: 225, radius: 19, color: 'red' },
{ id: 2, animating: false, x: 200, y: 200, radius: 26, color: 'blue' },
{ id: 3, animating: false, x: 403, y: 370, radius: 28, color: 'green' },
];
var links = [
{source: 0, target: 3},
{source: 1, target: 2},
{source: 2, target: 0},
];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var radialLine = d3.radialLine()
.angle(function(d){ return d.angle; })
.radius(function(d){ return d.radius; })
.curve(CURVE);
var reshapeCircle = ($circle, duration)=>{
const circle = $circle.data()[0];
$circle.classed('reshaping', true);
var old = $circle.attr('d') || circle.path;
var newPath = radialLine(circlePoints(circle.radius, circle.points.length));
const interpolator = d3.interpolatePath(old,newPath);
const $filteredLinks = $links.filter(
function(){
var link = d3.select(this).data()[0];
return link.data.source.id == circle.id;
}
).select('.link-base');
var t = d3.timer((p)=>{
var r = p/duration;
if(r>1.0){
t.stop();
r = 1.0;
$circle.classed('reshaping', false);
}
const path = interpolator(r);
$circle.attr('d', path);
$filteredLinks.attr('d', path);
});
};
var moveCircle = (circle, duration)=>{
var offsetX = randSign() * circle.radius * 0.3;
var offsetY = randSign() * circle.radius * 0.3;
var otherPosition = Object.assign({}, circle, {
x: circle.x + offsetX,
y: circle.y + offsetY
});
var interpolator = d3.interpolateObject(circle, otherPosition);
var revInterpolator = d3.interpolateObject(otherPosition, circle);
var timer = d3.timer(function(time){
circle.animating = true;
var timeRatio = time/duration;
var _interpolator = timeRatio <= 0.5 ? interpolator : revInterpolator;
var pos = _interpolator(timeRatio);
circle.x = pos.x;
circle.y = pos.y;
if(timeRatio > 1.0){
circle.animating = false;
timer.stop();
}
});
};
var circlePoints = function(radius, nbPoints){
var stepAngle = PHI/nbPoints;
var points = [];
for(var i=0; i<nbPoints; i++){
var angle = stepAngle*i;
var jitterRadius = randSign() * RADIUS_JITTER*(Math.random());
points.push({
angle: angle,
radius: radius + jitterRadius,
});
}
return points;
};
var shapePath = (shape)=>{
var {startcap, endcap, forward, back} = shape;
var rnd = Math.round;
var coords = ({x,y})=>`${rnd(x)},${rnd(y)}`;
var path = `M ${coords(startcap.points[0])}`
+ ` L ${coords(startcap.points[2])}`
+ forward.map(({points:pts})=>(
`C${coords(pts[1])} ${coords(pts[2])} ${coords(pts[3])}`
)).join(' ')
+ back.map(({points:pts})=>(
`C${coords(pts[1])} ${coords(pts[2])} ${coords(pts[3])}`
)).join(' ');
return path;
};
var outlineShapes = (curve, d1,d2,d3,d4, t)=>{
const utils = Bezier.getUtils();
var outline = curve.outline(d1,d2,d3,d4).curves;
return {
startcap: outline[0],
forward: outline.slice(1,3),
endcap: outline[3],
back: outline.slice(-2)
};
};
var linkBodyPath = (link)=>{
var outline;
var src = link.data.source;
var tgt = link.data.target;
if(link.source.x){
src = link.source;
tgt = link.target;
}
const midP = 0.5;
const d = LINK_CURVE_DISTANCE;
const mid = (a,b)=>({
x:(a.x+b.x)*midP,
y:(a.y+b.y)*midP
});
const scale = LINK_KERNEL_SCALE;
const r = scale * (link.data.source.radius || 10) - 4;
const _mid = mid(src,tgt);
const angle = Math.atan2(tgt.y-src.y, tgt.x-src.x)*180/Math.PI;
const normalAngle = 90-angle;
_mid.x += d*Math.cos((normalAngle*Math.PI)/180);
_mid.y += d*Math.sin((normalAngle*Math.PI)/180);
var curve = Bezier.quadraticFromPoints(src, _mid, tgt);
var shapes = outlineShapes(curve, r, r, 4, 4);
return { shape: shapePath(shapes), curve: curve.toSVG() }
};
var drawNodes = function(nodes){
$nodes = svg.selectAll('.node')
.data(nodes);
const $nodesEnter = $nodes.enter()
.append('path')
.attr('comp-op', 'src')
.attr('d', (node)=>(radialLine(node.points)))
.attr('fill', (node)=>(node.color));
$nodes = $nodes.merge($nodesEnter);
$nodes.exit().remove();
};
var drawLinks = (links)=>{
const scale = LINK_KERNEL_SCALE;
const linkColor = (link)=>link.data.source.color;
$links = svg.selectAll('.link')
.data(links);
const $linksEnter = $links.enter()
.append('g')
.attr('comp-op', 'src')
.style('opacity', 0.3)
.classed('link', true);
$linksEnter.append('path')
.classed('link-base', true)
.attr('comp-op', 'src')
.attr('d', (d)=>(d.data.source.path))
.attr('transform', (link)=>(
`translate(${link.data.source.x}, ${link.data.source.y}) scale(${scale})`
))
.attr('fill', linkColor);
$linksEnter.append('path')
.classed('link-body', true)
.attr('fill', linkColor)
.attr('comp-op', 'src-out')
.attr('d', (d)=>linkBodyPath(d).shape);
$linksEnter.append('path')
.classed('link-curve', true)
.attr('fill', 'none')
.attr('stroke','black');
$links = $links.merge($linksEnter);
}
var configSimulation = function(nodes, links){
var simulation;
var onTick = function(){
const scale = LINK_KERNEL_SCALE;
$nodes
// mouvement des noeuds
.attr('transform', (node)=>`translate(${node.x}, ${node.y})`);
// mouvement et déformation de la base des liens
$links
.select('.link-base')
.attr('transform', (link)=>(
`translate(${link.source.x}, ${link.source.y}) scale(${scale})`
));
$links.select('.link-body')
.attr('d', (d)=>linkBodyPath(d).shape);
$links.select('.link-curve')
.attr('d', (d)=>linkBodyPath(d).curve);
// déformation du corps des liens
};
simulation = d3.forceSimulation(nodes)
.force('charge', d3.forceManyBody().distanceMin(100))
.force('link', d3.forceLink(links).id((node)=>node.id).distance(150))
.on('tick', onTick);
d3.interval((time)=>{
simulation.alpha(0.5);
}, 400);
d3.interval((time)=>{
var _nodes = $nodes.filter(':not(.reshaping)');
if(!_nodes.empty()){
var node = randPick(_nodes.nodes());
reshapeCircle(d3.select(node), animations.shape.duration);
}
}, animations.shape.interval);
return simulation;
};
// first initialisation of circles
nodes.forEach(function(c){
c.points = circlePoints(c.radius, POINTS_PER_CIRCLE);
c.path = radialLine(c.points);
});
links.forEach((link)=>{
link.data = {
source: nodes.find((node)=>node.id == link.source),
target: nodes.find((node)=>node.id == link.target),
};
});
drawLinks(links);
drawNodes(nodes);
simulation = configSimulation(nodes, links);
</script>
</body>
Updated missing url https://rawgit.com/mrdoob/stats.js/master/build/stats.min.js to https://cdn.jsdelivr.net/gh/mrdoob/stats.js/build/stats.min.js
https://d3js.org/d3.v4.min.js
https://rawgit.com/mrdoob/stats.js/master/build/stats.min.js