|
125 | 125 |
|
126 | 126 | /* ---- treemap infographic ---- */ |
127 | 127 | .treemap{position:fixed;left:0;top:58px;right:0;bottom:66px;z-index:3;overflow:hidden} |
| 128 | +/* ---- mind-map (horizontal tree) ---- */ |
| 129 | +.mindmap{position:fixed;left:0;top:58px;right:0;bottom:66px;z-index:3;overflow:auto} |
| 130 | +.mindmap .mm-canvas{position:relative} |
| 131 | +.mindmap .mm-links{position:absolute;left:0;top:0;pointer-events:none} |
| 132 | +.mindmap .mm-node{ |
| 133 | + position:absolute;width:184px;height:20px;display:flex;align-items:center;gap:7px; |
| 134 | + padding:0 9px;border:1px solid var(--line);border-radius:11px;background:var(--glass); |
| 135 | + font:.72rem var(--mono);color:var(--ink);cursor:pointer;box-sizing:border-box; |
| 136 | + transition:filter .12s ease; |
| 137 | +} |
| 138 | +.mindmap .mm-node:hover{filter:brightness(1.4)} |
| 139 | +.mindmap .mm-root{ |
| 140 | + width:86px;justify-content:center;font-weight:700;color:#0a0e14; |
| 141 | + background:rgba(232,242,252,.96);border:none;box-shadow:0 0 16px rgba(120,200,255,.6); |
| 142 | +} |
| 143 | +.mindmap .mm-dot{width:8px;height:8px;border-radius:50%;flex:none} |
| 144 | +.mindmap .mm-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} |
| 145 | +.mindmap .mm-caret{ |
| 146 | + flex:none;min-width:15px;text-align:center;color:var(--soft);font-size:.64rem; |
| 147 | + background:rgba(255,255,255,.09);border-radius:7px;padding:0 4px; |
| 148 | +} |
| 149 | +.mindmap .mm-caret:hover{background:rgba(255,255,255,.2);color:var(--ink)} |
| 150 | +.mindmap::-webkit-scrollbar{width:9px;height:9px} |
| 151 | +.mindmap::-webkit-scrollbar-thumb{background:rgba(255,255,255,.12);border-radius:8px} |
128 | 152 | .treemap .tm-doc{position:absolute;border:1px solid rgba(255,255,255,.1);border-radius:8px;overflow:hidden} |
129 | 153 | .treemap .tm-h{ |
130 | 154 | position:absolute;left:0;top:0;right:0;height:21px;padding:0 8px; |
|
193 | 217 | <div class="vignette"></div> |
194 | 218 | <canvas id="c"></canvas> |
195 | 219 | <div class="treemap" id="treemap"></div> |
| 220 | +<div class="mindmap" id="mindmap"></div> |
196 | 221 |
|
197 | 222 | <div class="brand">openkb<small>knowledge graph</small></div> |
198 | 223 |
|
|
246 | 271 | r: 3.5 + Math.min(18, Math.sqrt((n.in || 0) + (n.out || 0)) * 2.1), // sqrt-scaled by degree → hubs read big, leaves stay small |
247 | 272 | col: colorOf(n.type), // cached color → no per-frame lookup |
248 | 273 | alpha: 1, // per-node fade (legend toggle) |
249 | | - sx:0, sy:0, rz:0, pp:1, hl:1, pinned:false, // projection cache + smoothed highlight + pinned (drag to pull out) |
250 | | - tx:0, ty:0, show:true, depth:0 // mind-map: radial target, visibility, layer |
| 274 | + sx:0, sy:0, rz:0, pp:1, hl:1, pinned:false // projection cache + smoothed highlight + pinned (drag to pull out) |
251 | 275 | })); |
252 | 276 | const byId = Object.fromEntries(nodes.map(n => [n.id, n])); |
253 | 277 | const edges = GRAPH.edges.filter(e => byId[e.source] && byId[e.target]); |
|
268 | 292 | let scale = DEF_SCALE, panX = 0, panY = 0; |
269 | 293 | let yaw = DEF_YAW, pitch = DEF_PITCH; // 3D orbit angles — drag the background to rotate |
270 | 294 | let orbiting = false, oStartX = 0, oStartY = 0, yawStart = 0, pitchStart = 0; |
271 | | -let panOrigX = 0, panOrigY = 0, pendingInspect = null; // mind-map: bg-drag pan + click-to-inspect |
272 | 295 | let didDrag = false; |
273 | 296 | let alpha = 1; // simulated-annealing "temperature"; cools to 0 so motion stops |
274 | 297 | const DEFAULT_SPREAD = 1.7; // open the nebula out by default so it isn't a clump |
275 | 298 | let spread = DEFAULT_SPREAD; // spacing knob: >1 spreads nodes apart, <1 packs them tighter |
276 | | -let mode = "mindmap"; // "mindmap" = OpenKB-rooted radial tree (default) · "graph" = 3D nebula · "treemap" = infographic |
277 | | -// synthetic centre node for the mind-map ("OpenKB" → documents → concepts) |
278 | | -const ROOT = { id:"__openkb__", label:"OpenKB", type:"__root__", x:0, y:0, z:0, tx:0, ty:0, |
279 | | - sx:0, sy:0, rz:0, pp:1, r:15, hl:1, alpha:1, depth:0, show:true }; |
280 | | -let treeEdges = []; // mind-map hierarchy edges (root→doc, doc→concept) |
| 299 | +let mode = "mindmap"; // "mindmap" = OpenKB-rooted horizontal tree (default) · "graph" = 3D nebula · "treemap" = infographic |
281 | 300 | let idleFrames = 0; // frames since last interaction → drives idle auto-rotation |
282 | 301 | let autoFit = true; // keep gently framing the cloud until the user takes control |
283 | 302 |
|
|
303 | 322 | }); |
304 | 323 | } |
305 | 324 |
|
306 | | -/* ===================== mind-map: OpenKB → documents → concepts (radial tree) ===================== */ |
307 | | -// concept/entity `sources` like "summaries/foo.md" → summary node id "summaries/foo" |
| 325 | +/* ===================== mind-map: OpenKB → documents → concepts (horizontal tree, DOM) ===================== */ |
| 326 | +const mmEl = document.getElementById("mindmap"); |
| 327 | +const mmCollapsed = new Set(); // node ids whose children are hidden |
| 328 | +let mmQuery = ""; |
308 | 329 | const mmResolveSrc = s => { const id = String(s).replace(/\.md$/i, ""); return (byId[id] && byId[id].id.startsWith("summaries/")) ? id : null; }; |
309 | | -function layoutMindmap(){ |
| 330 | +const ROOT_W = 86, NODE_W = 184, COL = 234, ROW = 27, PADX = 34, PADY = 28, NH = 20; |
| 331 | +function buildMindmap(){ |
| 332 | + const q = mmQuery.toLowerCase(); |
| 333 | + const keep = n => !q || n.label.toLowerCase().includes(q) || n.id.toLowerCase().includes(q); |
310 | 334 | const docs = nodes.filter(n => n.id.startsWith("summaries/") && typeVisible(n.type)); |
311 | 335 | const kidsOf = {}; docs.forEach(d => kidsOf[d.id] = []); |
312 | | - const rootDirect = []; // concepts with no resolvable document → hang off the centre |
| 336 | + const rootDirect = []; |
313 | 337 | nodes.forEach(n => { |
314 | | - if(n.id.startsWith("summaries/") || !typeVisible(n.type)){ n.show = false; return; } |
| 338 | + if(n.id.startsWith("summaries/") || !typeVisible(n.type)) return; |
315 | 339 | const p = (n.sources || []).map(mmResolveSrc).filter(Boolean)[0]; // primary document |
316 | 340 | if(p && kidsOf[p]) kidsOf[p].push(n); else rootDirect.push(n); |
317 | 341 | }); |
318 | | - nodes.forEach(n => { n.show = false; }); |
319 | | - const branches = docs.map(d => ({ node: d, kids: kidsOf[d.id], w: Math.max(1.6, kidsOf[d.id].length) })) |
320 | | - .concat(rootDirect.map(c => ({ node: c, kids: [], w: 1 }))); |
321 | | - const totW = branches.reduce((s, b) => s + b.w, 0) || 1; |
322 | | - const R1 = 250 * (0.6 + 0.4 * spread), R2 = R1 + 175 * (0.6 + 0.4 * spread); |
323 | | - treeEdges = []; |
324 | | - ROOT.show = true; ROOT.tx = 0; ROOT.ty = 0; |
325 | | - let a = -Math.PI; |
326 | | - branches.forEach(b => { |
327 | | - const wedge = 2 * Math.PI * b.w / totW, mid = a + wedge / 2; |
328 | | - b.node.show = true; b.node.depth = 1; |
329 | | - b.node.tx = Math.cos(mid) * R1; b.node.ty = Math.sin(mid) * R1; |
330 | | - treeEdges.push({ a: ROOT, b: b.node }); |
331 | | - b.kids.forEach((c, i) => { |
332 | | - const ca = a + wedge * (i + 0.5) / Math.max(1, b.kids.length); |
333 | | - c.show = true; c.depth = 2; |
334 | | - c.tx = Math.cos(ca) * R2; c.ty = Math.sin(ca) * R2; |
335 | | - treeEdges.push({ a: b.node, b: c }); |
336 | | - }); |
337 | | - a += wedge; |
| 342 | + const mk = n => ({ id:n.id, label:n.label, type:n.type, kids:[] }); |
| 343 | + const root = { id:"__openkb__", label:"OpenKB", type:"__root__", kids:[] }; |
| 344 | + docs.forEach(d => { |
| 345 | + const kids = kidsOf[d.id].filter(keep).map(mk); |
| 346 | + if(q && !keep(d) && !kids.length) return; // search prunes empty branches |
| 347 | + const dn = mk(d); dn.kids = kids; root.kids.push(dn); |
338 | 348 | }); |
339 | | -} |
340 | | -function stepMindmap(){ |
341 | | - for(const n of nodes){ |
342 | | - n.alpha += ((n.show && typeVisible(n.type) ? 1 : 0) - n.alpha) * 0.14; |
343 | | - if(n.show){ n.x += (n.tx - n.x) * 0.16; n.y += (n.ty - n.y) * 0.16; n.z += (0 - n.z) * 0.16; } |
344 | | - } |
345 | | - ROOT.x += (0 - ROOT.x) * 0.16; ROOT.y += (0 - ROOT.y) * 0.16; |
346 | | - alpha += (0 - alpha) * 0.02; |
| 349 | + rootDirect.filter(keep).forEach(c => root.kids.push(mk(c))); |
| 350 | + |
| 351 | + let rows = 0, maxDepth = 0; |
| 352 | + const flat = [], links = []; |
| 353 | + (function place(item, depth){ |
| 354 | + maxDepth = Math.max(maxDepth, depth); |
| 355 | + item.x = PADX + depth * COL; |
| 356 | + item.w = item.id === "__openkb__" ? ROOT_W : NODE_W; |
| 357 | + const expanded = item.kids.length && !mmCollapsed.has(item.id); |
| 358 | + if(expanded){ |
| 359 | + item.kids.forEach(k => place(k, depth + 1)); |
| 360 | + item.y = (item.kids[0].y + item.kids[item.kids.length - 1].y) / 2; |
| 361 | + item.kids.forEach(k => links.push([item, k])); |
| 362 | + } else { |
| 363 | + item.y = PADY + rows * ROW; rows++; |
| 364 | + } |
| 365 | + flat.push(item); |
| 366 | + })(root, 0); |
| 367 | + |
| 368 | + const Wpx = PADX + (maxDepth + 1) * COL + 30, Hpx = PADY + rows * ROW + 30; |
| 369 | + let svg = `<svg class="mm-links" width="${Wpx}" height="${Hpx}">`; |
| 370 | + links.forEach(([p, c]) => { |
| 371 | + const x1 = p.x + p.w, y1 = p.y + NH/2, x2 = c.x, y2 = c.y + NH/2, mx = (x1 + x2) / 2; |
| 372 | + svg += `<path d="M${x1},${y1} C${mx},${y1} ${mx},${y2} ${x2},${y2}" fill="none" stroke="rgba(255,255,255,.15)" stroke-width="1.3"/>`; |
| 373 | + }); |
| 374 | + svg += `</svg>`; |
| 375 | + let html = ""; |
| 376 | + flat.forEach(it => { |
| 377 | + if(it.id === "__openkb__"){ html += `<div class="mm-node mm-root" style="left:${it.x}px;top:${it.y}px">OpenKB</div>`; return; } |
| 378 | + const col = colorOf(it.type), kn = it.kids.length || (mmCollapsed.has(it.id) ? 1 : 0); |
| 379 | + const hasKids = it.id.startsWith("summaries/") && (kidsOf[it.id] || []).length; |
| 380 | + html += `<div class="mm-node" data-id="${esc(it.id)}" title="${esc(it.label)}" ` |
| 381 | + + `style="left:${it.x}px;top:${it.y}px;border-color:rgba(${col},.5);background:rgba(${col},.12)">` |
| 382 | + + `<span class="mm-dot" style="background:rgb(${col})"></span><span class="mm-label">${esc(it.label)}</span>` |
| 383 | + + (hasKids ? `<span class="mm-caret" data-tog="${esc(it.id)}">${mmCollapsed.has(it.id) ? "+" + (kidsOf[it.id] || []).length : "−"}</span>` : ``) |
| 384 | + + `</div>`; |
| 385 | + }); |
| 386 | + mmEl.innerHTML = `<div class="mm-canvas" style="width:${Wpx}px;height:${Hpx}px">${svg}${html}</div>`; |
| 387 | + mmEl.querySelectorAll("[data-tog]").forEach(el => el.onclick = e => { |
| 388 | + e.stopPropagation(); const id = el.dataset.tog; |
| 389 | + if(mmCollapsed.has(id)) mmCollapsed.delete(id); else mmCollapsed.add(id); |
| 390 | + buildMindmap(); |
| 391 | + }); |
| 392 | + mmEl.querySelectorAll(".mm-node[data-id]").forEach(el => el.onclick = () => { const m = byId[el.dataset.id]; if(m) openPanel(m); }); |
347 | 393 | } |
348 | 394 |
|
349 | 395 | /* ===================== physics (adapted from okf-spec.html step()) ===================== */ |
350 | 396 | const VMAX = 9; // per-frame speed cap → no violent swings, even on a dense hub |
351 | 397 | const alphaTarget = n => typeVisible(n.type) ? 1 : 0; |
352 | 398 | function step(){ |
353 | | - if(mode === "mindmap"){ stepMindmap(); return; } |
354 | 399 | for(let i=0;i<nodes.length;i++){ |
355 | 400 | const a = nodes[i]; |
356 | 401 | if(!visible(a)) continue; |
|
484 | 529 | const MODE_LABEL = { mindmap: "mind-map", graph: "3D nebula", treemap: "treemap" }; |
485 | 530 | function setMode(m){ |
486 | 531 | mode = m; |
487 | | - const tm = (m === "treemap"); |
488 | | - canvas.style.display = tm ? "none" : "block"; |
489 | | - tmEl.style.display = tm ? "block" : "none"; |
490 | | - spacingCtl.style.display = tm ? "none" : "flex"; |
| 532 | + const canvasMode = (m === "graph"); // only the 3D nebula uses the canvas; mind-map & treemap are DOM |
| 533 | + canvas.style.display = canvasMode ? "block" : "none"; |
| 534 | + tmEl.style.display = (m === "treemap") ? "block" : "none"; |
| 535 | + mmEl.style.display = (m === "mindmap") ? "block" : "none"; |
| 536 | + spacingCtl.style.display = canvasMode ? "flex" : "none"; |
491 | 537 | hover = null; closePanel(); panX = 0; panY = 0; autoFit = true; |
492 | 538 | if(m === "treemap"){ buildTreemap(); } |
493 | | - else if(m === "mindmap"){ yaw = 0; pitch = 0; scale = DEF_SCALE; layoutMindmap(); alpha = 1; } |
| 539 | + else if(m === "mindmap"){ buildMindmap(); } |
494 | 540 | else { yaw = DEF_YAW; pitch = DEF_PITCH; scale = DEF_SCALE; seed(); alpha = 1; } |
495 | 541 | modeBtn.textContent = MODE_LABEL[m]; |
496 | 542 | updateHint(); |
497 | 543 | } |
498 | 544 | function updateHint(){ |
499 | 545 | hintEl.innerHTML = |
500 | | - mode === "mindmap" ? 'centre <b>OpenKB</b> → documents → concepts faint lines = cross-refs <kbd>click</kbd> read <kbd>drag</kbd> pan <kbd>scroll</kbd> zoom' |
| 546 | + mode === "mindmap" ? 'OpenKB → documents → concepts <kbd>+/−</kbd> expand a branch <kbd>click</kbd> read details' |
501 | 547 | : mode === "treemap" ? 'each block = a document tiles = its concepts (size = connections) <kbd>click</kbd> read' |
502 | 548 | : '<kbd>scroll</kbd> zoom <kbd>drag bg</kbd> rotate <kbd>click</kbd> inspect<br>' |
503 | 549 | + '<kbd>drag node</kbd> pull out & pin <kbd>dbl-click</kbd> release'; |
|
580 | 626 | return best; |
581 | 627 | } |
582 | 628 |
|
583 | | -/* ===================== mind-map rendering ===================== */ |
584 | | -function drawCrossEdge(e){ // faint wikilink "cross-reference"; lights up on hover |
585 | | - const a = byId[e.source], b = byId[e.target]; |
586 | | - if(!a.show || !b.show || a.alpha < 0.05 || b.alpha < 0.05) return; |
587 | | - const hot = hover && (e.source === hover || e.target === hover); |
588 | | - ctx.globalAlpha = hot ? 0.85 : 0.05; |
589 | | - ctx.strokeStyle = hot ? "rgba(45,212,191,.9)" : "rgba(150,180,225,.6)"; |
590 | | - ctx.lineWidth = hot ? 1.4 : 0.6; |
591 | | - ctx.beginPath(); ctx.moveTo(a.sx, a.sy); ctx.lineTo(b.sx, b.sy); ctx.stroke(); |
592 | | - ctx.globalAlpha = 1; |
593 | | -} |
594 | | -function drawTreeEdge(e){ // bright hierarchy spoke (root→doc, doc→concept) |
595 | | - const a = e.a, b = e.b; |
596 | | - if(b.alpha < 0.05) return; |
597 | | - ctx.globalAlpha = 0.5 * (b.alpha || 1); |
598 | | - ctx.strokeStyle = "rgba(255,255,255,.34)"; |
599 | | - ctx.lineWidth = (a === ROOT) ? 1.7 : 1.0; |
600 | | - ctx.beginPath(); ctx.moveTo(a.sx, a.sy); ctx.lineTo(b.sx, b.sy); ctx.stroke(); |
601 | | - ctx.globalAlpha = 1; |
602 | | -} |
603 | | -function drawRoot(){ |
604 | | - const x = ROOT.sx, y = ROOT.sy, r = ROOT.r; |
605 | | - ctx.beginPath(); ctx.arc(x, y, r, 0, TAU); |
606 | | - ctx.fillStyle = "rgba(232,242,252,.96)"; |
607 | | - ctx.shadowColor = "rgba(120,200,255,.95)"; ctx.shadowBlur = 24; ctx.fill(); ctx.shadowBlur = 0; |
608 | | - ctx.fillStyle = "rgba(238,242,247,.98)"; ctx.font = "700 13px ui-monospace,monospace"; ctx.textAlign = "center"; |
609 | | - ctx.shadowColor = "rgba(4,6,10,.95)"; ctx.shadowBlur = 5; |
610 | | - ctx.fillText("OpenKB", x, y + r + 16); |
611 | | - ctx.shadowBlur = 0; |
612 | | -} |
613 | | - |
614 | 629 | /* ===================== rendering ===================== */ |
615 | 630 | function drawEdge(e, t){ |
616 | 631 | const a = byId[e.source], b = byId[e.target]; |
|
709 | 724 | } |
710 | 725 | // (flat discs — no glossy core highlight) |
711 | 726 | // label — declutter: hubs at rest; hovered node + neighbors; selection; zoomed in; near depth only |
712 | | - const showLabel = (mode === "mindmap" && n.depth <= 1) || (scale > 1.4) || (n.id === selected) || (hover ? hl > 0.55 : labelIds.has(n.id)); |
| 727 | + const showLabel = (scale > 1.4) || (n.id === selected) || (hover ? hl > 0.55 : labelIds.has(n.id)); |
713 | 728 | if(showLabel && fog > 0.55){ |
714 | 729 | ctx.fillStyle = `rgba(238,242,247,${0.4 + 0.55*hl})`; |
715 | 730 | ctx.font = `600 11px ui-monospace,monospace`; |
|
723 | 738 | } |
724 | 739 |
|
725 | 740 | function frame(t){ |
726 | | - if(mode === "mindmap"){ |
727 | | - if(alpha > 0.005) step(); |
728 | | - project(); // flat (yaw=pitch=0) |
729 | | - ROOT.pp = 1; ROOT.rz = 0; |
730 | | - ROOT.sx = W()/2 + panX + ROOT.x*scale; ROOT.sy = H()/2 + panY + ROOT.y*scale; |
731 | | - if(autoFit && !orbiting) fitStep(); |
732 | | - const focus = hover || selected; |
733 | | - for(const n of nodes){ |
734 | | - const near = !focus || n.id===focus || (adj[focus] && adj[focus].has(n.id)); |
735 | | - n.hl += ((near ? 1 : 0.5) - n.hl) * 0.12; // gentle dim — keep the whole tree readable |
736 | | - } |
737 | | - ctx.setTransform(DPR,0,0,DPR,0,0); ctx.clearRect(0,0,W(),H()); |
738 | | - edges.forEach(drawCrossEdge); // faint cross-references (+ hovered node's lit) |
739 | | - treeEdges.forEach(drawTreeEdge); // bright hierarchy |
740 | | - nodes.filter(visible).forEach(n => drawNode(n, t)); |
741 | | - drawRoot(); |
742 | | - } else if(mode === "graph"){ // treemap is DOM — the canvas idles |
| 741 | + if(mode === "graph"){ // mind-map & treemap are DOM — the canvas idles |
743 | 742 | if(alpha > 0.005) step(); |
744 | 743 | project(); |
745 | 744 | if(autoFit && !orbiting && !drag){ fitStep(); } // smoothly keep the cloud framed to the viewport |
|
771 | 770 | alpha = Math.max(alpha, 0.5); // reheat so neighbors re-settle around the dragged node |
772 | 771 | return; |
773 | 772 | } |
774 | | - if(orbiting){ |
775 | | - if(mode === "mindmap"){ // flat tree → drag background to pan |
776 | | - panX = panOrigX + (e.offsetX - oStartX); |
777 | | - panY = panOrigY + (e.offsetY - oStartY); |
778 | | - } else { // 3D → drag background to orbit |
779 | | - yaw = yawStart + (e.offsetX - oStartX) * 0.0045; |
780 | | - pitch = Math.max(-1.45, Math.min(1.45, pitchStart - (e.offsetY - oStartY) * 0.0045)); |
781 | | - } |
| 773 | + if(orbiting){ // 3D → drag background to orbit |
| 774 | + yaw = yawStart + (e.offsetX - oStartX) * 0.0045; |
| 775 | + pitch = Math.max(-1.45, Math.min(1.45, pitchStart - (e.offsetY - oStartY) * 0.0045)); |
782 | 776 | didDrag = true; return; |
783 | 777 | } |
784 | 778 | const n = pick(e.offsetX, e.offsetY); |
|
788 | 782 | const n = pick(e.offsetX, e.offsetY); |
789 | 783 | didDrag = false; idleFrames = 0; autoFit = false; // user takes control → stop auto-framing |
790 | 784 | oStartX = e.offsetX; oStartY = e.offsetY; |
791 | | - if(mode === "mindmap"){ // click a node → inspect; drag bg → pan |
792 | | - pendingInspect = n; orbiting = true; panOrigX = panX; panOrigY = panY; |
793 | | - canvas.style.cursor = "grabbing"; return; |
794 | | - } |
795 | 785 | if(n){ drag = n; hover = n.id; canvas.style.cursor = "grabbing"; } |
796 | 786 | else { orbiting = true; yawStart = yaw; pitchStart = pitch; canvas.style.cursor = "grabbing"; } |
797 | 787 | }); |
798 | 788 | window.addEventListener("mouseup", e => { |
799 | | - if(mode === "mindmap"){ |
800 | | - if(pendingInspect && !didDrag) openPanel(pendingInspect); |
801 | | - } else if(drag && !didDrag){ openPanel(drag); } // click (no drag) → inspect |
| 789 | + if(drag && !didDrag){ openPanel(drag); } // click (no drag) → inspect |
802 | 790 | else if(drag && didDrag){ drag.pinned = true; alpha = Math.max(alpha, 0.5); } // pulled out → pin it |
803 | | - drag = null; orbiting = false; pendingInspect = null; |
| 791 | + drag = null; orbiting = false; |
804 | 792 | canvas.style.cursor = "grab"; |
805 | 793 | }); |
806 | 794 | canvas.addEventListener("dblclick", e => { |
|
857 | 845 | row.classList.toggle("off"); |
858 | 846 | if(hiddenTypes.has(t)) hiddenTypes.delete(t); else hiddenTypes.add(t); |
859 | 847 | if(mode === "treemap") buildTreemap(); // rebuild without the hidden type |
| 848 | + else if(mode === "mindmap") buildMindmap(); |
860 | 849 | else alpha = Math.max(alpha, 0.4); // graph: reheat so the layout re-settles |
861 | 850 | }; |
862 | 851 | legend.appendChild(row); |
|
866 | 855 | search.addEventListener("input", e => { |
867 | 856 | const raw = e.target.value.trim(); |
868 | 857 | if(mode === "treemap"){ tmQuery = raw; buildTreemap(); return; } // filter tiles |
| 858 | + if(mode === "mindmap"){ mmQuery = raw; buildMindmap(); return; } // filter branches |
869 | 859 | const q = raw.toLowerCase(); |
870 | 860 | if(!q){ hover = null; return; } |
871 | 861 | const m = nodes.find(n => n.label.toLowerCase().includes(q)) |
|
892 | 882 | hover = null; closePanel(); |
893 | 883 | if(mode === "treemap"){ |
894 | 884 | tmQuery = ""; search.value = ""; buildTreemap(); |
| 885 | + } else if(mode === "mindmap"){ |
| 886 | + mmQuery = ""; search.value = ""; mmCollapsed.clear(); collapseDocs(); buildMindmap(); mmEl.scrollTop = 0; mmEl.scrollLeft = 0; |
895 | 887 | } else { |
896 | 888 | spread = DEFAULT_SPREAD; spreadEl.value = String(DEFAULT_SPREAD); panX = 0; panY = 0; |
897 | 889 | scale = DEF_SCALE; yaw = DEF_YAW; pitch = DEF_PITCH; |
|
901 | 893 | }); |
902 | 894 |
|
903 | 895 | /* ===================== boot ===================== */ |
| 896 | +function collapseDocs(){ nodes.forEach(n => { if(n.id.startsWith("summaries/")) mmCollapsed.add(n.id); }); } // start compact: docs folded |
904 | 897 | window.addEventListener("resize", () => { size(); autoFit = true; if(mode === "treemap") buildTreemap(); }); |
905 | | -size(); seed(); |
| 898 | +size(); seed(); collapseDocs(); |
906 | 899 | setMode("mindmap"); // open on the OpenKB-rooted mind-map |
907 | 900 | requestAnimationFrame(frame); |
908 | 901 | </script> |
|
0 commit comments