viewof graph = {
const nodes = Array.from({length:5}, () => ({}));
const links = [
{ source: 0, target: 1 },
{ source: 0, target: 2 },
{ source: 0, target: 3 },
{ source: 0, target: 4 },
{ source: 1, target: 2 },
{ source: 1, target: 3 },
{ source: 1, target: 4 },
{ source: 2, target: 3 },
{ source: 2, target: 4 },
];
let mouse = null;
const svg = d3.create("svg")
.property("value", {nodes: [], links: []})
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("cursor", "crosshair")
.on("mouseleave", mouseleft)
.on("mousemove", mousemoved)
.on("click", clicked);
const simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink(links).distance(100))
.force("x", d3.forceX())
.force("y", d3.forceY())
.on("tick", ticked);
const dragger = drag(simulation)
.on("start.mouse", mouseleft)
.on("end.mouse", mousemoved);
let link = svg.append("g")
.attr("stroke", "#5c6166")
.attr("stroke-width", 2)
.selectAll("line");
let mouselink = svg.append("g")
.attr("stroke", "red")
.attr("opacity", 0.1)
.selectAll("line");
let node = svg.append("g")
.attr("stroke", "black")
.attr("fill", "#6699CC")
.selectAll("circle");
const cursor = svg.append("circle")
.attr("display", "none")
.attr("fill", "none")
.attr("stroke", "red")
.attr("opacity", 0.1)
.attr("r", mindistance - 5);
function ticked() {
node.attr("cx", d => d.x)
.attr("cy", d => d.y)
link.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
mouselink = mouselink
.data(mouse ? nodes.filter(node => inrange(mouse, node)) : [])
.join("line")
.attr("x1", mouse && mouse.x)
.attr("y1", mouse && mouse.y)
.attr("x2", d => d.x)
.attr("y2", d => d.y);
cursor
.attr("display", mouse ? null : "none")
.attr("cx", mouse && mouse.x)
.attr("cy", mouse && mouse.y);
}
function mouseleft() {
mouse = null;
}
function mousemoved(event) {
const [x, y] = d3.pointer(event);
mouse = {x, y};
//simulation.alpha(0.5).restart();
}
function clicked(event) {
mousemoved.call(this, event);
spawn({x: mouse.x, y: mouse.y});
}
// 2. Spawn function to create new vertex on click
function spawn(source) {
nodes.push(source);
for (const target of nodes) {
if (inrange(source, target)) {
links.push({source, target});
}
}
for(let i=links.length-1; i>=0; i--){
if(links[i].source.index === links[i].target.index){
links.splice(i, 1);
}
}
link = link
.data(links)
.join("line");
link.attr("class", d=> "source_" + d.source.index + " " + "target_" + d.target.index)
node = node
.data(nodes)
.join(
enter => enter.append("circle").attr("r", 0)
.call(enter => enter.transition().attr("r", 10))
.call(dragger),
update => update,
exit => exit.remove()
);
simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.alpha(1).restart();
svg.property("value", {
nodes: nodes.map(d => ({id: d.index})),
links: links.map(d => ({source: d.source.index, target: d.target.index}))
});
svg.dispatch("input");
}
// 3. Subdivide function to replace edge with path
function subdivide(source, target) {
simulation.stop();
const sourceObj = nodes.filter(el => el.index === source);
const targetObj = nodes.filter(el => el.index === target);
svg.append("circle").attr("r", 0)
.attr("stroke", "black")
.attr("fill", "#6699CC")
.attr("cx", 0.5 * (sourceObj[0].x + targetObj[0].x))
.attr("cy", 0.5 * (sourceObj[0].y + targetObj[0].y))
.transition()
.duration(1500)
.attr("r",10)
.remove()
setTimeout(()=> {
nodes.push({x: 0.5 * (sourceObj[0].x + targetObj[0].x) , y: 0.5 * (sourceObj[0].y + targetObj[0].y)});
node = node
.data(nodes)
.join(
enter => enter.append("circle").attr("r", 0)
.call(enter => enter.attr("r", 10))
.call(dragger),
update => update,
exit => exit.remove()
);
const newIndex = nodes.length - 1;
for(let i=links.length-1; i>=0; i--){
if(
(Math.min(links[i].source.index, links[i].target.index) === Math.min(source, target))
&& (Math.max(links[i].source.index, links[i].target.index) === Math.max(source, target))
){
links.splice(i, 1);
}
}
links.push({source: newIndex, target: target})
links.push({source: newIndex, target: source})
link = link
.data(links)
.join("line");
link.attr("class", d=> "source_" + d.source.index + " " + "target_" + d.target.index)
simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.alpha(0.1).restart();
},1500)
svg.property("value", {
nodes: nodes.map(d => ({id: d.index})),
links: links.map(d => ({source: d.source.index, target: d.target.index}))
});
svg.dispatch("input");
}
// 4. Contract function to contract edge
function contract(source, target) {
simulation.stop();
const sourceObj = nodes.filter(el => el.index === source);
const targetObj = nodes.filter(el => el.index === target);
svg.append("circle").attr("r", 10)
.attr("stroke", "black")
.attr("fill", "#6699CC")
.attr("cx", sourceObj[0].x)
.attr("cy", sourceObj[0].y)
.transition()
.duration(1000)
.attr("cx", targetObj[0].x)
.attr("cy", targetObj[0].y)
.remove()
svg.selectAll(".source_"+source)
.transition()
.duration(1000)
.attr("x1",targetObj[0].x + 1)
.attr("y1",targetObj[0].y + 1)
.remove()
svg.selectAll(".target_"+source)
.transition()
.duration(1000)
.attr("x2",targetObj[0].x)
.attr("y2",targetObj[0].y)
.remove()
setTimeout(()=> {
console.log(links)
for(let i=nodes.length-1; i>=0; i--){
if(nodes[i].index === +source){
nodes.splice(i, 1);
}
}
let newLinkIndex = links.length;
links.forEach(link => {
if(link.source.index === +source){
console.log("found it sou")
link.source.index = +target;
link.index = newLinkIndex;
newLinkIndex++;
}
if(link.target.index === +source){
console.log("found it tar")
link.target.index = +target;
link.index = newLinkIndex;
newLinkIndex++;
}
})
console.log(links)
console.log("nodes")
console.log(nodes)
node = node
.data(nodes)
.join(
enter => enter.append("circle").attr("r", 0)
.call(enter => enter.attr("r", 10))
.call(dragger),
update => update,
exit => exit.remove()
);
link = link
.data(links)
.join(enter => enter.append("line"),
update => update,
exit => exit.remove()
);
link.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
//simulation.nodes(nodes);
//simulation.force("link").links(links);
// simulation.alpha(0.5).restart();
},1000)
svg.property("value", {
nodes: nodes.map(d => ({id: d.index})),
links: links.map(d => ({source: d.source.index, target: d.target.index}))
});
svg.dispatch("input");
}
spawn({x: 1000, y: 1000})
//setTimeout(()=> {subdivide(3,0)}, 2000);
//setTimeout(()=> {subdivide(0,4)}, 6500);
invalidation.then(() => simulation.stop());
/*
setTimeout(()=> {contract(2,5)}, 3000);
setTimeout(()=> {subdivide(2,5)}, 4000);
*/
/*
setTimeout(() => {
simulation.stop()
node
.transition()
.duration(1000)
.attr("cx", d=> 20*d.index);
}, 10000);
*/
return svg.node();
}