Public
Edited
Sep 16, 2023
2 forks
Insert cell
Insert cell
graph
Insert cell
viewof graph = {
// 1. Define initial vertices and edges

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")
//.on("mouseleave", mouseleft)
//.on("mousemove", mousemoved)
//.on("click", clicked);

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();
}
Insert cell
inrange = ({x: sx, y: sy}, {x: tx, y: ty}) => Math.hypot(sx - tx, sy - ty) <= mindistance
Insert cell
mindistance = 120
Insert cell
height = 500
Insert cell
K5 = {
const K5 = new Object;
K5.nodes = [
{x: 0, y: -80},
{x: 80 * Math.sin(6.28/5), y: -80 * Math.cos(6.28/5)},
{x: 80 * Math.sin(2*6.28/5), y: -80 * Math.cos(2* 6.28/5)},
{x: 80 * Math.sin(3*6.28/5), y: -80 * Math.cos(3* 6.28/5)},
{x: 80 * Math.sin(4*6.28/5), y: -80 * Math.cos(4* 6.28/5)}
];
K5.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 },
{ source: 3, target: 4 }
];
return K5;

}

/*
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);
*/
Insert cell

/* Graph containing K3,3 as minor
const nodes = [
{x: 130 * Math.sin(-6.28/6), y: -130 * Math.cos(-6.28/6)},
{x: 130 * Math.sin(6.28/6), y: -130 * Math.cos(6.28/6)},
{x: 130 * Math.sin(2*6.28/6), y: -130 * Math.cos(2* 6.28/6)},
{x: 130 * Math.sin(3*6.28/6), y: -130 * Math.cos(3* 6.28/6)},
{x: 130 * Math.sin(4*6.28/6), y: -130 * Math.cos(4* 6.28/6)},
{x: 40, y: 0},
{x: -40, y: 0},
{x: 130 * Math.sin(0), y: -130 * Math.cos(0)}
];
const links = [
{ source: 0, target: 7 },
{ source: 0, target: 4 },
{ source: 6, target: 5 },
{ source: 0, target: 5 },
{ source: 1, target: 2 },
{ source: 1, target: 7 },
{ source: 1, target: 6 },
{ source: 6, target: 3 },
{ source: 2, target: 3 },
{ source: 3, target: 4 },
{ source: 6, target: 4 },
{ source: 2, target: 5 },
{ source: 3, target: 5 },
];
*/

/*

setTimeout(()=> {contract(7,1)}, 3000);
setTimeout(()=> {contract(4,3)}, 5000);
setTimeout(()=> {deleteEdge(0,6)}, 7000);
setTimeout(()=> {deleteEdge(3,5)}, 8000);
setTimeout(()=> {
nodes[1].x = 60
nodes[1].y = -80
nodes[5].x = 60
nodes[5].y = 0
nodes[3].x = 60
nodes[3].y = 80
nodes[0].x = -60
nodes[0].y = -80
nodes[2].x = -60
nodes[2].y = 80
nodes[6].x = -60
nodes[6].y = 0
updateNodes();
updateLinks();
},9000);
*/
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