Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active Feb 20, 2019
Embed
What would you like to do?
Stressed Cells 1
license: gpl-3.0

Cells adapt their shape to their environment. When pressed by surrounding cells, they get stressed and become more opaque.

Drag a cell to play.

Made by Philippe Rivière with blockbuilder.org.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
margin: 0;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: #333;
}
.active path {
stroke-width: 3px;
}
</style>
</head>
<body>
<script>
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
.append('g')
.attr('transform', 'translate(480,250)');
var color = d3.scaleOrdinal(d3.schemeCategory10);
color = d3.scaleOrdinal(['#999', '#966', '#696']);
color = function(i) { return d3.cubehelix((i%100)*3.60, 1.2, 0.6); }
// my data is a set of initial positions and a radius
var data = d3.range(500)
.map(function (i) {
var a = 2 * Math.PI * Math.random(),
d = Math.sqrt(Math.random());
return {
id: i,
r: 4 * (1 + 5 * Math.random() * Math.random()),
x: 300 * Math.cos(a) * d,
y: 200 * Math.sin(a) * d,
color: color(i),
};
});
// add n feelers
data = data.map(function (d) {
var n = Math.floor(4 + d.r / 3),
start = 2 * Math.PI * Math.random();
d.children = d3.range(n)
.map(function (i) {
var angle = i * Math.PI * 2 / n + start,
t = {
length: 1,
angle: angle,
sin: Math.sin(angle),
cos: Math.cos(angle),
parent: d,
};
return t;
});
return d;
});
a = 0;
var simulation = d3.forceSimulation(data)
.alphaTarget(0.02)
.force("surface", function (alpha) {
cells.each(function (d) {
d.surface = Math.sqrt(
d.children.map(function (t) {
return t.length * t.length;
})
.reduce(function (a, b) {
return a + b;
}, 0) / d.children.length);
})
})
.force("collidecell", function (alpha) {
cells.each(function (d) {
var e = d3.extent(d.children.map(function (t) {
return t.length;
})),
p1 = d.r + 3;
d.polygon = d.children.map(function (t) {
return [d.x + p1 * t.length * t.sin, d.y - p1 * t.length * t.cos];
});
});
var quadtree = d3.quadtree(data,
function (d) {
return d.x;
},
function (d) {
return d.y;
});
var collisions = 0;
cells.each(function (d, i) {
// simulation.findMany(…)
quadtree.visit(function (node, x0, y0, x1, y1) {
if (x1 < d.x - 2 * d.r || x0 > d.x + 2 * d.r || y1 < d.y - 2 * d.r || y0 > d.y + 2 * d.r) {
return true;
}
var p = node.data;
if (!p) return;
if (p.id == d.id) return;
var dx = p.x - d.x,
dy = p.y - d.y,
dist2 = dx * dx + dy * dy;
if (dist2 > 4 * (d.r + p.r) * (d.r + p.r)) return;
var stress = 0;
d.children.forEach(function (t) {
var txy = [d.x + d.r * t.length * t.sin, d.y - d.r * t.length * t.cos];
if (d3.polygonContains(p.polygon, txy)) {
collisions++;
stress++;
t.length /= 1.05;
var tens = d.surface / p.surface,
f = 0.1 * (stress > 2 ? 6 : 1);
d.vx += f * Math.atan((d.x - p.x) * tens);
d.vy += f * Math.atan((d.y - p.y) * tens);
p.vx -= f * Math.atan((d.x - p.x) / tens);
p.vy -= f * Math.atan((d.y - p.y) / tens);
}
});
});
});
//console.log('collisions', collisions);
})
.force("tension", function (alpha) {
cells.each(function (d) {
var l = d.children.length;
d.children.forEach(function (t, i) {
var m = d.children[(l - 1) % l].length + d.children[(l + 1) % l].length;
var f = 1 / 10; // spiky-ness
t.length = (1 - f) * t.length + f * m / 2;
});
})
})
.force("expand", function (alpha) {
cells.each(function (d) {
var u = 1 / Math.sqrt(d.surface);
d.children.forEach(function (t) {
t.length *= u;
t.length = Math.min(t.length, 2);
})
})
})
.force("internal", function (alpha) {
var f = 1 / 10;
cells.each(function (d) {
d.vx += f * d3.sum(d.children, function (t) {
return t.length * t.sin;
});
d.vy += f * d3.sum(d.children, function (t) {
return -t.length * t.cos;
});
})
})
.force("x", d3.forceX(function(d) { return d.x; }).strength(0.01))
.force("y", d3.forceY(function(d) { return d.y; }).strength(0.01))
.force("move", function(){
cells.each(function (d) {
d.vx += Math.random()-0.5;
d.vy += Math.random()-0.5;
});
})
.on("tick", ticked);
// cells
var cells = svg.selectAll('g.cell')
.data(data)
.enter()
.append('g')
.classed('cell', true)
.attr('transform', function (d) {
return 'translate(' + [d.x, d.y] + ')'
});
if (false) cells.append('circle')
.attr('r', 1)
.attr('fill', function (d) {
return d.color;
});
// segments
var paths = cells
.append('path')
.attr('stroke', function (d, i) {
return color(i);
})
.attr('fill', function (d, i) {
return color(i);
})
.attr('fill-opacity', 0.3);
function ticked() {
cells
.attr('transform', function (d) {
return 'translate(' + [d.x, d.y] + ')'
});
paths
.attr('d', function (d) {
var arc = 2 * Math.PI / d.children.length,
data = d.children
.map(function (t, i) {
return [i * arc, d.r * t.length];
});
return line(data) + 'Z';
})
.transition()
.attr('fill-opacity', function (d) {
return 0.05 - 8*Math.log(d.surface);
});
}
var line = d3.radialArea()
.curve(d3.curveCatmullRomClosed)
.innerRadius(function (d) {
return 0.4 * line.outerRadius()(d);
});
var line = d3.radialLine()
.curve(d3.curveCatmullRomClosed);
var ease = function (t) {
return t > 1 ? 1 : t < 0 ? 0 : d3.easeExpIn(t);
}
cells
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this)
.attr('transform', function (d) {
return 'translate(' + [d.x = d3.event.x, d.y = d3.event.y] + ')';
});
}
function dragended(d) {
d3.select(this).classed("active", false);
simulation.alpha(1).restart();
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment