@mauroandre/velojs 0.0.40 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/graph-viewer.js +87 -49
- package/package.json +1 -1
package/dist/graph-viewer.js
CHANGED
|
@@ -48,7 +48,7 @@ const s = `<!DOCTYPE html>
|
|
|
48
48
|
<div><i style="background:#5b8def"></i>page</div>
|
|
49
49
|
<div><i style="background:#4b5565"></i>module</div>
|
|
50
50
|
</div>
|
|
51
|
-
<div id="hint">drag to pan · scroll to zoom · drag nodes · hover =
|
|
51
|
+
<div id="hint">drag to pan · scroll to zoom · drag nodes · hover = highlight · click = pin highlight + details · / = search · esc = reset</div>
|
|
52
52
|
<script>
|
|
53
53
|
var KIND_COLORS = { root: "#f59e0b", layout: "#a78bfa", page: "#5b8def", unknown: "#4b5565" };
|
|
54
54
|
|
|
@@ -90,16 +90,16 @@ function buildRouteLayout() {
|
|
|
90
90
|
var leafCount = 0;
|
|
91
91
|
// tidy-ish: leaves get sequential x; parents sit at the midpoint of children
|
|
92
92
|
function layout(node, depth, parent) {
|
|
93
|
-
var rn = { data: node, depth: depth, x: 0, y: depth, parent: parent };
|
|
93
|
+
var rn = { data: node, depth: depth, x: 0, y: depth, parent: parent, kids: [] };
|
|
94
94
|
routeNodes.push(rn);
|
|
95
|
-
if (parent) routeEdges.push({ from: parent, to: rn });
|
|
95
|
+
if (parent) { parent.kids.push(rn); routeEdges.push({ from: parent, to: rn }); }
|
|
96
96
|
if (!node.children.length) { rn.x = leafCount++; return rn; }
|
|
97
97
|
var xs = node.children.map(function (c) { return layout(c, depth + 1, rn).x; });
|
|
98
98
|
rn.x = (xs[0] + xs[xs.length - 1]) / 2;
|
|
99
99
|
return rn;
|
|
100
100
|
}
|
|
101
101
|
graph.routes.forEach(function (r) { layout(r, 0, null); });
|
|
102
|
-
var gapX =
|
|
102
|
+
var gapX = 250, gapY = 130;
|
|
103
103
|
var minX = Math.min.apply(null, routeNodes.map(function (n) { return n.x; }));
|
|
104
104
|
routeNodes.forEach(function (n) { n.px = (n.x - minX) * gapX + 80; n.py = n.y * gapY + 80; });
|
|
105
105
|
}
|
|
@@ -118,8 +118,9 @@ function toWorld(sx, sy) { return [(sx - cam.x) / cam.k, (sy - cam.y) / cam.k];
|
|
|
118
118
|
|
|
119
119
|
// ---------- force simulation (modules) ----------
|
|
120
120
|
var alpha = 1;
|
|
121
|
+
var MAXV = 25;
|
|
121
122
|
function tick() {
|
|
122
|
-
if (
|
|
123
|
+
if (alpha < 0.005) return;
|
|
123
124
|
alpha *= 0.995;
|
|
124
125
|
var i, j, n, m, dx, dy, d2, f;
|
|
125
126
|
for (i = 0; i < modNodes.length; i++) {
|
|
@@ -145,26 +146,40 @@ function tick() {
|
|
|
145
146
|
if (n === dragNode) return;
|
|
146
147
|
n.vx += -n.x * 0.002 * alpha; n.vy += -n.y * 0.002 * alpha;
|
|
147
148
|
n.vx *= 0.85; n.vy *= 0.85;
|
|
149
|
+
if (n.vx > MAXV) n.vx = MAXV; else if (n.vx < -MAXV) n.vx = -MAXV;
|
|
150
|
+
if (n.vy > MAXV) n.vy = MAXV; else if (n.vy < -MAXV) n.vy = -MAXV;
|
|
148
151
|
n.x += n.vx; n.y += n.vy;
|
|
149
152
|
});
|
|
150
153
|
}
|
|
151
154
|
|
|
152
|
-
// ----------
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
var
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
155
|
+
// ---------- highlight — hover = transient, click = pinned ----------
|
|
156
|
+
// Same pattern in both views. Related set: modules → transitive
|
|
157
|
+
// importedBy (impact); routes → ancestors + subtree (context).
|
|
158
|
+
var hoverHL = null, pinnedHL = null;
|
|
159
|
+
function activeHL() { return pinnedHL || hoverHL; }
|
|
160
|
+
function relatedNodes(node) {
|
|
161
|
+
var list = [];
|
|
162
|
+
if (view === "modules") {
|
|
163
|
+
var seen = {}, q = [node];
|
|
164
|
+
seen[node.id] = true;
|
|
165
|
+
while (q.length) {
|
|
166
|
+
var cur = q.shift();
|
|
167
|
+
list.push(cur);
|
|
168
|
+
var m = graph.modules[cur.id];
|
|
169
|
+
if (!m) continue;
|
|
170
|
+
m.importedBy.forEach(function (i) {
|
|
171
|
+
if (modById[i] && !seen[i]) { seen[i] = true; q.push(modById[i]); }
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
var p = node.parent;
|
|
176
|
+
while (p) { list.push(p); p = p.parent; }
|
|
177
|
+
(function down(n) { list.push(n); n.kids.forEach(down); })(node);
|
|
165
178
|
}
|
|
166
|
-
return
|
|
179
|
+
return list;
|
|
167
180
|
}
|
|
181
|
+
function modVisible(n) { return matches(n.id); }
|
|
182
|
+
function routeVisible(n) { return matches(n.data.moduleId || "(wrapper)"); }
|
|
168
183
|
|
|
169
184
|
// ---------- selection / panel ----------
|
|
170
185
|
var selected = null;
|
|
@@ -205,19 +220,22 @@ var query = "";
|
|
|
205
220
|
document.getElementById("search").addEventListener("input", function (e) { query = e.target.value.toLowerCase(); });
|
|
206
221
|
document.addEventListener("keydown", function (e) {
|
|
207
222
|
if (e.key === "/" && document.activeElement !== document.getElementById("search")) { e.preventDefault(); document.getElementById("search").focus(); }
|
|
208
|
-
if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; }
|
|
223
|
+
if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; hoverHL = null; pinnedHL = null; }
|
|
209
224
|
});
|
|
210
225
|
|
|
211
226
|
// ---------- interaction ----------
|
|
212
227
|
var dragNode = null, panning = false, lastX = 0, lastY = 0, moved = false;
|
|
213
228
|
function nodeAt(sx, sy) {
|
|
214
|
-
|
|
229
|
+
// Hit test in SCREEN space, tight to the dot (dot radius + 6px slack)
|
|
215
230
|
var list = view === "modules" ? modNodes : routeNodes;
|
|
216
231
|
for (var i = list.length - 1; i >= 0; i--) {
|
|
217
232
|
var n = list[i];
|
|
218
|
-
|
|
219
|
-
var
|
|
220
|
-
|
|
233
|
+
if (view === "modules" ? !modVisible(n) : !routeVisible(n)) continue;
|
|
234
|
+
var wx = view === "modules" ? n.x : n.px, wy = view === "modules" ? n.y : n.py;
|
|
235
|
+
var p = toScreen(wx, wy);
|
|
236
|
+
var rad = (view === "modules" ? (n.kind === "unknown" ? 5 : 8) : 6) + 6;
|
|
237
|
+
var dx = sx - p[0], dy = sy - p[1];
|
|
238
|
+
if (dx * dx + dy * dy < rad * rad) return n;
|
|
221
239
|
}
|
|
222
240
|
return null;
|
|
223
241
|
}
|
|
@@ -234,8 +252,7 @@ canvas.addEventListener("mousemove", function (e) {
|
|
|
234
252
|
else if (panning) { cam.x += dx; cam.y += dy; }
|
|
235
253
|
else {
|
|
236
254
|
var n = nodeAt(e.clientX, e.clientY);
|
|
237
|
-
|
|
238
|
-
impactSet = hoverNode ? computeImpact(hoverNode) : null;
|
|
255
|
+
hoverHL = n ? { list: relatedNodes(n), origin: n } : null;
|
|
239
256
|
canvas.style.cursor = n ? "pointer" : "grab";
|
|
240
257
|
}
|
|
241
258
|
lastX = e.clientX; lastY = e.clientY;
|
|
@@ -243,9 +260,14 @@ canvas.addEventListener("mousemove", function (e) {
|
|
|
243
260
|
canvas.addEventListener("mouseup", function (e) {
|
|
244
261
|
if (!moved) {
|
|
245
262
|
var n = nodeAt(e.clientX, e.clientY);
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
263
|
+
if (n) {
|
|
264
|
+
pinnedHL = { list: relatedNodes(n), origin: n };
|
|
265
|
+
var id = view === "modules" ? n.id : n.data.moduleId;
|
|
266
|
+
showPanel(id || null);
|
|
267
|
+
} else {
|
|
268
|
+
pinnedHL = null;
|
|
269
|
+
showPanel(null);
|
|
270
|
+
}
|
|
249
271
|
}
|
|
250
272
|
dragNode = null; panning = false;
|
|
251
273
|
});
|
|
@@ -262,57 +284,73 @@ function matches(id) { return !query || id.toLowerCase().indexOf(query) >= 0; }
|
|
|
262
284
|
|
|
263
285
|
function drawRoutes() {
|
|
264
286
|
ctx.clearRect(0, 0, W, H);
|
|
265
|
-
|
|
287
|
+
var hl = activeHL();
|
|
266
288
|
routeEdges.forEach(function (e) {
|
|
289
|
+
if (!routeVisible(e.from) || !routeVisible(e.to)) return;
|
|
290
|
+
var hot = hl && hl.list.indexOf(e.from) >= 0 && hl.list.indexOf(e.to) >= 0;
|
|
267
291
|
var a = toScreen(e.from.px, e.from.py), b = toScreen(e.to.px, e.to.py);
|
|
292
|
+
ctx.globalAlpha = hl && !hot ? 0.08 : 1;
|
|
293
|
+
ctx.strokeStyle = hot ? "#f59e0b" : "#2a3247";
|
|
294
|
+
ctx.lineWidth = hot ? 2 : 1.2;
|
|
268
295
|
ctx.beginPath();
|
|
269
296
|
ctx.moveTo(a[0], a[1]);
|
|
270
297
|
ctx.bezierCurveTo(a[0], (a[1] + b[1]) / 2, b[0], (a[1] + b[1]) / 2, b[0], b[1]);
|
|
271
298
|
ctx.stroke();
|
|
272
299
|
});
|
|
300
|
+
ctx.globalAlpha = 1;
|
|
273
301
|
routeNodes.forEach(function (n) {
|
|
274
302
|
var d = n.data;
|
|
275
303
|
var id = d.moduleId || "(wrapper)";
|
|
276
|
-
if (!
|
|
304
|
+
if (!routeVisible(n)) return;
|
|
277
305
|
var p = toScreen(n.px, n.py);
|
|
306
|
+
var member = hl && hl.list.indexOf(n) >= 0;
|
|
307
|
+
var dim = hl && !member;
|
|
308
|
+
var isOrigin = hl && hl.origin === n;
|
|
278
309
|
var color = d.isRoot ? KIND_COLORS.root : (d.children.length ? KIND_COLORS.layout : KIND_COLORS.page);
|
|
279
310
|
var sel = selected === d.moduleId;
|
|
280
|
-
ctx.
|
|
281
|
-
ctx.
|
|
311
|
+
ctx.globalAlpha = dim ? 0.15 : 1;
|
|
312
|
+
ctx.beginPath(); ctx.arc(p[0], p[1], (sel || isOrigin) ? 9 : 6, 0, 7);
|
|
313
|
+
ctx.fillStyle = isOrigin ? "#f59e0b" : color; ctx.fill();
|
|
282
314
|
if (sel) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
315
|
+
if (!dim) {
|
|
316
|
+
ctx.fillStyle = member ? "#e8edf5" : "#d7dde8"; ctx.font = "11px ui-monospace, monospace"; ctx.textAlign = "center";
|
|
317
|
+
ctx.fillText(id.split("/").pop(), p[0], p[1] - 13);
|
|
318
|
+
ctx.fillStyle = "#55607a"; ctx.font = "10px ui-monospace, monospace";
|
|
319
|
+
var sub = d.fullPath || "";
|
|
320
|
+
if (d.middlewares.length) sub += " · " + d.middlewares.join(", ");
|
|
321
|
+
ctx.fillText(sub, p[0], p[1] + 20);
|
|
322
|
+
}
|
|
323
|
+
ctx.globalAlpha = 1;
|
|
289
324
|
});
|
|
290
325
|
}
|
|
291
326
|
|
|
292
327
|
function drawModules() {
|
|
293
328
|
ctx.clearRect(0, 0, W, H);
|
|
294
|
-
|
|
329
|
+
var hl = activeHL();
|
|
295
330
|
modEdges.forEach(function (e) {
|
|
296
|
-
if (!
|
|
297
|
-
var hot =
|
|
331
|
+
if (!modVisible(e.from) || !modVisible(e.to)) return;
|
|
332
|
+
var hot = hl && hl.list.indexOf(e.from) >= 0 && hl.list.indexOf(e.to) >= 0;
|
|
298
333
|
var a = toScreen(e.from.x, e.from.y), b = toScreen(e.to.x, e.to.y);
|
|
334
|
+
ctx.globalAlpha = hl && !hot ? 0.08 : 1;
|
|
299
335
|
ctx.strokeStyle = hot ? "#f59e0b" : "#232a3a";
|
|
300
336
|
ctx.lineWidth = hot ? 1.8 : 1;
|
|
301
337
|
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
|
|
302
338
|
});
|
|
339
|
+
ctx.globalAlpha = 1;
|
|
303
340
|
modNodes.forEach(function (n) {
|
|
304
|
-
if (!
|
|
341
|
+
if (!modVisible(n)) return;
|
|
305
342
|
var p = toScreen(n.x, n.y);
|
|
306
|
-
var
|
|
307
|
-
var
|
|
308
|
-
var
|
|
343
|
+
var member = hl && hl.list.indexOf(n) >= 0;
|
|
344
|
+
var dim = hl && !member;
|
|
345
|
+
var isOrigin = hl && hl.origin === n;
|
|
346
|
+
var r = n.kind === "unknown" ? 5 : 8;
|
|
309
347
|
ctx.globalAlpha = dim ? 0.15 : 1;
|
|
310
|
-
ctx.beginPath(); ctx.arc(p[0], p[1], selected === n.id ? r + 3 : r, 0, 7);
|
|
311
|
-
ctx.fillStyle =
|
|
348
|
+
ctx.beginPath(); ctx.arc(p[0], p[1], (selected === n.id || isOrigin) ? r + 3 : r, 0, 7);
|
|
349
|
+
ctx.fillStyle = isOrigin ? "#f59e0b" : KIND_COLORS[n.kind] || KIND_COLORS.unknown;
|
|
312
350
|
ctx.fill();
|
|
313
351
|
if (selected === n.id) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
|
|
314
352
|
if (!dim) {
|
|
315
|
-
ctx.fillStyle =
|
|
353
|
+
ctx.fillStyle = member ? "#e8edf5" : "#9aa5b8";
|
|
316
354
|
ctx.font = "10px ui-monospace, monospace"; ctx.textAlign = "center";
|
|
317
355
|
ctx.fillText(n.id.split("/").pop(), p[0], p[1] - r - 5);
|
|
318
356
|
}
|