@takagaki/cortex-decisions-viewer 0.4.6 → 0.4.7
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/build.js +22 -8
- package/dist/render.js +64 -8
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -513,26 +513,40 @@ function buildGraph(decisions, home, sections, content, targetUrls, repoBaseUrl,
|
|
|
513
513
|
if (target)
|
|
514
514
|
edges.push({ from: fromId, to: target, rel });
|
|
515
515
|
};
|
|
516
|
+
// 外部URLノードを作る(source/references の出典用フォールバック)。
|
|
517
|
+
const externalNode = (url, label) => addNode({
|
|
518
|
+
id: url,
|
|
519
|
+
label: (label ?? url.replace(/^https?:\/\//, "")).slice(0, 60),
|
|
520
|
+
group: "external",
|
|
521
|
+
url,
|
|
522
|
+
});
|
|
523
|
+
// 出典(source/reference)の解決: Backlog課題URLは実ノード(bronze)へ、議事録ID等は該当ノードへ。
|
|
524
|
+
// 解決できない外部URL(Wiki・GitHub等)は外部ノードとして必ず繋ぐ(出典は少数で意味があるため残す。
|
|
525
|
+
// 大量の「本文リンク」の騒音は addLinkEdges 側でドロップしたまま)。
|
|
526
|
+
const provenanceTarget = (raw, label) => {
|
|
527
|
+
const v = raw.trim();
|
|
528
|
+
return classifyTarget(backlogViewKey(v) ?? v) ?? externalNode(v, label);
|
|
529
|
+
};
|
|
516
530
|
const addRelationEdges = (fromId, relations) => {
|
|
517
531
|
for (const r of relations)
|
|
518
532
|
pushEdge(fromId, classifyTarget(r.target), r.rel);
|
|
519
533
|
};
|
|
520
534
|
const addSourceEdge = (fromId, source) => {
|
|
521
535
|
if (source && source.trim() !== "")
|
|
522
|
-
|
|
536
|
+
edges.push({ from: fromId, to: provenanceTarget(source), rel: "source" });
|
|
523
537
|
};
|
|
524
538
|
const addReferenceEdges = (fromId, references) => {
|
|
525
|
-
// ref.url(http(s))は classifyTarget が null を返す=外部はノード化しない。
|
|
526
|
-
// URLが無い参照(課題キー・議事録ID等)は従来どおり該当ノードへ繋ぐ。
|
|
527
539
|
for (const ref of references) {
|
|
528
|
-
|
|
540
|
+
const to = ref.url ? provenanceTarget(ref.url, ref.label) : classifyTarget(ref.label);
|
|
541
|
+
pushEdge(fromId, to, "reference");
|
|
529
542
|
}
|
|
530
543
|
};
|
|
544
|
+
// Gold層(Decision/用語/レポート/Home)の本文リンクは必ず繋ぐ(外部URLでも外部ノードとして接続)。
|
|
545
|
+
// =frontmatterを持つレコードは孤立させない。大量の騒音は「コンテキスト側の本文リンク」(content edges)
|
|
546
|
+
// のみドロップする(下の content ループは classifyTarget のまま)。
|
|
531
547
|
const addLinkEdges = (fromId, links) => {
|
|
532
|
-
for (const link of links)
|
|
533
|
-
|
|
534
|
-
pushEdge(fromId, classifyTarget(key ?? link), "link");
|
|
535
|
-
}
|
|
548
|
+
for (const link of links)
|
|
549
|
+
edges.push({ from: fromId, to: provenanceTarget(link), rel: "link" });
|
|
536
550
|
};
|
|
537
551
|
// Bronze/Silverのノードを先に登録する(Gold・Bronze/Silver双方からの参照が同一ノードに解決されるように)
|
|
538
552
|
for (const c of content) {
|
package/dist/render.js
CHANGED
|
@@ -979,8 +979,8 @@ const CLIENT_JS = `
|
|
|
979
979
|
}
|
|
980
980
|
resize();
|
|
981
981
|
|
|
982
|
-
|
|
983
|
-
|
|
982
|
+
// 反発(O(n²)・全ペア総当たり)。点が少ないうちはこれが最速。
|
|
983
|
+
function repulseNaive() {
|
|
984
984
|
for (var i = 0; i < nodes.length; i++) {
|
|
985
985
|
for (var j = i + 1; j < nodes.length; j++) {
|
|
986
986
|
var a = nodes[i], b = nodes[j];
|
|
@@ -992,6 +992,60 @@ const CLIENT_JS = `
|
|
|
992
992
|
a.vx -= fx; a.vy -= fy; b.vx += fx; b.vy += fy;
|
|
993
993
|
}
|
|
994
994
|
}
|
|
995
|
+
}
|
|
996
|
+
// 反発(Barnes-Hut・O(n log n))。四分木で遠いノード群を重心1点に近似する。
|
|
997
|
+
// 点が多い案件(フリート拡大時)でも tick が重くならない=点を削らずに固まりを防ぐための土台。
|
|
998
|
+
function repulseBH() {
|
|
999
|
+
var nn = nodes.length, i;
|
|
1000
|
+
if (nn < 2) return;
|
|
1001
|
+
var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1002
|
+
for (i = 0; i < nn; i++) { var p0 = nodes[i]; if (p0.x < minX) minX = p0.x; if (p0.y < minY) minY = p0.y; if (p0.x > maxX) maxX = p0.x; if (p0.y > maxY) maxY = p0.y; }
|
|
1003
|
+
var size = Math.max(maxX - minX, maxY - minY, 1) + 1;
|
|
1004
|
+
// 配列ベースの四分木。BODY: -1=空 / >=0=葉(そのノードindex) / -2=内部ノード
|
|
1005
|
+
var QX = [minX], QY = [minY], QS = [size], MASS = [0], CX = [0], CY = [0];
|
|
1006
|
+
var C0 = [-1], C1 = [-1], C2 = [-1], C3 = [-1], BODY = [-1];
|
|
1007
|
+
function nc(x, y, s) { QX.push(x); QY.push(y); QS.push(s); MASS.push(0); CX.push(0); CY.push(0); C0.push(-1); C1.push(-1); C2.push(-1); C3.push(-1); BODY.push(-1); return QX.length - 1; }
|
|
1008
|
+
function childOf(c, q) { return q === 0 ? C0[c] : q === 1 ? C1[c] : q === 2 ? C2[c] : C3[c]; }
|
|
1009
|
+
function setChild(c, q, v) { if (q === 0) C0[c] = v; else if (q === 1) C1[c] = v; else if (q === 2) C2[c] = v; else C3[c] = v; }
|
|
1010
|
+
function pushDown(c, bi, depth) {
|
|
1011
|
+
var hx = QX[c] + QS[c] / 2, hy = QY[c] + QS[c] / 2;
|
|
1012
|
+
var q = (nodes[bi].x >= hx ? 1 : 0) + (nodes[bi].y >= hy ? 2 : 0);
|
|
1013
|
+
var ch = childOf(c, q);
|
|
1014
|
+
if (ch === -1) { var s = QS[c] / 2; ch = nc(QX[c] + ((q & 1) ? s : 0), QY[c] + ((q & 2) ? s : 0), s); setChild(c, q, ch); }
|
|
1015
|
+
insert(ch, bi, depth + 1);
|
|
1016
|
+
}
|
|
1017
|
+
function insert(c, bi, depth) {
|
|
1018
|
+
var x = nodes[bi].x, y = nodes[bi].y;
|
|
1019
|
+
if (MASS[c] === 0) { CX[c] = x; CY[c] = y; } else { CX[c] = (CX[c] * MASS[c] + x) / (MASS[c] + 1); CY[c] = (CY[c] * MASS[c] + y) / (MASS[c] + 1); }
|
|
1020
|
+
if (MASS[c] === 0 && BODY[c] === -1) { BODY[c] = bi; MASS[c] = 1; return; }
|
|
1021
|
+
MASS[c]++;
|
|
1022
|
+
if (depth >= 24) return; // 深さ上限(ほぼ同一座標): これ以上分割せず集約
|
|
1023
|
+
if (BODY[c] !== -2) { var old = BODY[c]; BODY[c] = -2; pushDown(c, old, depth); }
|
|
1024
|
+
pushDown(c, bi, depth);
|
|
1025
|
+
}
|
|
1026
|
+
for (i = 0; i < nn; i++) insert(0, i, 0);
|
|
1027
|
+
var theta2 = 1.0; // 受容角θ=1.0 の二乗(セルサイズ/距離 < θ なら重心1点で近似)
|
|
1028
|
+
for (i = 0; i < nn; i++) {
|
|
1029
|
+
var p = nodes[i]; var stack = [0];
|
|
1030
|
+
while (stack.length) {
|
|
1031
|
+
var c = stack.pop();
|
|
1032
|
+
if (MASS[c] === 0) continue;
|
|
1033
|
+
if (BODY[c] === i && MASS[c] === 1) continue; // 自分自身の葉
|
|
1034
|
+
var dx = CX[c] - p.x, dy = CY[c] - p.y; var d2 = dx * dx + dy * dy + 0.01;
|
|
1035
|
+
if (BODY[c] !== -2 || QS[c] * QS[c] < theta2 * d2) {
|
|
1036
|
+
var f = 1800 * MASS[c] / d2; var d = Math.sqrt(d2);
|
|
1037
|
+
p.vx -= dx / d * f; p.vy -= dy / d * f;
|
|
1038
|
+
} else {
|
|
1039
|
+
if (C0[c] !== -1) stack.push(C0[c]); if (C1[c] !== -1) stack.push(C1[c]);
|
|
1040
|
+
if (C2[c] !== -1) stack.push(C2[c]); if (C3[c] !== -1) stack.push(C3[c]);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
// 約900点を境に切替: 小規模=naive(オーバーヘッド無しで最速) / 大規模=Barnes-Hut(固まらない)。
|
|
1046
|
+
var REPULSE_NAIVE_MAX = 900;
|
|
1047
|
+
function tick() {
|
|
1048
|
+
if (nodes.length < REPULSE_NAIVE_MAX) repulseNaive(); else repulseBH();
|
|
995
1049
|
// ばね(辺)
|
|
996
1050
|
edges.forEach(function (e) {
|
|
997
1051
|
var dx = e.b.x - e.a.x, dy = e.b.y - e.a.y;
|
|
@@ -1000,13 +1054,15 @@ const CLIENT_JS = `
|
|
|
1000
1054
|
var fx = (dx / d) * f, fy = (dy / d) * f;
|
|
1001
1055
|
e.a.vx += fx; e.a.vy += fy; e.b.vx -= fx; e.b.vy -= fy;
|
|
1002
1056
|
});
|
|
1003
|
-
// Gold
|
|
1057
|
+
// 全ノードを中心へ向ける等方的な引力(Goldは強め=中央に、コンテキストは弱め=その周り)。
|
|
1058
|
+
// さらに半径 RING の円で外周を押し戻して封じ込める。これで対角の伸び・飛び出したスポークが
|
|
1059
|
+
// 抑えられ、全体が円形のディスクに収まる(force-directed本来の「丸める力の不在」を補う)。
|
|
1004
1060
|
nodes.forEach(function (n) {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
var rf = (r - RING) * 0.
|
|
1061
|
+
var g = n.recordId ? 0.012 : 0.005;
|
|
1062
|
+
n.vx -= n.x * g; n.vy -= n.y * g;
|
|
1063
|
+
var r = Math.sqrt(n.x * n.x + n.y * n.y) + 0.01;
|
|
1064
|
+
if (r > RING) {
|
|
1065
|
+
var rf = (r - RING) * 0.05; // 円の外に出た分だけ内側へ強めに戻す(円形の境界)
|
|
1010
1066
|
n.vx -= (n.x / r) * rf; n.vy -= (n.y / r) * rf;
|
|
1011
1067
|
}
|
|
1012
1068
|
if (n === draggingNode) { n.vx = 0; n.vy = 0; return; }
|