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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more