local-knowledge-graph 1.10.2 → 1.10.4
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/package.json +1 -1
- package/public/app.js +26 -77
- package/public/index.html +0 -1
- package/public/style.css +0 -10
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -298,7 +298,7 @@ function makeRelLine(pa, pb, arc, color, dashed, opacity, dashSize, gapSize) {
|
|
|
298
298
|
return line;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
//
|
|
301
|
+
// 每帧根据节点最新位置刷新弧线几何与标签位置(Sprite 标签贴线、近大远小)
|
|
302
302
|
const _arcDir = new THREE.Vector3(), _arcOff = new THREE.Vector3(), _arcTmp = new THREE.Vector3();
|
|
303
303
|
function updateRelLine(l) {
|
|
304
304
|
if (!l.arc) {
|
|
@@ -307,7 +307,7 @@ function updateRelLine(l) {
|
|
|
307
307
|
posAttr.setXYZ(1, l.b.pos.x, l.b.pos.y, l.b.pos.z);
|
|
308
308
|
posAttr.needsUpdate = true;
|
|
309
309
|
if (l.dashed) l.line.computeLineDistances();
|
|
310
|
-
|
|
310
|
+
if (l.label) l.label.position.copy(l.a.pos).add(l.b.pos).multiplyScalar(0.5);
|
|
311
311
|
return;
|
|
312
312
|
}
|
|
313
313
|
_arcDir.subVectors(l.b.pos, l.a.pos);
|
|
@@ -319,73 +319,19 @@ function updateRelLine(l) {
|
|
|
319
319
|
}
|
|
320
320
|
posAttr.needsUpdate = true;
|
|
321
321
|
if (l.dashed) l.line.computeLineDistances();
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
/* ---- 关系标签:HTML层 + 屏幕空间防重叠 ---- */
|
|
327
|
-
const relLabelEls = new Map(); // relationId -> div
|
|
328
|
-
function rebuildLinkLabelEls() {
|
|
329
|
-
const layer = $('link-labels');
|
|
330
|
-
layer.innerHTML = '';
|
|
331
|
-
relLabelEls.clear();
|
|
332
|
-
for (const l of simLinks) {
|
|
333
|
-
const el = document.createElement('div');
|
|
334
|
-
el.className = 'rel-label';
|
|
335
|
-
const name = l.inferredName || (state.relations.find((x) => x.id === l.id) || {}).name || '';
|
|
336
|
-
el.textContent = name;
|
|
337
|
-
el.style.color = l.colorCss || '#ccc';
|
|
338
|
-
el.style.borderColor = (l.colorCss || '#ccc') + '55';
|
|
339
|
-
el.onclick = () => {
|
|
340
|
-
state.selected = { type: 'relation', id: l.id };
|
|
341
|
-
renderInfoCard();
|
|
342
|
-
};
|
|
343
|
-
layer.appendChild(el);
|
|
344
|
-
relLabelEls.set(l.id, el);
|
|
345
|
-
l.labelEl = el;
|
|
346
|
-
}
|
|
347
|
-
measureLinkLabels();
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// 一次性测量标签尺寸(投影定位需要 w/h;避免每帧读 offsetWidth 强制回流)
|
|
351
|
-
function measureLinkLabels() {
|
|
352
|
-
for (const el of relLabelEls.values()) {
|
|
353
|
-
el._w = el.offsetWidth;
|
|
354
|
-
el._h = el.offsetHeight;
|
|
322
|
+
// 标签置于弧顶(t=0.5),贴合弧线
|
|
323
|
+
if (l.label) {
|
|
324
|
+
arcPoint(_arcTmp, l.a.pos, l.b.pos, _arcDir, _arcOff, 0.5);
|
|
325
|
+
l.label.position.copy(_arcTmp);
|
|
355
326
|
}
|
|
356
327
|
}
|
|
357
|
-
window.addEventListener('resize', () => setTimeout(measureLinkLabels, 60));
|
|
358
328
|
|
|
359
|
-
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
for (const l of simLinks) {
|
|
366
|
-
const el = l.labelEl;
|
|
367
|
-
if (!el) continue;
|
|
368
|
-
_projV.copy(l.arcTop || l.a.pos).project(camera);
|
|
369
|
-
if (_projV.z > 1 || _projV.z < -1) { el.style.display = 'none'; continue; }
|
|
370
|
-
const x = (_projV.x + 1) / 2 * W, y = (1 - _projV.y) / 2 * H;
|
|
371
|
-
if (x < -80 || x > W + 80 || y < -40 || y > H + 40) { el.style.display = 'none'; continue; }
|
|
372
|
-
el.style.display = 'block';
|
|
373
|
-
items.push({ el, x, y, w: el._w || 40, h: el._h || 18 });
|
|
374
|
-
}
|
|
375
|
-
// 屏幕空间防重叠:按 y 排序,与已放置矩形相交的标签向下推开,直到无碰撞
|
|
376
|
-
items.sort((p, q) => p.y - q.y);
|
|
377
|
-
const placed = [];
|
|
378
|
-
for (const it of items) {
|
|
379
|
-
let ny = it.y, guard = 0;
|
|
380
|
-
while (guard++ < 60) {
|
|
381
|
-
const hit = placed.find((p) => Math.abs(ny - p.y) < (it.h + p.h) / 2 + 2 && Math.abs(it.x - p.x) < (it.w + p.w) / 2 + 4);
|
|
382
|
-
if (!hit) break;
|
|
383
|
-
ny = hit.y + (hit.h + it.h) / 2 + 2;
|
|
384
|
-
}
|
|
385
|
-
it.y = ny;
|
|
386
|
-
placed.push(it);
|
|
387
|
-
it.el.style.transform = `translate(${(it.x - it.w / 2).toFixed(1)}px, ${(it.y - it.h / 2).toFixed(1)}px)`;
|
|
388
|
-
}
|
|
329
|
+
// 弧顶标签初始定位(构建时用;此后每帧由 updateRelLine 跟随)
|
|
330
|
+
function setRelLabelPos(lbl, pa, pb, arc) {
|
|
331
|
+
if (!arc) { lbl.position.copy(pa).add(pb).multiplyScalar(0.5); return; }
|
|
332
|
+
const dir = pb.clone().sub(pa);
|
|
333
|
+
const off = arcOffsetVec(dir, arc);
|
|
334
|
+
lbl.position.copy(arcPoint(new THREE.Vector3(), pa, pb, dir, off, 0.5));
|
|
389
335
|
}
|
|
390
336
|
|
|
391
337
|
function buildNodeMesh(category) {
|
|
@@ -499,7 +445,12 @@ function rebuildGraph() {
|
|
|
499
445
|
line.userData.relationId = r.id;
|
|
500
446
|
line.userData.baseOpacity = op;
|
|
501
447
|
linkGroup.add(line);
|
|
502
|
-
|
|
448
|
+
// 线标注显示具体关系名(如"父子"),线型/颜色仍由大类规定;Sprite 保持贴线与近大远小
|
|
449
|
+
const lbl = makeLabelSprite(r.name, st.css, 24);
|
|
450
|
+
lbl.userData.text = r.name;
|
|
451
|
+
setRelLabelPos(lbl, a.pos, b.pos, arc);
|
|
452
|
+
labelGroup.add(lbl);
|
|
453
|
+
simLinks.push({ id: r.id, a, b, line, label: lbl, dashed, confidence: conf, arc });
|
|
503
454
|
});
|
|
504
455
|
|
|
505
456
|
// 推理关系叠加:虚化虚线 + "(推)"标注,负数id与库中显式关系区分;仅显示两端均在当前视图的边
|
|
@@ -518,10 +469,12 @@ function rebuildGraph() {
|
|
|
518
469
|
const line = makeRelLine(a.pos, b.pos, arc, 0xc792ea, true, 0.35, 3, 5);
|
|
519
470
|
line.userData.relationId = -(idx + 1);
|
|
520
471
|
linkGroup.add(line);
|
|
521
|
-
|
|
472
|
+
const lbl = makeLabelSprite(ir.name + '(推)', '#c792ea', 22);
|
|
473
|
+
setRelLabelPos(lbl, a.pos, b.pos, arc);
|
|
474
|
+
labelGroup.add(lbl);
|
|
475
|
+
simLinks.push({ id: line.userData.relationId, a, b, line, label: lbl, dashed: true, arc });
|
|
522
476
|
});
|
|
523
477
|
}
|
|
524
|
-
rebuildLinkLabelEls();
|
|
525
478
|
|
|
526
479
|
simBudget = 420;
|
|
527
480
|
settleCount = 0;
|
|
@@ -599,7 +552,6 @@ function animate() {
|
|
|
599
552
|
if (camFly.t >= 1) camFly = null;
|
|
600
553
|
}
|
|
601
554
|
controls.update();
|
|
602
|
-
updateLinkLabels();
|
|
603
555
|
renderer.render(scene, camera);
|
|
604
556
|
}
|
|
605
557
|
animate();
|
|
@@ -1573,7 +1525,7 @@ function applyCanvasHi(nodes, rels) {
|
|
|
1573
1525
|
const base = l.line.userData.baseOpacity === undefined ? 0.9 : l.line.userData.baseOpacity;
|
|
1574
1526
|
const on = rels.has(l.id);
|
|
1575
1527
|
l.line.material.opacity = on ? Math.max(base, 0.95) : 0.05;
|
|
1576
|
-
if (l.
|
|
1528
|
+
if (l.label) l.label.material.opacity = on ? 1 : 0.06;
|
|
1577
1529
|
}
|
|
1578
1530
|
}
|
|
1579
1531
|
|
|
@@ -1586,7 +1538,7 @@ function clearCanvasHi() {
|
|
|
1586
1538
|
}
|
|
1587
1539
|
for (const l of simLinks) {
|
|
1588
1540
|
l.line.material.opacity = l.line.userData.baseOpacity === undefined ? 0.9 : l.line.userData.baseOpacity;
|
|
1589
|
-
if (l.
|
|
1541
|
+
if (l.label) l.label.material.opacity = 1;
|
|
1590
1542
|
}
|
|
1591
1543
|
}
|
|
1592
1544
|
|
|
@@ -1879,11 +1831,8 @@ function applyStyleLight(dirty) {
|
|
|
1879
1831
|
: new THREE.LineBasicMaterial({ color: st.color, transparent: true, opacity: op });
|
|
1880
1832
|
if (st.dashed) l.line.computeLineDistances();
|
|
1881
1833
|
l.dashed = st.dashed;
|
|
1882
|
-
|
|
1883
|
-
if (l.
|
|
1884
|
-
l.labelEl.style.color = st.css;
|
|
1885
|
-
l.labelEl.style.borderColor = st.css + '55';
|
|
1886
|
-
}
|
|
1834
|
+
const fresh = relabel(l.label, st.css, 24);
|
|
1835
|
+
if (fresh) l.label = fresh;
|
|
1887
1836
|
}
|
|
1888
1837
|
}
|
|
1889
1838
|
}
|
package/public/index.html
CHANGED
package/public/style.css
CHANGED
|
@@ -98,16 +98,6 @@
|
|
|
98
98
|
|
|
99
99
|
#canvas-wrap { flex: 1; position: relative; min-width: 0; background: radial-gradient(ellipse at center, #0d1424 0%, #070a12 100%); }
|
|
100
100
|
#canvas-wrap canvas { display: block; touch-action: none; }
|
|
101
|
-
/* 关系标签层:屏幕空间定位,防重叠 */
|
|
102
|
-
#link-labels { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
|
|
103
|
-
.rel-label {
|
|
104
|
-
position: absolute; left: 0; top: 0; pointer-events: auto; cursor: pointer;
|
|
105
|
-
font-size: 11px; line-height: 1; padding: 3px 7px; border-radius: 9px; white-space: nowrap;
|
|
106
|
-
background: rgba(9,13,24,.78); border: 1px solid transparent; user-select: none;
|
|
107
|
-
will-change: transform; text-shadow: none;
|
|
108
|
-
}
|
|
109
|
-
.rel-label:hover { filter: brightness(1.25); }
|
|
110
|
-
html[data-theme="light"] .rel-label { background: rgba(255,255,255,.86); }
|
|
111
101
|
.legend {
|
|
112
102
|
position: absolute; left: 12px; bottom: 12px; background: rgba(13,20,36,.85);
|
|
113
103
|
border: 1px solid #1e2a44; border-radius: 8px; padding: 9px 12px; font-size: 11px; color: #a9bbd8;
|