chart = {
var svg = d3.create("svg")
.attr("viewBox", [100, 100, width/2, height/2]);
const simulation = d3.forceSimulation(data)
.alphaMin(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('colidecell', function(){
cells.each(function(d){
var p1 = d.r + buffer; //buffer for the current polygon
//Co-ordinates of current polygon points
d.polygon = d.children.map(function(t){
return [(d.x + ((d.r * t.length) + buffer) * t.sin) , (d.y + ((d.r * t.length) + buffer) * t.cos)];
// return [(d.x + d.r * t.length * t.sin), (d.y + d.r * t.length * t.cos)];
});
});
//Q: Why is quadtree used? Does this speed up identification of overlapping cells?
//Or is it because quadtree functionality allow us to loop through the cells, while accessing
//co-ordinates of all other cells in a single iteration?
var quadtree = d3.quadtree(data, d=>d.x, d=>d.y);
//Plot quadtree squares
// svg.append("g").attr('transform', 'translate(300,250)')
// .selectAll(".node")
// .data(nodes(quadtree))
// .enter().append("rect")
// .attr("class", "node")
// .attr("x", function(d) { return d.x0; })
// .attr("y", function(d) { return d.y0; })
// .attr("width", function(d) { return d.y1 - d.y0; })
// .attr("height", function(d) { return d.x1 - d.x0; })
// .attr("fill","white").attr("opacity",0.1)
// .attr("stroke","black");
var collisions = 0;
//Loop through the cells, and shrink the polygon where it overlaps with neighbours
cells.each(function(d,i){
quadtree.visit(function(node, x0, y0, x1, y1){
var p = node.data;
//Return quadtree nodes that contain any data.
//Q: This confused me a little bit.
//I thought the "!" would return nodes that DIDN'T contain any data.
if (!p) return;
//remove the current node
if (p.id == d.id) return;
//Calculate distance between current node and all others
var dx = p.x - d.x,
dy = p.y - d.y,
dist2 = dx*dx + dy*dy;
//Only keep nodes within a reasonable distance of current node
if (dist2 > 4 * (d.r + p.r) * (d.r + p.r)) return;
var stress = 0;
//Polygon co-ordinates for current cell polygon
d.children.forEach(function(t){
//Co-ordinates for a single feeler
var txy = [(d.x + ((d.r * t.length) + buffer) * t.sin), (d.y + ((d.r * t.length) + buffer) * t.cos)];
var collisions = 0;
//If the feeler overlaps neighbouring polygon
if (d3.polygonContains(p.polygon, txy)) {
collisions ++;
stress++;
//Bring feeler in a little bit
t.length /= 1.05;
//Q: Could you please provide an overview of what this bit does?
//I'm guessing this makes the overlapping polygons jiggle a bit more when
//they overlap?
var tens = d.surface / p.surface,
f = 0.1 * (stress > 2 ? 1 : 0.5);
// f = 0.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([d.vx,d.vy,p.vx,p.vy])
}
})
})
})
})
// .force('x', d3.forceX().strength(0.015).x(50))
// .force('y', function(){
// cells.each(function(d){
// d3.forceY().strength(0.01).y(10)
// })
// })
// .force('x', d3.forceX().strength(function(d,i){return i < 5? 0.1 : 0.01}).x(100))
// .force("tension", function () {
// 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("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("expand", function () {
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, 1.1);
})
})
})
// .force('x', d3.forceX().strength(0.01).x(50))
.force('y', d3.forceY().strength(function(d,i){return i < 40? 0.03 : 0.005 + Math.random()/25}).y(-50))
// .force('collide', d3.forceCollide().radius(5).strength(0.2))
.force('move', function(){
cells.each(function(d){
d.x += Math.random()-0.5;
d.y += Math.random()-0.5;
})
})
//Plot elements--------------------------------------------------
let cells = svg.append("g")
.attr('transform', 'translate(300,250)')
.selectAll('g.cell')
.data(simulation.nodes())
.enter()
.append('g')
.classed('cell', true)
.attr('transform', function (d) {
return 'translate(' + [d.x, d.y] + ')'
})
.attr("opacity",0.5);
let paths = cells
.append('path')
// .attr("fill","black")
.attr('fill', function (d,i){
return color(i)
})
.attr("opacity",0.6)
.attr('d', function (d) {
var arc = 2 * Math.PI / d.children.length,//Point spacing along circle
data = d.children
.map(function (t, i) {
return [i * arc, d.r * t.length];
})
return line(data);
})
.attr("stroke","black")
.attr("stroke-width",1)
// .attr("stroke-opacity",0.8);
cells.each(function(d,i){
let currentCell = d3.select(this);
let data = d.children;
currentCell.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx",function(t){return (((d.r * t.length) + buffer) * t.sin)})
.attr("cy",function(t){return (((d.r * t.length) + buffer) * t.cos)})
.attr("r",1)
.attr('fill', color(i))
.attr("opacity",1)
});
//--------------------------------------------------------------------
//Simulation iteration updates------------------------------------------
let tickcount = 0
simulation.on("tick",function(d) {
tickcount++
cells.attr('transform', function (d) {
return 'translate(' + [d.x, d.y] + ')'
});
paths.attr('d', function (d) {
var arc = 2 * Math.PI / d.children.length,//Point spacing along circle
data = d.children.map(function (t, i) {
return [i * arc, d.r * t.length];
});
return line(data);
});
cells.each(function(d){
d3.select(this)
.selectAll("circle")
.attr("cx",function(t){return (((d.r * t.length) + buffer) * t.sin)})
.attr("cy",function(t){return (((d.r * t.length) + buffer) * t.cos)})
})
})
// })
// })
//--------------------------------------------------
return svg.node()
}