Public
Edited
May 10, 2024
Insert cell
Insert cell
import {formWithSumbit} from "@forresto/form-input-with-submit"
Insert cell
Insert cell
mutable ticks = 0
Insert cell
height
Insert cell
strokeWidth = 1
Insert cell
buffer = 1 + strokeWidth
Insert cell
data2
Insert cell
// data2 = data.slice(0,200)

Insert cell
data2.length
Insert cell
sim_scrubber = (n_frames) => Scrubber(Array.from({length: n_frames}, (_, i) => i), {
format: i => "frame of simulation: " + (i + 1),
delay: 50,
autoplay: false,
loop: false
})
Insert cell
import {Scrubber} from '@mbostock/scrubber'
Insert cell
keyframe

Insert cell
centroid = [215.8945648640038, 196.72263607892805]
Insert cell
data
Insert cell
// initialData = data.map(d => ({...d, x: NaN, y: NaN}))
initialData = data.map(d => ({...d, x: d3.randomNormal(centroid[0], 0)(), y: d3.randomNormal(centroid[1], 0)()}))
Insert cell
viewof keyframe = sim_scrubber(100);
Insert cell
initialData
Insert cell
data
Insert cell
height
Insert cell
chart2 = {
// let data2 = data.slice(0,300)
// "this" saves the output value of the block, and can be used
// to persist the DOM from one execution to the next.
const svg = d3.select(DOM.svg(width, height)),
data2 = initialData.map(d => Object.create(d));


///////Render inital points/////////////////////////

let node = svg.selectAll(".node")
.data(data2)
// .data(simulation.nodes())
let g = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x},${d.y})`);
g.append("path")
.attr('class','child-path')
.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", strokeWidth);

////////Simulation 1///////////////////////
const simulation1 = d3.forceSimulation(data2)
// .force("x", d3.forceX((d,i) => data[i].x))
// .force("y", d3.forceY((d,i) => data[i].y))
// .force("x", d3.forceX((d,i) => Math.random() * width))
// .force("y", d3.forceY((d,i) => Math.random() * height))
.force("collide", d3.forceCollide().radius(d => d.r * 1.3).strength(0.1))
.stop();

function ticked(){
tick++
// console.log(tick)

// if (tick == 1) {
// console.log(simulation1.nodes().map(d => d.y))
// }
d3.selectAll(".node")
.attr("transform", d => `translate(${d.x},${d.y})`);
d3.selectAll(".child-path")
.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);
});

// data2.forEach(d => {
// console.log(`Node at (${d.x}, ${d.y}) has force (${d.vx}, ${d.vy}) applied.`);
// });
simulation1.alphaDecay(0.05);
}

let tick = 0
function ticked2(){
// console.log(tick)

// console.log([d3.min(data2, d => d.y),d3.max(data2, d => d.y)])

// if (tick == 1) {
// console.log([simulation1.nodes()[0], data2[0], simulation2.nodes()[0]])
// }
d3.selectAll(".node")
.attr("transform", d => `translate(${d.x},${d.y})`);
d3.selectAll(".child-path")
.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);
});
// simulation2.nodes().map(d => {
// console.log(d.y);
// });
// simulation2.alphaDecay(0.05);
}

////////Simulation 2///////////////////////
const simulation2 = d3.forceSimulation(data2)
// .force('A', d3.forceY().y(function(d){
// // console.log([d, d.y]);
// console.log(data2[1]);
// return d.y - 5
// }))
// .force("test", d3.forceY(d => Math.random()*200))
// .force("y", function(){
// data2.map((d,i) => data2[i].y = data2[i].y - 1)
// })
// .force("x", d3.forceX((d,i) => data2[i].x+50))
// .force("y", d3.forceY((d,i) => data[i].y+50))
.alphaMin(0.01)
.force("surface", surface(data2))
.force("collideCell", colideCell(data2)) //how do adjust colideCell to push existing cells out the way more, like d3.forceCollide().
.force("tension", tension(data2))
.force("internal", internal(data2))
.force("expand", expand(data2))
// .force("y2", customY(data2))
// .force("upward", upwards(data2))
// .force('A', d3.forceY().strength(1).y(d => d.y - 5))
.stop();



function sim2tick(){
simulation2.on('tick', ticked2);
// console.log(simulation2.nodes().map(d => d.y));
simulation2.alpha(1).restart();
// simulation2.on('end', function(){return console.log(data2.map(d=>d.y))})
}


///////Execute simulation/////////////////

setTimeout(function(){
simulation1.on('tick', ticked);
simulation1.alpha(1).restart();
simulation1.on('end', function(){
// simulation1
// .force("x", null)
// .force("y", null)
// .force("collide", null)
// .alpha(0)
// .alphaTarget(0)
// .stop();
setTimeout(sim2tick, 1000);
});
}, 2000)


return svg.node();
}
Insert cell
// chart = {
// // let data2 = data.slice(0,300)
// // "this" saves the output value of the block, and can be used
// // to persist the DOM from one execution to the next.
// const svg = d3.select(DOM.svg(width, height)),
// // data2 = data.map(d => Object.create(d)),
// data2 = initialData.map(d => Object.create(d)),
// kf = keyframe;

// const simulation = d3.forceSimulation(data2)
// // .alphaMin(0.01)
// // .alphaDecay(0.01)
// // .force("fall", d3.forceManyBody().strength(-0.5))
// // .force("center", d3.forceCenter(width / 2, height / 2))
// .force("x", d3.forceX((d,i) => data[i].x))
// .force("y", d3.forceY((d,i) => data[i].y))
// .force("collide", d3.forceCollide().radius(d => d.r * 1.3).strength(0.1))
// // .force("surface", surface)
// // .force("collideCell", colideCell) //how do adjust colideCell to push existing cells out the way more, like d3.forceCollide().
// // .force("tension", tension)
// // .force("internal", internal)
// // .force("expand", expand)
// .stop()
// .tick(kf);
// // .alpha(0.01)

// // .force("charge", d3.forceManyBody().strength(-0.05))
// // .alphaDecay(0.001)
// // .velocityDecay(0.8);
// // .alphaTarget(0.5)
// // .force("shrink", shrink)
// // .force("link", d3.forceLink(dynamicData.links).id(d => d.id))
// // .force("center", d3.forceCenter(width / 2, height / 2))
// // .force("charge", d3.forceManyBody().strength(1))
// // .force("collide", d3.forceCollide().radius(function(d) {
// // return d.r * 1.2; // Set the radius for each node
// // }))
// // .force("charge", d3.forceManyBody().strength(0))
// // .force('y', d3.forceY().y(d => d.y))
// // .alphaDecay(0.0002)
// // .nodes(data2)
// // .on("tick", ticked);

// if (kf >= 10) {
// simulation.force("x", d3.forceX(0).strength(1))
// .force("y", d3.forceY(0).strength(1));
// // simulation.nodes().map(function(d){
// // d.x = -100; // Set fixed x-coordinate
// // d.y = -100; // Set fixed y-coordinate
// // // simulation.alpha(1).restart();
// // });
// }


// // Select the already existing nodes ("g") and edges ("line") if they exist
// // Set their data to the new, updated dynamicData
// let node = svg.selectAll(".node")
// .data(data2)
// // .data(simulation.nodes())

// let g = node.enter()
// .append("g")
// .attr("class", "node")
// .attr("transform", d => `translate(${d.x},${d.y})`);

// // g.append("circle")
// // .attr("r", d => d.r)
// // .style("fill", "#d9d9d9");


// g.append("path")
// .attr('class','child-path')
// .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", strokeWidth);


// // Create a text element for displaying alpha
// const alphaText = svg.append("text")
// .attr("id", "alphaText")
// .attr("x", 10)
// .attr("y", 20)
// .style("font-size", "14px")
// .style("fill", "black")
// .text(`Alpha: ${simulation.alpha().toFixed(3)}`);

// // simulation.on('tick', ticked);

// // setInterval(() => {
// // // simulation.tick();
// // // ticked();
// // // setTimeout(() => {
// // // let i = data2.length;
// // if (data2.length < 700) {
// // for (let i=0; i<5; i++){
// // let j = Math.round(Math.random() * (data.length-1))
// // data2.push(data[j]);
// // };
// // };

// // // console.log(data2.slice(-5))

// // node = node.data(data2);
// // simulation.nodes(data2);


// // // // Enter any new nodes
// // let nodeEnter = node.enter()
// // .append("g")
// // .attr('class', 'node');

// // nodeEnter.append("path")
// // .attr('class','child-path')
// // .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];
// // })
// // return line(data);
// // })
// // .attr("stroke","black")
// // .attr("stroke-width", strokeWidth);

// // // nodeEnter.append('circle')
// // // .style("fill", "#d9d9d9")
// // // .transition().ease(d3.easeElasticOut)
// // // .attr("r", d => d.r)


// // node = nodeEnter.merge(node);

// // // nodeEnter.exit().remove();

// // // console.log(simulation.alpha())
// // simulation.alpha(0.3).restart();
// // }, 50)

// // console.log(simulation.alpha())


// // update each node upon simulation tick
// // simulation.on("tick", () => {
// // d3.select("#alphaText").text(`Alpha: ${simulation.alpha().toFixed(3)}`);

// // svg.selectAll(".node")
// // // .attr("transform", d => `translate(${d.x},${d.y}) skewX(30)`)
// // .attr("transform", d => `translate(${d.x},${d.y})`);
// // // .attr("cx", d => d.x)
// // // .attr("cy", d => d.y);

// // svg.selectAll(".child-path")
// // .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);

// // });

// // })

// // merge the new edges with the existing edges
// // edges = edges.merge(enterEdges);
// // function ticked() {
// // // console.log(simulation.alpha())
// // // tick++
// // // console.log(tick)
// // // edges
// // // .attr("x1", d => d.source.x)
// // // .attr("y1", d => d.source.y)
// // // .attr("x2", d => d.target.x)
// // // .attr("y2", d => d.target.y);

// // // dynamicData.forEach(d => {
// // // // You can adjust the rate of movement by changing the factor (e.g., 0.05)
// // // d.targetY = d.targetY ? d.targetY - 0.5 : d.y;
// // // });

// // // simulation.force('y', d3.forceY().y(d => d.targetY));



// // });



// // };


// return svg.node();
// }
Insert cell
d3.forceCollide
Insert cell
internal = function(nodeData,alpha){
var f = 1 / 10;
nodeData.map(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;
});
})
}
Insert cell
upwards =
function(nodeData) {
nodeData.map(function(d) {
const y0 = d.y
d.y = d. y+ 10; // Shift each node upwards by 10 pixels
console.log([y0, d.y]);
});
}
Insert cell
customY = function(nodeData){
nodeData.forEach(d => d.y = d.y +5)
}

Insert cell
tension = function(nodeData){
nodeData.map(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 / 8; // spiky-ness
// var f = 1 / 10; // spiky-ness
t.length = (1 - f) * t.length + f * m / 2;
});
});
}
Insert cell
expand = function(nodeData){
nodeData.map(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);
})
})
}
Insert cell
colideCell = function(nodeData){
nodeData.map(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)];
});
});
var quadtree = d3.quadtree(nodeData, d=>d.x, d=>d.y);
var collisions = 0;
//Loop through the cells, and shrink the polygon where it overlaps with neighbours
nodeData.map(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;
// t.length /= 1.01;
t.length /= 1.08;
//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);
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])
}
})
})
})
};
Insert cell
Insert cell
surface = function(nodeData){
nodeData.map(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);
});
}
Insert cell
line = d3.radialLine().curve(d3.curveCatmullRomClosed)
Insert cell
import {data} from "8a0f66ecb094746a"
Insert cell
// dynamicData = {
// for (let i=3; i < data.length; i++) {
// data2.push(data[i]);
// node = node.data(data2)
// simulation.nodes(data2)
// yield Promises.tick(500, dataset)
// }
// return this
// }
Insert cell
dynamicData.nodes
Insert cell
// dynamicData = {
// let numeric_node_size2 = function(size) {
// switch(size) {
// case "small":
// return 5;
// case "medium":
// return 10;
// case "large":
// return 15;
// }
// }
// // If the data does not yet exist, initialize
// if (!this) {
// return {
// nodes: [],
// links: []
// }
// }
// let color = new_node.color || "black";
// let size = numeric_node_size2(new_node.size);
// let len = this.nodes.length

// // create new nodes when nodeColor activated
// if (this.nodes.length > 0) {
// //randomly select another node to connect with
// //let randomNode = this.nodes[Math.floor(Math.random() * len)];
// let randomNode = this.nodes[0];
// console.log(this.nodes.length);
// console.log("=> " + randomNode.id);
// this.nodes.push({id: len,
// color: color,
// r: size, // Math.floor(Math.random() * 10) + 5,
// x: Math.random() * width,
// y: Math.random()* height}) // x: randomNode.x, y: randomNode.y})
// this.links.push({source: randomNode.id,
// target: len,
// value: 1})
// } else {
// console.log("first node");
// this.nodes.push({id: len,
// r: size,
// color: color})
// }
// return this
// }
Insert cell
data
Insert cell
// simulation = {
// // If the simulation already exists, just alter the nodes, links, and restart
// if (this) {
// return this.nodes(data2)
// // .force("link", d3.forceLink(dynamicData.links).id(d => d.id))
// .restart()
// }
// // If the simulation doesn't yet exist, create it.
// return d3.forceSimulation(data2)
// // .force("link", d3.forceLink(dynamicData.links).id(d => d.id))
// .force("center", d3.forceCenter(width / 2, height / 2))
// // .force("charge", d3.forceManyBody().strength(-0.05))
// // .force("charge", d3.forceManyBody().strength(0))
// .force('y', d3.forceY().y(d => d.y))
// .restart()
// .alphaDecay(0.0002);
// }
Insert cell
Insert cell
drag = () => {
function dragstarted(d) {
console.log("started drag");
if (!d3.event.active) simulation.alphaTarget(0.0002).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
Insert cell
// d3 = require("d3@6")

Insert cell
import { text } from "@jashkenas/inputs"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more