local-knowledge-graph 1.10.2 → 1.10.3
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 +27 -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,20 @@ 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).addScaledVector(_arcOff, 6);
|
|
355
326
|
}
|
|
356
327
|
}
|
|
357
|
-
window.addEventListener('resize', () => setTimeout(measureLinkLabels, 60));
|
|
358
328
|
|
|
359
|
-
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
|
|
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
|
+
const p = arcPoint(new THREE.Vector3(), pa, pb, dir, off, 0.5);
|
|
335
|
+
lbl.position.copy(p).addScaledVector(off, 6);
|
|
389
336
|
}
|
|
390
337
|
|
|
391
338
|
function buildNodeMesh(category) {
|
|
@@ -499,7 +446,12 @@ function rebuildGraph() {
|
|
|
499
446
|
line.userData.relationId = r.id;
|
|
500
447
|
line.userData.baseOpacity = op;
|
|
501
448
|
linkGroup.add(line);
|
|
502
|
-
|
|
449
|
+
// 线标注显示具体关系名(如"父子"),线型/颜色仍由大类规定;Sprite 保持贴线与近大远小
|
|
450
|
+
const lbl = makeLabelSprite(r.name, st.css, 24);
|
|
451
|
+
lbl.userData.text = r.name;
|
|
452
|
+
setRelLabelPos(lbl, a.pos, b.pos, arc);
|
|
453
|
+
labelGroup.add(lbl);
|
|
454
|
+
simLinks.push({ id: r.id, a, b, line, label: lbl, dashed, confidence: conf, arc });
|
|
503
455
|
});
|
|
504
456
|
|
|
505
457
|
// 推理关系叠加:虚化虚线 + "(推)"标注,负数id与库中显式关系区分;仅显示两端均在当前视图的边
|
|
@@ -518,10 +470,12 @@ function rebuildGraph() {
|
|
|
518
470
|
const line = makeRelLine(a.pos, b.pos, arc, 0xc792ea, true, 0.35, 3, 5);
|
|
519
471
|
line.userData.relationId = -(idx + 1);
|
|
520
472
|
linkGroup.add(line);
|
|
521
|
-
|
|
473
|
+
const lbl = makeLabelSprite(ir.name + '(推)', '#c792ea', 22);
|
|
474
|
+
setRelLabelPos(lbl, a.pos, b.pos, arc);
|
|
475
|
+
labelGroup.add(lbl);
|
|
476
|
+
simLinks.push({ id: line.userData.relationId, a, b, line, label: lbl, dashed: true, arc });
|
|
522
477
|
});
|
|
523
478
|
}
|
|
524
|
-
rebuildLinkLabelEls();
|
|
525
479
|
|
|
526
480
|
simBudget = 420;
|
|
527
481
|
settleCount = 0;
|
|
@@ -599,7 +553,6 @@ function animate() {
|
|
|
599
553
|
if (camFly.t >= 1) camFly = null;
|
|
600
554
|
}
|
|
601
555
|
controls.update();
|
|
602
|
-
updateLinkLabels();
|
|
603
556
|
renderer.render(scene, camera);
|
|
604
557
|
}
|
|
605
558
|
animate();
|
|
@@ -1573,7 +1526,7 @@ function applyCanvasHi(nodes, rels) {
|
|
|
1573
1526
|
const base = l.line.userData.baseOpacity === undefined ? 0.9 : l.line.userData.baseOpacity;
|
|
1574
1527
|
const on = rels.has(l.id);
|
|
1575
1528
|
l.line.material.opacity = on ? Math.max(base, 0.95) : 0.05;
|
|
1576
|
-
if (l.
|
|
1529
|
+
if (l.label) l.label.material.opacity = on ? 1 : 0.06;
|
|
1577
1530
|
}
|
|
1578
1531
|
}
|
|
1579
1532
|
|
|
@@ -1586,7 +1539,7 @@ function clearCanvasHi() {
|
|
|
1586
1539
|
}
|
|
1587
1540
|
for (const l of simLinks) {
|
|
1588
1541
|
l.line.material.opacity = l.line.userData.baseOpacity === undefined ? 0.9 : l.line.userData.baseOpacity;
|
|
1589
|
-
if (l.
|
|
1542
|
+
if (l.label) l.label.material.opacity = 1;
|
|
1590
1543
|
}
|
|
1591
1544
|
}
|
|
1592
1545
|
|
|
@@ -1879,11 +1832,8 @@ function applyStyleLight(dirty) {
|
|
|
1879
1832
|
: new THREE.LineBasicMaterial({ color: st.color, transparent: true, opacity: op });
|
|
1880
1833
|
if (st.dashed) l.line.computeLineDistances();
|
|
1881
1834
|
l.dashed = st.dashed;
|
|
1882
|
-
|
|
1883
|
-
if (l.
|
|
1884
|
-
l.labelEl.style.color = st.css;
|
|
1885
|
-
l.labelEl.style.borderColor = st.css + '55';
|
|
1886
|
-
}
|
|
1835
|
+
const fresh = relabel(l.label, st.css, 24);
|
|
1836
|
+
if (fresh) l.label = fresh;
|
|
1887
1837
|
}
|
|
1888
1838
|
}
|
|
1889
1839
|
}
|
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;
|