Skip to content

Commit 03369bb

Browse files
committed
feat(visualize): drop treemap, default to 3D nebula
Per request: remove the treemap view (squarify, buildTreemap, its CSS/DOM, and all its branches) and make the 3D nebula the default. Tab bar is now [mind-map · radial · 3D].
1 parent dd9dc18 commit 03369bb

1 file changed

Lines changed: 11 additions & 127 deletions

File tree

openkb/templates/graph.html

Lines changed: 11 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,6 @@
131131
.controls .tabs button:hover{color:var(--ink)}
132132
.controls .tabs button.on{background:rgba(56,189,248,.22);color:#bfe6ff}
133133

134-
/* ---- treemap infographic ---- */
135-
.treemap{position:fixed;left:0;top:58px;right:0;bottom:66px;z-index:3;overflow:hidden}
136134
/* ---- mind-map (horizontal tree) ---- */
137135
.mindmap{position:fixed;left:0;top:58px;right:0;bottom:66px;z-index:3;overflow:auto}
138136
.mindmap .mm-canvas{position:relative}
@@ -157,22 +155,6 @@
157155
.mindmap .mm-caret:hover{background:rgba(255,255,255,.2);color:var(--ink)}
158156
.mindmap::-webkit-scrollbar{width:9px;height:9px}
159157
.mindmap::-webkit-scrollbar-thumb{background:rgba(255,255,255,.12);border-radius:8px}
160-
.treemap .tm-doc{position:absolute;border:1px solid rgba(255,255,255,.1);border-radius:8px;overflow:hidden}
161-
.treemap .tm-h{
162-
position:absolute;left:0;top:0;right:0;height:21px;padding:0 8px;
163-
font:600 .62rem/21px var(--mono);letter-spacing:.04em;color:var(--soft);
164-
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;
165-
background:rgba(255,255,255,.04);border-bottom:1px solid rgba(255,255,255,.07);
166-
}
167-
.treemap .tm-h:hover{color:var(--ink);background:rgba(255,255,255,.08)}
168-
.treemap .tm-t{
169-
position:absolute;border:1px solid;border-radius:5px;cursor:pointer;overflow:hidden;
170-
display:flex;align-items:center;justify-content:center;text-align:center;
171-
transition:filter .12s ease,transform .12s ease;
172-
}
173-
.treemap .tm-t:hover{filter:brightness(1.5);z-index:2}
174-
.treemap .tm-t span{font:.66rem/1.15 var(--mono);padding:2px 4px;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word}
175-
.treemap .tm-empty{position:absolute;left:30px;top:30px;color:var(--mut);font:.8rem var(--mono)}
176158

177159
/* ---- glass inspector panel ---- */
178160
.panel{
@@ -224,7 +206,6 @@
224206
<div class="grain"></div>
225207
<div class="vignette"></div>
226208
<canvas id="c"></canvas>
227-
<div class="treemap" id="treemap"></div>
228209
<div class="mindmap" id="mindmap"></div>
229210

230211
<div class="brand">openkb<small>knowledge graph</small></div>
@@ -242,7 +223,7 @@
242223

243224
<div class="controls">
244225
<div class="tabs" id="modeTabs">
245-
<button data-m="mindmap">mind-map</button><button data-m="radial">radial</button><button data-m="graph">3D</button><button data-m="treemap">treemap</button>
226+
<button data-m="mindmap">mind-map</button><button data-m="radial">radial</button><button data-m="graph">3D</button>
246227
</div>
247228
<span class="sepc"></span>
248229
<label id="spacingCtl">spacing <input id="spread" type="range" min="0.5" max="3.5" step="0.1" value="1.7"></label>
@@ -312,7 +293,7 @@
312293
let treeEdges = []; // radial hierarchy edges (root→doc, doc→concept)
313294
const DEFAULT_SPREAD = 1.7; // open the nebula out by default so it isn't a clump
314295
let spread = DEFAULT_SPREAD; // spacing knob: >1 spreads nodes apart, <1 packs them tighter
315-
let mode = "mindmap"; // "mindmap" = OpenKB-rooted horizontal tree (default) · "graph" = 3D nebula · "treemap" = infographic
296+
let mode = "graph"; // "graph" = 3D nebula (default) · "mindmap" = horizontal tree · "radial" = OpenKB-centred circle
316297
let idleFrames = 0; // frames since last interaction → drives idle auto-rotation
317298
let autoFit = true; // keep gently framing the cloud until the user takes control
318299

@@ -490,108 +471,16 @@
490471
alpha += (0 - alpha) * 0.02; // anneal: forces fade → the layout settles and stops
491472
}
492473

493-
/* ===================== provenance tree (document → summary → concept/entity) ===================== */
494-
// The wiki's real hierarchy lives in the frontmatter: each summary's `sources` is its origin
495-
// document (full_text), and each concept/entity's `sources` are the summaries it was drawn from.
496-
// Treemap: documents as blocks (area ∝ their concepts' total connectivity), concepts tiled inside
497-
// (tile area ∝ connectivity), coloured by type. An at-a-glance infographic; click a tile to read it.
498-
const tmEl = document.getElementById("treemap");
499474
const spacingCtl = document.getElementById("spacingCtl");
500-
let tmQuery = "";
501-
const tmResolveSrc = s => { // "summaries/foo.md" → summary node id "summaries/foo"
502-
const id = String(s).replace(/\.md$/i, "");
503-
return (byId[id] && byId[id].id.startsWith("summaries/")) ? id : null;
504-
};
505-
const tmWeight = n => 1 + (n.in || 0) + (n.out || 0); // bigger = more connected
506-
507-
// Squarified treemap (Bruls et al.): pack weighted items into a rect with near-square tiles.
508-
function squarify(items, x, y, w, h){
509-
const out = [];
510-
const ns = items.filter(it => it.w > 0).map(it => ({ it, area: 0 }));
511-
if(!ns.length || w <= 0 || h <= 0) return out;
512-
const total = ns.reduce((s, n) => s + n.it.w, 0);
513-
const k = (w * h) / total;
514-
ns.forEach(n => n.area = n.it.w * k);
515-
ns.sort((a, b) => b.area - a.area);
516-
const rect = { x, y, w, h };
517-
let row = [];
518-
const worst = (rw, len) => {
519-
if(!rw.length) return Infinity;
520-
const sum = rw.reduce((s, n) => s + n.area, 0);
521-
const mx = Math.max(...rw.map(n => n.area)), mn = Math.min(...rw.map(n => n.area));
522-
return Math.max((len * len * mx) / (sum * sum), (sum * sum) / (len * len * mn));
523-
};
524-
const flush = () => {
525-
const sum = row.reduce((s, n) => s + n.area, 0);
526-
if(rect.w >= rect.h){
527-
const cw = sum / rect.h; let yy = rect.y;
528-
row.forEach(n => { const hh = n.area / cw; out.push({ ...n.it, X: rect.x, Y: yy, W: cw, H: hh }); yy += hh; });
529-
rect.x += cw; rect.w -= cw;
530-
} else {
531-
const rh = sum / rect.w; let xx = rect.x;
532-
row.forEach(n => { const ww = n.area / rh; out.push({ ...n.it, X: xx, Y: rect.y, W: ww, H: rh }); xx += ww; });
533-
rect.y += rh; rect.h -= rh;
534-
}
535-
row = [];
536-
};
537-
ns.forEach(n => {
538-
const len = Math.min(rect.w, rect.h);
539-
if(row.length && worst([...row, n], len) > worst(row, len)) flush();
540-
row.push(n);
541-
});
542-
if(row.length) flush();
543-
return out;
544-
}
545-
546-
function buildTreemap(){
547-
const q = tmQuery.toLowerCase();
548-
const childrenOf = {}, sums = [];
549-
nodes.forEach(n => { if(n.id.startsWith("summaries/")){ childrenOf[n.id] = []; sums.push(n); } });
550-
nodes.forEach(n => {
551-
if(n.id.startsWith("summaries/") || !typeVisible(n.type)) return;
552-
[...new Set((n.sources || []).map(tmResolveSrc).filter(Boolean))]
553-
.forEach(p => { if(childrenOf[p]) childrenOf[p].push(n); });
554-
});
555-
const docItems = sums.filter(s => typeVisible(s.type)).map(s => {
556-
const kids = childrenOf[s.id].filter(c => !q || c.label.toLowerCase().includes(q));
557-
return { id: s.id, label: s.label, kids, w: kids.reduce((a, c) => a + tmWeight(c), 0) };
558-
}).filter(d => d.w > 0);
559-
560-
const W = tmEl.clientWidth, H = tmEl.clientHeight, PAD = 4, HEAD = 21;
561-
let html = "";
562-
squarify(docItems, 0, 0, W, H).forEach(d => {
563-
html += `<div class="tm-doc" style="left:${d.X}px;top:${d.Y}px;width:${d.W}px;height:${d.H}px">`
564-
+ `<div class="tm-h" data-id="${esc(d.id)}" title="${esc(d.label)}">${esc(d.label)}</div>`;
565-
// tiles are children of the doc div → use coords RELATIVE to it (origin = PAD, HEAD)
566-
const iw = d.W - 2 * PAD, ih = d.H - HEAD - PAD;
567-
if(iw > 6 && ih > 6){
568-
squarify(d.kids.map(c => ({ id: c.id, label: c.label, type: c.type, w: tmWeight(c) })), PAD, HEAD, iw, ih)
569-
.forEach(t => {
570-
const col = colorOf(t.type), txt = t.W > 38 && t.H > 17;
571-
html += `<div class="tm-t" data-id="${esc(t.id)}" title="${esc(t.label)}" `
572-
+ `style="left:${t.X}px;top:${t.Y}px;width:${Math.max(0, t.W - 2)}px;height:${Math.max(0, t.H - 2)}px;`
573-
+ `background:rgba(${col},.2);border-color:rgba(${col},.5)">`
574-
+ (txt ? `<span style="color:rgb(${col})">${esc(t.label)}</span>` : ``) + `</div>`;
575-
});
576-
}
577-
html += `</div>`;
578-
});
579-
tmEl.innerHTML = html || '<div class="tm-empty">Nothing to show.</div>';
580-
tmEl.querySelectorAll("[data-id]").forEach(el => el.onclick = () => {
581-
const m = byId[el.dataset.id]; if(m) openPanel(m);
582-
});
583-
}
584-
const MODE_LABEL = { mindmap: "mind-map", radial: "radial", graph: "3D", treemap: "treemap" };
475+
const MODE_LABEL = { mindmap: "mind-map", radial: "radial", graph: "3D" };
585476
function setMode(m){
586477
mode = m;
587-
const canvasMode = (m === "graph" || m === "radial"); // canvas: 3D nebula + radial. mind-map & treemap are DOM
478+
const canvasMode = (m === "graph" || m === "radial"); // canvas: 3D nebula + radial. mind-map is DOM
588479
canvas.style.display = canvasMode ? "block" : "none";
589-
tmEl.style.display = (m === "treemap") ? "block" : "none";
590480
mmEl.style.display = (m === "mindmap") ? "block" : "none";
591-
spacingCtl.style.display = (m === "treemap" || m === "mindmap") ? "none" : "flex";
481+
spacingCtl.style.display = (m === "mindmap") ? "none" : "flex";
592482
hover = null; closePanel(); panX = 0; panY = 0; autoFit = true;
593-
if(m === "treemap"){ buildTreemap(); }
594-
else if(m === "mindmap"){ buildMindmap(); }
483+
if(m === "mindmap"){ buildMindmap(); }
595484
else if(m === "radial"){ yaw = 0; pitch = 0; scale = DEF_SCALE; layoutRadial(); alpha = 1; }
596485
else { yaw = DEF_YAW; pitch = DEF_PITCH; scale = DEF_SCALE; seed(); alpha = 1; }
597486
[...modeTabs.children].forEach(b => b.classList.toggle("on", b.dataset.m === m));
@@ -601,7 +490,6 @@
601490
hintEl.innerHTML =
602491
mode === "mindmap" ? 'OpenKB → documents → concepts &nbsp; <kbd>+/−</kbd> expand a branch &nbsp; <kbd>click</kbd> read details'
603492
: mode === "radial" ? 'centre <b>OpenKB</b> → documents → concepts &nbsp; faint = cross-refs &nbsp; <kbd>click</kbd> read &nbsp; <kbd>drag</kbd> pan'
604-
: mode === "treemap" ? 'each block = a document &nbsp; tiles = its concepts (size = connections) &nbsp; <kbd>click</kbd> read'
605493
: '<kbd>scroll</kbd> zoom &nbsp; <kbd>drag bg</kbd> rotate &nbsp; <kbd>click</kbd> inspect<br>'
606494
+ '<kbd>drag node</kbd> pull out &amp; pin &nbsp; <kbd>dbl-click</kbd> release';
607495
}
@@ -842,7 +730,7 @@
842730
treeEdges.forEach(drawTreeEdge);
843731
nodes.filter(visible).forEach(n => drawNode(n, t));
844732
drawRoot();
845-
} else if(mode === "graph"){ // mind-map & treemap are DOM — the canvas idles
733+
} else if(mode === "graph"){ // mind-map is DOM — the canvas idles in that mode
846734
if(alpha > 0.005) step();
847735
project();
848736
if(autoFit && !orbiting && !drag){ fitStep(); } // smoothly keep the cloud framed to the viewport
@@ -959,8 +847,7 @@
959847
row.onclick = () => {
960848
row.classList.toggle("off");
961849
if(hiddenTypes.has(t)) hiddenTypes.delete(t); else hiddenTypes.add(t);
962-
if(mode === "treemap") buildTreemap(); // rebuild without the hidden type
963-
else if(mode === "mindmap") buildMindmap();
850+
if(mode === "mindmap") buildMindmap(); // rebuild without the hidden type
964851
else { if(mode === "radial") layoutRadial(); alpha = Math.max(alpha, 0.4); } // reheat / re-fan
965852
};
966853
legend.appendChild(row);
@@ -969,7 +856,6 @@
969856
/* ===================== search ===================== */
970857
search.addEventListener("input", e => {
971858
const raw = e.target.value.trim();
972-
if(mode === "treemap"){ tmQuery = raw; buildTreemap(); return; } // filter tiles
973859
if(mode === "mindmap"){ mmQuery = raw; buildMindmap(); return; } // filter branches
974860
const q = raw.toLowerCase();
975861
if(!q){ hover = null; return; }
@@ -995,9 +881,7 @@
995881
});
996882
document.getElementById("reset").addEventListener("click", () => {
997883
hover = null; closePanel();
998-
if(mode === "treemap"){
999-
tmQuery = ""; search.value = ""; buildTreemap();
1000-
} else if(mode === "mindmap"){
884+
if(mode === "mindmap"){
1001885
mmQuery = ""; search.value = ""; mmCollapsed.clear(); collapseDocs(); buildMindmap(); mmEl.scrollTop = 0; mmEl.scrollLeft = 0;
1002886
} else if(mode === "radial"){
1003887
spread = DEFAULT_SPREAD; spreadEl.value = String(DEFAULT_SPREAD); panX = 0; panY = 0;
@@ -1013,9 +897,9 @@
1013897

1014898
/* ===================== boot ===================== */
1015899
function collapseDocs(){ nodes.forEach(n => { if(n.id.startsWith("summaries/")) mmCollapsed.add(n.id); }); } // start compact: docs folded
1016-
window.addEventListener("resize", () => { size(); autoFit = true; if(mode === "treemap") buildTreemap(); });
900+
window.addEventListener("resize", () => { size(); autoFit = true; if(mode === "mindmap") buildMindmap(); });
1017901
size(); seed(); collapseDocs();
1018-
setMode("mindmap"); // open on the OpenKB-rooted mind-map
902+
setMode("graph"); // open on the 3D nebula
1019903
requestAnimationFrame(frame);
1020904
</script>
1021905
</body>

0 commit comments

Comments
 (0)