Show code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Kruskal's MST</title>
<style>
html,body{margin:0;height:100%;background:#080b11;overflow:hidden}
canvas{display:block}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv=document.getElementById('c'),g=cv.getContext('2d'),N=25,D=115;
let w,h,dpr,nodes,edges,par,siz,hue,idx,acc,tot,t0,done;
const ease=p=>p*p*(3-2*p),la=(a,b,k)=>a+((((b-a)%360)+540)%360-180)*k;
function resize(){dpr=Math.min(devicePixelRatio||1,2);w=innerWidth;h=innerHeight;cv.width=w*dpr;cv.height=h*dpr;g.setTransform(dpr,0,0,dpr,0,0);}
function find(i){return par[i]===i?i:par[i]=find(par[i]);}
function gen(){
const m=68,ty=Math.min(120,h*0.22);nodes=[];
for(let i=0;i<N;i++){let p,k=0;
do{p={x:m+Math.random()*Math.max(20,w-2*m),y:ty+Math.random()*Math.max(20,h-ty-m)};k++;}
while(k<300&&nodes.some(q=>Math.hypot(q.x-p.x,q.y-p.y)<Math.min(w,h)*0.105));
p.hu=i*360/N;p.th=p.hu;nodes.push(p);}
const all=[];
for(let i=0;i<N;i++)for(let j=i+1;j<N;j++)all.push({a:i,b:j,w:Math.round(Math.hypot(nodes[i].x-nodes[j].x,nodes[i].y-nodes[j].y)/6),s:0});
all.sort((p,q)=>p.w-q.w||p.a-q.a);
const up=[...Array(N).keys()],f=i=>up[i]===i?i:up[i]=f(up[i]),keep=new Set();
for(const e of all){const ra=f(e.a),rb=f(e.b);if(ra!==rb){up[ra]=rb;keep.add(e.a+'-'+e.b);}}
const lim=all[Math.min(all.length,70)-1].w;
edges=all.filter(e=>e.w<=lim||keep.has(e.a+'-'+e.b));
par=[...Array(N).keys()];siz=Array(N).fill(1);hue=nodes.map(n=>n.hu);
idx=0;acc=0;tot=0;done=0;
}
function commit(){
const e=edges[idx++],ra=find(e.a),rb=find(e.b);
if(ra===rb){e.s=2;return;}
let A=ra,B=rb;if(siz[A]<siz[B]){A=rb;B=ra;}
par[B]=A;siz[A]+=siz[B];e.s=1;acc++;tot+=e.w;
}
function draw(p){
g.fillStyle='#080b11';g.fillRect(0,0,w,h);
g.lineCap='round';
for(const e of edges){
if(e.s===0)continue;
const A=nodes[e.a],B=nodes[e.b];
g.strokeStyle=e.s===1?'rgba(64,230,140,.9)':'rgba(255,72,96,.32)';
g.lineWidth=e.s===1?2.6:1.2;
if(e.s===1){g.shadowColor='rgba(64,230,140,.75)';g.shadowBlur=10;}
g.beginPath();g.moveTo(A.x,A.y);g.lineTo(B.x,B.y);g.stroke();g.shadowBlur=0;
}
g.strokeStyle='rgba(150,180,220,.09)';g.lineWidth=1;
for(let i=idx;i<edges.length;i++){const e=edges[i],A=nodes[e.a],B=nodes[e.b];
g.beginPath();g.moveTo(A.x,A.y);g.lineTo(B.x,B.y);g.stroke();}
if(idx<edges.length&&!done){
const e=edges[idx],A=nodes[e.a],B=nodes[e.b],k=ease(p),cyc=find(e.a)===find(e.b);
g.strokeStyle=cyc?'rgba(255,120,140,.95)':'rgba(255,255,255,.95)';g.lineWidth=3.4;
g.shadowColor=cyc?'rgba(255,80,110,.8)':'rgba(255,255,255,.6)';g.shadowBlur=16;
g.beginPath();g.moveTo(A.x,A.y);g.lineTo(A.x+(B.x-A.x)*k,A.y+(B.y-A.y)*k);g.stroke();g.shadowBlur=0;
g.fillStyle='#fff';g.beginPath();g.arc(A.x+(B.x-A.x)*k,A.y+(B.y-A.y)*k,3.2,0,7);g.fill();
g.font='12px ui-monospace,monospace';g.fillStyle='rgba(230,240,255,.85)';g.textAlign='center';
g.fillText(e.w,(A.x+B.x)/2,(A.y+B.y)/2-8);
}
for(let i=0;i<N;i++){
const n=nodes[i];n.th=hue[find(i)];n.hu=la(n.hu,n.th,.12);
g.fillStyle='hsl('+n.hu.toFixed(0)+',72%,58%)';
g.shadowColor='hsl('+n.hu.toFixed(0)+',80%,50%)';g.shadowBlur=14;
g.beginPath();g.arc(n.x,n.y,9,0,7);g.fill();g.shadowBlur=0;
g.strokeStyle='rgba(8,11,17,.9)';g.lineWidth=2;g.stroke();
}
const comps=new Set();for(let i=0;i<N;i++)comps.add(find(i));
g.textAlign='left';
g.font='600 20px ui-sans-serif,system-ui,sans-serif';g.fillStyle='#eaf2ff';
g.fillText("Kruskal's Minimum Spanning Tree", 28, 40);
g.font='13px ui-monospace,monospace';g.fillStyle='rgba(190,210,235,.85)';
g.fillText('edge '+Math.min(idx+1,edges.length)+' / '+edges.length+' nodes 25 components '+comps.size, 28, 62);
g.fillStyle='rgba(64,230,140,.95)';g.fillText('accepted '+acc+' / '+(N-1), 28, 84);
g.fillStyle='rgba(255,200,120,.95)';g.fillText('total weight '+tot, 190, 84);
if(done){g.fillStyle='rgba(255,255,255,.75)';g.fillText('MST complete — restarting…', 400, 84);}
const bw=Math.min(360,w-56);
g.fillStyle='rgba(255,255,255,.08)';g.fillRect(28,96,bw,4);
g.fillStyle='rgba(64,230,140,.8)';g.fillRect(28,96,bw*(idx/edges.length),4);
}
function loop(now){
if(t0===undefined)t0=now;
if(done){if(now-done>1700){gen();t0=now;}}
else{
while(now-t0>=D&&idx<edges.length){commit();t0+=D;}
if(idx>=edges.length)done=now;
}
draw(idx<edges.length&&!done?Math.min(1,(now-t0)/D):0);
requestAnimationFrame(loop);
}
addEventListener('resize',()=>{resize();gen();t0=undefined;});
resize();gen();requestAnimationFrame(loop);
</script>
</body>
</html>