viewof graph = {
const nodes = K5.nodes;
const links = K5.links;
const svg = d3.create("svg")
.property("value", {nodes: [], links: []})
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("cursor", "crosshair")
let link = svg.selectAll("line")
function updateLinks(){
links.forEach(link => {
link.sourceX = nodes[link.source].x;
link.sourceY = nodes[link.source].y;
link.targetX = nodes[link.target].x;
link.targetY = nodes[link.target].y;
});
link = link.data(links)
.join(enter => enter.append("line")
.attr("stroke", "#5c6166")
.attr("stroke-width", 2)
.attr("x1", d => d.sourceX)
.attr("y1", d => d.sourceY)
.attr("x2", d => d.targetX)
.attr("y2", d => d.targetY),
update => update
.transition()
.duration(1000)
.attr("x1", d => d.sourceX)
.attr("y1", d => d.sourceY)
.attr("x2", d => d.targetX)
.attr("y2", d => d.targetY)
.style("display", d=> d.removed ? "none" : "block"),
exit => exit.remove()
);
}
updateLinks();
let node = svg.selectAll("circle");
let clickedNodes = [];
function updateNodes(){
let nodeClick = (e,d) => {
let i = e.target.id.split("v_")[1];
e.target.style.stroke = "yellow";
e.target.style.strokeWidth = 2;
clickedNodes.push(+i);
if(clickedNodes.length === 2){
for(let l=0; l<links.length; l++){
if((links[l].source === clickedNodes[0] && links[l].target === clickedNodes[1])
|| (links[l].source === clickedNodes[1] && links[l].target === clickedNodes[0])){
contract(clickedNodes[0], clickedNodes[1]);
clickedNodes = [];
break;
}
if(l === links.length - 1){
e.target.style.stroke = "red";
setTimeout(()=>{
clickedNodes = [];
updateNodes();
},800);
}
}
}
}
node = node
.data(nodes)
.join(
enter => enter.append("circle")
.attr("stroke", "black")
.attr("fill", "#6699FF")
.attr("r", 0)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("id", (d,i) => "v_"+i)
.on("click", (e,d) => nodeClick(e,d))
.call(enter => enter.transition().attr("r", 10)),
//.call(dragger),
update => update.transition()
.duration(1000)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.style("stroke", "black")
.style("stroke-width", 1),
exit => exit.remove()
);
}
updateNodes();
function clicked(event) {
spawn({x: d3.pointer(event)[0], y: d3.pointer(event)[1]});
}
// 2. Function to create new node on click
function spawn(source) {
nodes.push(source);
updateNodes();
}
// 3. Function to add edge
function addEdge(source, target){
links.push({source: source, target: target, sourceX: nodes[source].x, sourceY: nodes[source].y, targetX: nodes[target].x, targetY: nodes[target].y });
updateLinks();
node.raise();
}
// 4. Function to delete edge
function deleteEdge(source, target){
links.forEach(link => {
if(Math.min(link.source, link.target) === Math.min(source, target)
&& Math.max(link.source, link.target) === Math.max(source, target)){
link.removed = true;
}
})
updateLinks();
node.raise();
}
// 4. Subdivide function to replace edge with path
function subdivide(source, target) {
nodes.push({x: 1.1 * (0.5 * (nodes[source].x + nodes[target].x)) , y: 1.1 * (0.5 * (nodes[source].y + nodes[target].y))});
updateNodes();
const newIndex = nodes.length - 1;
links.forEach(link => {
if(Math.min(link.source, link.target) === Math.min(source, target)
&& Math.max(link.source, link.target) === Math.max(source, target)){
link.removed = true;
}
})
links.push({source: newIndex, target: target})
links.push({source: newIndex, target: source})
updateLinks();
node.raise();
}
// 5. Function to contract edge
const removedNodes = [];
function contract(source, target) {
nodes[source].x = nodes[target].x;
nodes[source].y = nodes[target].y;
updateNodes();
links.forEach(link => {
if(link.source === +source){
link.source = +target;
}
if(link.target === +source){
link.target = +target;
}
})
updateLinks();
node.raise();
setTimeout(()=>{
removedNodes.push(source)
node
.attr("class", d => "")
.style("display", (d,i) => removedNodes.includes(i) ? "none" : "block");
},1000);
}
// 6. Reposition nodes and edges
/*
setTimeout(()=> {
nodes[0].x = 200
nodes[0].y = 80
updateNodes();
updateLinks();
},2000);
*/
//7. Buttons to select operation
let operation = "contract"
svg.append("rect")
.attr("x", width/2 - 270)
.attr("y",-height/2 + 15)
.attr("rx",2)
.attr("width", 120)
.attr("height", 30)
.attr("fill", "lightblue")
.attr("stroke", "blue")
.on("click", () => {operation = "contract"})
svg.append("text")
.attr("x", width/2 - 210)
.attr("y",-height/2 + 35)
.attr("text-anchor", "middle")
.text("contract edge")
svg.append("rect")
.attr("x", width/2 - 140)
.attr("y",-height/2 + 15)
.attr("rx",2)
.attr("width", 120)
.attr("height", 30)
.attr("fill", "lightblue")
.attr("stroke", "blue")
.on("click", () => {operation = "delete"})
svg.append("text")
.attr("x", width/2 - 80)
.attr("y",-height/2 + 35)
.attr("text-anchor", "middle")
.text("delete edge")
const createEdge = () =>{
let selectionMade = false;
while(selectionMade === false){
let randLink = links[Math.floor(Math.random()*links.length)];
if(!randLink.removed){
selectionMade = true;
subdivide(randLink.source,randLink.target);
console.log(nodes)
console.log(links)
addEdge(nodes.length - 1, Math.floor(Math.random()*nodes.length));
}
}
}
setTimeout(()=> {subdivide(2,4)}, 2000);
setTimeout(()=> {subdivide(1,3)}, 3000);
setTimeout(()=> {
nodes[5].x = -100
nodes[5].y = 120
nodes[6].x = 100
nodes[6].y = 120
updateNodes();
updateLinks();
},4000);
setTimeout(()=> {subdivide(1,4)}, 6000);
setTimeout(()=> {subdivide(1,7)}, 7000);
setTimeout(()=> {
nodes[7].x = -150
nodes[7].y = 170
nodes[8].x = 150
nodes[8].y = 170
nodes[0].x = 0
nodes[0].y = 0
updateNodes();
updateLinks();
},8000);
setTimeout(()=> {
spawn({x: 0, y: 120});
addEdge(7, 9)
addEdge(8, 9)
}, 9000);
/*
for(let i=0; i<6; i++){
setTimeout(()=> {
createEdge();
}, 1000+1200*i);
}
*/
/*
setTimeout(()=> {
nodes.forEach((node,i) => {
node.x = 100 * Math.cos((2*Math.PI*i)/(nodes.length) )
node.y = 100 * Math.sin((2*Math.PI*i)/(nodes.length) )
})
updateLinks();
updateNodes();
}, 1000+1200*11);
*/
// setTimeout(()=> {subdivide(2,5)}, 8000);
//setTimeout(()=> {subdivide(1,3)}, 6000);
return svg.node();
}