// DGAC demo graphs: two variants // - "hetero": Texas/Wisc style — cross-cluster edges common (low homophily) // - "homo": Cora-style — mostly intra-cluster edges (high homophily) // Both use the SAME 20 nodes / 4 clusters / attribute positions, // only the edge set differs → demonstrates that DGAC handles both. function buildGraph(mode){ const clusters = [ { color: "oklch(0.62 0.14 25)", label: "C1", cx: 0.22, cy: 0.28 }, { color: "oklch(0.62 0.14 160)", label: "C2", cx: 0.78, cy: 0.28 }, { color: "oklch(0.62 0.14 260)", label: "C3", cx: 0.22, cy: 0.78 }, { color: "oklch(0.62 0.14 90)", label: "C4", cx: 0.78, cy: 0.78 }, ]; const nodes = []; const jitter = [ [-0.09,-0.06],[0.07,-0.08],[0.08,0.07],[-0.06,0.09],[0.00,-0.02], [-0.08,-0.05],[0.08,-0.06],[0.06,0.08],[-0.07,0.07],[0.01,0.00], [-0.07,-0.07],[0.08,-0.07],[0.06,0.08],[-0.08,0.06],[0.00,0.01], [-0.08,-0.04],[0.07,-0.08],[0.07,0.07],[-0.07,0.08],[0.00,-0.01], ]; for (let c=0;c<4;c++){ for (let i=0;i<5;i++){ const idx = c*5+i; nodes.push({ id: idx, cluster: c, color: clusters[c].color, ax: clusters[c].cx + jitter[idx][0]*0.6, ay: clusters[c].cy + jitter[idx][1]*0.6, }); } } // HETERO: many cross edges const heteroEdges = [ [0,1],[1,2],[2,3],[3,4],[0,2],[1,4], [5,6],[6,7],[7,8],[8,9],[5,7], [10,11],[11,12],[12,13],[13,14],[10,12],[11,14], [15,16],[16,17],[17,18],[18,19],[15,19],[16,18], [0,5],[2,7],[4,10],[3,15],[6,11],[9,14],[8,16], [12,17],[13,18],[5,15],[1,10],[7,19], ]; // HOMO: few cross edges, denser intra const homoEdges = [ [0,1],[1,2],[2,3],[3,4],[0,2],[1,4],[0,3],[2,4], [5,6],[6,7],[7,8],[8,9],[5,7],[5,9],[6,8],[7,9], [10,11],[11,12],[12,13],[13,14],[10,12],[11,14],[10,13],[12,14], [15,16],[16,17],[17,18],[18,19],[15,19],[16,18],[15,17],[17,19], // just a few bridges [4,5],[9,10],[14,15], ]; const edgeList = mode==="homo" ? homoEdges : heteroEdges; const pos = nodes.map(n => ({ x: clusters[n.cluster].cx, y: clusters[n.cluster].cy })); nodes.forEach((n, i) => { pos[i].x += jitter[i][0]; pos[i].y += jitter[i][1]; }); for (let iter=0; iter<12; iter++){ edgeList.forEach(([a,b]) => { const dx = pos[b].x - pos[a].x, dy = pos[b].y - pos[a].y; pos[a].x += dx*0.02; pos[a].y += dy*0.02; pos[b].x -= dx*0.02; pos[b].y -= dy*0.02; }); } nodes.forEach((n, i) => { n.tx = Math.max(0.06, Math.min(0.94, pos[i].x)); n.ty = Math.max(0.08, Math.min(0.92, pos[i].y)); }); const N = nodes.length; // homophily ratio const sameCount = edgeList.filter(([a,b])=>nodes[a].cluster===nodes[b].cluster).length; const homoRatio = sameCount / edgeList.length; return { nodes, edges: edgeList, clusters, N, homoRatio, mode }; } const DEMO_GRAPHS = { hetero: buildGraph("hetero"), homo: buildGraph("homo"), }; function initialKmeans(nodes, mode){ // hetero: more misassignments if relying on topology alone const miss = mode==="homo" ? new Set([7, 15]) // few errors : new Set([2, 7, 13, 15, 1, 18]); // more errors return nodes.map((n,i) => ({ pred: miss.has(i) ? (n.cluster + 1) % 4 : n.cluster, correct: !miss.has(i), })); } window.DEMO_GRAPHS = DEMO_GRAPHS; window.initialKmeans = initialKmeans;