Spaces:
Running
Running
Cap Explore layout at 3 nodes per column to prevent tall stacking
Browse filesWhen the LLM produces a shallow dependency graph (many nodes at depth 0),
all root concepts piled into a single column making the layout very tall
and vertical. Now each depth level overflows to a new column after 3 items,
keeping the graph readable regardless of how the LLM structures depends_on.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ui/src/components/ExploreView.jsx
CHANGED
|
@@ -90,8 +90,15 @@ function expansionOffsets(selectedId, concepts, basePositions) {
|
|
| 90 |
return offsets;
|
| 91 |
}
|
| 92 |
|
| 93 |
-
// ββ Layout: topological column assignment βββββββββββββββ
|
| 94 |
// Returns { [conceptId]: { x, y } } in canvas coordinates.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
function computeLayout(concepts) {
|
| 96 |
if (!concepts.length) return {};
|
| 97 |
|
|
@@ -106,22 +113,52 @@ function computeLayout(concepts) {
|
|
| 106 |
}
|
| 107 |
concepts.forEach(c => depth(c.id));
|
| 108 |
|
| 109 |
-
// Step 2: group by column, sort within column by reading_order
|
| 110 |
-
const
|
| 111 |
concepts.forEach(c => {
|
| 112 |
const col = depthCache[c.id] ?? 0;
|
| 113 |
-
if (!
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
});
|
| 116 |
-
Object.values(
|
| 117 |
arr.sort((a, b) => (a.reading_order ?? 99) - (b.reading_order ?? 99))
|
| 118 |
);
|
| 119 |
|
| 120 |
-
|
| 121 |
-
const maxColH = Math.max(...Object.values(cols).map(a => a.length)) * (CARD_H + ROW_GAP);
|
| 122 |
const positions = {};
|
| 123 |
-
Object.entries(
|
| 124 |
-
const x = Number(
|
| 125 |
const colH = nodes.length * (CARD_H + ROW_GAP) - ROW_GAP;
|
| 126 |
const startY = (maxColH - colH) / 2 + 48;
|
| 127 |
nodes.forEach((node, row) => {
|
|
|
|
| 90 |
return offsets;
|
| 91 |
}
|
| 92 |
|
| 93 |
+
// ββ Layout: topological column assignment with overflow wrapping βββββββββββββββ
|
| 94 |
// Returns { [conceptId]: { x, y } } in canvas coordinates.
|
| 95 |
+
//
|
| 96 |
+
// The LLM sometimes produces shallow dependency graphs (many nodes at depth 0),
|
| 97 |
+
// which naively stacks all root concepts into one tall column. We cap each
|
| 98 |
+
// column at MAX_PER_COL items and push overflow into the next available column,
|
| 99 |
+
// keeping the visual width reasonable and the graph readable.
|
| 100 |
+
const MAX_PER_COL = 3;
|
| 101 |
+
|
| 102 |
function computeLayout(concepts) {
|
| 103 |
if (!concepts.length) return {};
|
| 104 |
|
|
|
|
| 113 |
}
|
| 114 |
concepts.forEach(c => depth(c.id));
|
| 115 |
|
| 116 |
+
// Step 2: group by depth-column, sort within column by reading_order
|
| 117 |
+
const depthCols = {};
|
| 118 |
concepts.forEach(c => {
|
| 119 |
const col = depthCache[c.id] ?? 0;
|
| 120 |
+
if (!depthCols[col]) depthCols[col] = [];
|
| 121 |
+
depthCols[col].push(c);
|
| 122 |
+
});
|
| 123 |
+
Object.values(depthCols).forEach(arr =>
|
| 124 |
+
arr.sort((a, b) => (a.reading_order ?? 99) - (b.reading_order ?? 99))
|
| 125 |
+
);
|
| 126 |
+
|
| 127 |
+
// Step 3: assign visual columns, capping at MAX_PER_COL items per column.
|
| 128 |
+
// Each depth level starts in its own column. If a depth has more than MAX_PER_COL
|
| 129 |
+
// nodes, overflow spills into the next column. The following depth level then
|
| 130 |
+
// starts in the column after the last one used by the previous depth.
|
| 131 |
+
const colAssign = {};
|
| 132 |
+
let nextCol = 0;
|
| 133 |
+
|
| 134 |
+
const sortedDepths = Object.keys(depthCols).map(Number).sort((a, b) => a - b);
|
| 135 |
+
sortedDepths.forEach(d => {
|
| 136 |
+
const nodes = depthCols[d];
|
| 137 |
+
let col = nextCol;
|
| 138 |
+
let count = 0;
|
| 139 |
+
nodes.forEach(node => {
|
| 140 |
+
if (count >= MAX_PER_COL) { col++; count = 0; }
|
| 141 |
+
colAssign[node.id] = col;
|
| 142 |
+
count++;
|
| 143 |
+
});
|
| 144 |
+
nextCol = col + 1; // next depth starts after the last column used here
|
| 145 |
+
});
|
| 146 |
+
|
| 147 |
+
// Step 4: assign pixel positions β group by visual column, center each vertically
|
| 148 |
+
const visualCols = {};
|
| 149 |
+
concepts.forEach(c => {
|
| 150 |
+
const vc = colAssign[c.id] ?? 0;
|
| 151 |
+
if (!visualCols[vc]) visualCols[vc] = [];
|
| 152 |
+
visualCols[vc].push(c);
|
| 153 |
});
|
| 154 |
+
Object.values(visualCols).forEach(arr =>
|
| 155 |
arr.sort((a, b) => (a.reading_order ?? 99) - (b.reading_order ?? 99))
|
| 156 |
);
|
| 157 |
|
| 158 |
+
const maxColH = Math.max(...Object.values(visualCols).map(a => a.length)) * (CARD_H + ROW_GAP);
|
|
|
|
| 159 |
const positions = {};
|
| 160 |
+
Object.entries(visualCols).forEach(([vc, nodes]) => {
|
| 161 |
+
const x = Number(vc) * (CARD_W + COL_GAP) + 48;
|
| 162 |
const colH = nodes.length * (CARD_H + ROW_GAP) - ROW_GAP;
|
| 163 |
const startY = (maxColH - colH) / 2 + 48;
|
| 164 |
nodes.forEach((node, row) => {
|