|
|
@@ -958,17 +958,17 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
</div>
|
|
|
<div class="cascade-edges">
|
|
|
<div class="cascade-edge-group active" data-level="1">
|
|
|
- <div class="cascade-edge-group-header">L1 <span class="header-actions"><label class="select-all"><input type="checkbox" data-level="1" data-select-all checked>全选</label><span class="invert-btn" data-level="1" onclick="invertSelection(1)">反选</span><span class="reset-btn" onclick="resetSelection()">重置</span></span></div>
|
|
|
- <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="属于" checked><span class="edge-color" style="background:#9b59b6"></span>属于</label>
|
|
|
- <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="包含" checked><span class="edge-color" style="background:#ffb6c1"></span>包含</label>
|
|
|
- <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="分类共现(跨点)" checked><span class="edge-color" style="background:#2ecc71"></span>跨点共现</label>
|
|
|
- <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="分类共现(点内)" checked><span class="edge-color" style="background:#3498db"></span>点内共现</label>
|
|
|
- <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="标签共现" checked><span class="edge-color" style="background:#f39c12"></span>标签共现</label>
|
|
|
+ <div class="cascade-edge-group-header">L1 <span class="header-actions"><label class="select-all"><input type="checkbox" data-level="1" data-select-all>全选</label><span class="invert-btn" data-level="1" onclick="invertSelection(1)">反选</span><span class="reset-btn" onclick="resetSelection()">重置</span></span></div>
|
|
|
+ <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="属于"><span class="edge-color" style="background:#9b59b6"></span>属于</label>
|
|
|
+ <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="包含"><span class="edge-color" style="background:#ffb6c1"></span>包含</label>
|
|
|
+ <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="分类共现(跨点)"><span class="edge-color" style="background:#2ecc71"></span>跨点共现</label>
|
|
|
+ <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="分类共现(点内)"><span class="edge-color" style="background:#3498db"></span>点内共现</label>
|
|
|
+ <label class="cascade-edge-item"><input type="checkbox" data-level="1" data-type="标签共现"><span class="edge-color" style="background:#f39c12"></span>标签共现</label>
|
|
|
</div>
|
|
|
<div class="cascade-edge-group" data-level="2">
|
|
|
<div class="cascade-edge-group-header">L2 <span class="header-actions"><label class="select-all"><input type="checkbox" data-level="2" data-select-all>全选</label><span class="invert-btn" data-level="2" onclick="invertSelection(2)">反选</span></span></div>
|
|
|
<label class="cascade-edge-item"><input type="checkbox" data-level="2" data-type="属于"><span class="edge-color" style="background:#9b59b6"></span>属于</label>
|
|
|
- <label class="cascade-edge-item"><input type="checkbox" data-level="2" data-type="包含" checked><span class="edge-color" style="background:#ffb6c1"></span>包含</label>
|
|
|
+ <label class="cascade-edge-item"><input type="checkbox" data-level="2" data-type="包含"><span class="edge-color" style="background:#ffb6c1"></span>包含</label>
|
|
|
<label class="cascade-edge-item"><input type="checkbox" data-level="2" data-type="分类共现(跨点)"><span class="edge-color" style="background:#2ecc71"></span>跨点共现</label>
|
|
|
<label class="cascade-edge-item"><input type="checkbox" data-level="2" data-type="分类共现(点内)"><span class="edge-color" style="background:#3498db"></span>点内共现</label>
|
|
|
<label class="cascade-edge-item"><input type="checkbox" data-level="2" data-type="标签共现"><span class="edge-color" style="background:#f39c12"></span>标签共现</label>
|
|
|
@@ -976,7 +976,7 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
<div class="cascade-edge-group" data-level="3">
|
|
|
<div class="cascade-edge-group-header">L3 <span class="header-actions"><label class="select-all"><input type="checkbox" data-level="3" data-select-all>全选</label><span class="invert-btn" data-level="3" onclick="invertSelection(3)">反选</span></span></div>
|
|
|
<label class="cascade-edge-item"><input type="checkbox" data-level="3" data-type="属于"><span class="edge-color" style="background:#9b59b6"></span>属于</label>
|
|
|
- <label class="cascade-edge-item"><input type="checkbox" data-level="3" data-type="包含" checked><span class="edge-color" style="background:#ffb6c1"></span>包含</label>
|
|
|
+ <label class="cascade-edge-item"><input type="checkbox" data-level="3" data-type="包含"><span class="edge-color" style="background:#ffb6c1"></span>包含</label>
|
|
|
<label class="cascade-edge-item"><input type="checkbox" data-level="3" data-type="分类共现(跨点)"><span class="edge-color" style="background:#2ecc71"></span>跨点共现</label>
|
|
|
<label class="cascade-edge-item"><input type="checkbox" data-level="3" data-type="分类共现(点内)"><span class="edge-color" style="background:#3498db"></span>点内共现</label>
|
|
|
<label class="cascade-edge-item"><input type="checkbox" data-level="3" data-type="标签共现"><span class="edge-color" style="background:#f39c12"></span>标签共现</label>
|
|
|
@@ -2045,9 +2045,21 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
|
|
|
if (postTree && postTree.root) {{
|
|
|
// D3树布局(只用于帖子→维度→点→标签)
|
|
|
+ // separation 根据节点名称长度动态计算
|
|
|
const postTreeLayout = d3.tree()
|
|
|
.nodeSize([50, 80])
|
|
|
- .separation((a, b) => a.parent === b.parent ? 1.2 : 1.5);
|
|
|
+ .separation((a, b) => {{
|
|
|
+ const nameA = a.data.name || '';
|
|
|
+ const nameB = b.data.name || '';
|
|
|
+ const charWidth = 9;
|
|
|
+ const baseGap = 30;
|
|
|
+ // 根据两个节点的文字长度计算间距
|
|
|
+ const widthA = nameA.length * charWidth / 2;
|
|
|
+ const widthB = nameB.length * charWidth / 2;
|
|
|
+ const minSep = (widthA + widthB + baseGap) / 50; // 除以 nodeSize[0]
|
|
|
+ const defaultSep = a.parent === b.parent ? 1.2 : 1.5;
|
|
|
+ return Math.max(minSep, defaultSep);
|
|
|
+ }});
|
|
|
|
|
|
// 过滤掉人设节点,只保留树结构
|
|
|
function filterTreeData(node) {{
|
|
|
@@ -2082,10 +2094,11 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
}} else if (d.data.nodeType === "标签") {{
|
|
|
d.y = targetTagY;
|
|
|
// 记录标签节点位置(同一个ID可能有多个位置)
|
|
|
+ // 保存 d 的引用,以便后续 spreadTagPositions 可以更新节点位置
|
|
|
if (!tagNodePositions[d.data.id]) {{
|
|
|
tagNodePositions[d.data.id] = [];
|
|
|
}}
|
|
|
- tagNodePositions[d.data.id].push({{ x: d.x, y: d.y, data: d.data }});
|
|
|
+ tagNodePositions[d.data.id].push({{ x: d.x, y: d.y, data: d.data, node: d }});
|
|
|
}}
|
|
|
}});
|
|
|
|
|
|
@@ -2229,6 +2242,79 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
}});
|
|
|
}});
|
|
|
|
|
|
+ // 对帖子标签层应用防重叠(展平后处理)
|
|
|
+ function spreadTagPositions(tagPositions, baseGap = 30, charWidth = 9) {{
|
|
|
+ // 展平所有标签位置
|
|
|
+ const allTags = [];
|
|
|
+ Object.entries(tagPositions).forEach(([id, posArray]) => {{
|
|
|
+ posArray.forEach((pos, idx) => {{
|
|
|
+ const name = pos.data ? (pos.data.name || '') : '';
|
|
|
+ allTags.push({{ id, idx, x: pos.x, y: pos.y, nameLen: name.length, pos }});
|
|
|
+ }});
|
|
|
+ }});
|
|
|
+
|
|
|
+ // 按y分层
|
|
|
+ const layers = {{}};
|
|
|
+ allTags.forEach(tag => {{
|
|
|
+ const y = tag.y;
|
|
|
+ if (!layers[y]) layers[y] = [];
|
|
|
+ layers[y].push(tag);
|
|
|
+ }});
|
|
|
+
|
|
|
+ // 对每层进行防重叠处理
|
|
|
+ Object.values(layers).forEach(layerTags => {{
|
|
|
+ if (layerTags.length < 2) return;
|
|
|
+
|
|
|
+ // 按x排序
|
|
|
+ layerTags.sort((a, b) => a.x - b.x);
|
|
|
+
|
|
|
+ // 多轮迭代
|
|
|
+ for (let iter = 0; iter < 5; iter++) {{
|
|
|
+ let hasOverlap = false;
|
|
|
+ for (let i = 1; i < layerTags.length; i++) {{
|
|
|
+ const prev = layerTags[i - 1];
|
|
|
+ const curr = layerTags[i];
|
|
|
+
|
|
|
+ const prevHalfWidth = (prev.nameLen * charWidth) / 2 + 10;
|
|
|
+ const currHalfWidth = (curr.nameLen * charWidth) / 2 + 10;
|
|
|
+ const minGap = prevHalfWidth + currHalfWidth + baseGap;
|
|
|
+
|
|
|
+ const gap = curr.x - prev.x;
|
|
|
+
|
|
|
+ if (gap < minGap) {{
|
|
|
+ hasOverlap = true;
|
|
|
+ const shift = (minGap - gap) / 2;
|
|
|
+ prev.x -= shift;
|
|
|
+ curr.x += shift;
|
|
|
+
|
|
|
+ // 更新原始位置
|
|
|
+ prev.pos.x = prev.x;
|
|
|
+ curr.pos.x = curr.x;
|
|
|
+ // 同步更新节点对象的 x(用于后续更新 DOM 位置)
|
|
|
+ if (prev.pos.node) prev.pos.node.x = prev.x - postTreeOffsetX;
|
|
|
+ if (curr.pos.node) curr.pos.node.x = curr.x - postTreeOffsetX;
|
|
|
+ }}
|
|
|
+ }}
|
|
|
+ if (!hasOverlap) break;
|
|
|
+ }}
|
|
|
+ }});
|
|
|
+ }}
|
|
|
+
|
|
|
+ spreadTagPositions(tagNodePositions, 25, 9);
|
|
|
+
|
|
|
+ // 更新标签节点的 DOM 位置(根据 spreadTagPositions 调整后的 d.x)
|
|
|
+ treeNodes.filter(d => d.data.nodeType === "标签")
|
|
|
+ .attr("transform", d => `translate(${{d.x}},${{d.y}})`);
|
|
|
+
|
|
|
+ // 更新树边的路径(因为标签节点位置可能变了)
|
|
|
+ treeLinks.attr("d", d => {{
|
|
|
+ const sourceY = d.source.data.isRoot ? d.source.y + cardHeight/2 : d.source.y;
|
|
|
+ return `M${{d.source.x}},${{sourceY}}
|
|
|
+ C${{d.source.x}},${{(sourceY + d.target.y) / 2}}
|
|
|
+ ${{d.target.x}},${{(sourceY + d.target.y) / 2}}
|
|
|
+ ${{d.target.x}},${{d.target.y}}`;
|
|
|
+ }});
|
|
|
+
|
|
|
// ===== 下半部分:三分图(标签 ↔ 人设_1层 ↔ 人设_2层)=====
|
|
|
// 复用圆形图的节点数据,但只取人设节点(去重)
|
|
|
const personaLayer1Nodes = nodes.filter(n => getNodeLayer(n) === 1);
|
|
|
@@ -2238,7 +2324,12 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
const crossLayerLinks = links.filter(link => {{
|
|
|
const srcLayer = getNodeLayer(link.source);
|
|
|
const tgtLayer = getNodeLayer(link.target);
|
|
|
- return srcLayer !== tgtLayer;
|
|
|
+ if (srcLayer === tgtLayer) return false;
|
|
|
+ // 1-2层之间只显示"属于"边(从上到下),过滤掉"包含"边(从下到上)
|
|
|
+ if ((srcLayer === 1 && tgtLayer === 2) || (srcLayer === 2 && tgtLayer === 1)) {{
|
|
|
+ if (link.type === "包含") return false;
|
|
|
+ }}
|
|
|
+ return true;
|
|
|
}});
|
|
|
|
|
|
console.log("层间边数:", crossLayerLinks.length, "人设1层:", personaLayer1Nodes.length, "人设2层:", personaLayer2Nodes.length);
|
|
|
@@ -2300,8 +2391,65 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
}}
|
|
|
}});
|
|
|
|
|
|
+ // 防重叠:分散同层中x坐标太近的节点(根据文字长度动态计算间距)
|
|
|
+ function spreadOverlappingNodes(positions, baseGap = 40, charWidth = 8) {{
|
|
|
+ // 按y分层
|
|
|
+ const layers = {{}};
|
|
|
+ Object.entries(positions).forEach(([id, pos]) => {{
|
|
|
+ const y = pos.y;
|
|
|
+ if (!layers[y]) layers[y] = [];
|
|
|
+ // 获取节点名称长度
|
|
|
+ const name = pos.node ? (pos.node.节点名称 || pos.node.name || '') : '';
|
|
|
+ layers[y].push({{ id, ...pos, nameLen: name.length }});
|
|
|
+ }});
|
|
|
+
|
|
|
+ // 对每层进行防重叠处理
|
|
|
+ Object.values(layers).forEach(layerNodes => {{
|
|
|
+ if (layerNodes.length < 2) return;
|
|
|
+
|
|
|
+ // 按x排序
|
|
|
+ layerNodes.sort((a, b) => a.x - b.x);
|
|
|
+
|
|
|
+ // 多轮迭代确保所有节点都不重叠
|
|
|
+ for (let iter = 0; iter < 5; iter++) {{
|
|
|
+ let hasOverlap = false;
|
|
|
+ for (let i = 1; i < layerNodes.length; i++) {{
|
|
|
+ const prev = layerNodes[i - 1];
|
|
|
+ const curr = layerNodes[i];
|
|
|
+
|
|
|
+ // 根据两个节点的文字长度计算最小间距
|
|
|
+ const prevHalfWidth = (prev.nameLen * charWidth) / 2 + 10;
|
|
|
+ const currHalfWidth = (curr.nameLen * charWidth) / 2 + 10;
|
|
|
+ const minGap = prevHalfWidth + currHalfWidth + baseGap;
|
|
|
+
|
|
|
+ const gap = curr.x - prev.x;
|
|
|
+
|
|
|
+ if (gap < minGap) {{
|
|
|
+ hasOverlap = true;
|
|
|
+ // 需要分散:向两侧推开
|
|
|
+ const shift = (minGap - gap) / 2;
|
|
|
+ prev.x -= shift;
|
|
|
+ curr.x += shift;
|
|
|
+
|
|
|
+ // 更新原始位置
|
|
|
+ positions[prev.id].x = prev.x;
|
|
|
+ positions[curr.id].x = curr.x;
|
|
|
+ }}
|
|
|
+ }}
|
|
|
+ if (!hasOverlap) break;
|
|
|
+ }}
|
|
|
+ }});
|
|
|
+ }}
|
|
|
+
|
|
|
+ // 对人设节点应用防重叠
|
|
|
+ spreadOverlappingNodes(personaNodePositions, 30, 9);
|
|
|
+
|
|
|
// 合并所有节点位置(标签 + 人设)
|
|
|
- const allGraphNodePositions = {{ ...tagNodePositions }};
|
|
|
+ const allGraphNodePositions = {{}};
|
|
|
+ // 标签位置:取第一个位置用于合并(边绘制时会用所有位置)
|
|
|
+ Object.keys(tagNodePositions).forEach(id => {{
|
|
|
+ allGraphNodePositions[id] = tagNodePositions[id][0];
|
|
|
+ }});
|
|
|
Object.keys(personaNodePositions).forEach(id => {{
|
|
|
allGraphNodePositions[id] = personaNodePositions[id];
|
|
|
}});
|
|
|
@@ -2318,6 +2466,7 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
}};
|
|
|
|
|
|
// 展开边数据:如果标签有多个位置,为每个位置创建一条边
|
|
|
+ // 边直接引用位置对象,绘制时动态获取 x, y
|
|
|
const expandedLinks = [];
|
|
|
crossLayerLinks.forEach(link => {{
|
|
|
const srcId = link.source.id;
|
|
|
@@ -2327,13 +2476,13 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
|
|
|
// 源是层0(帖子标签),可能有多个位置
|
|
|
if (srcLayer === 0 && tagNodePositions[srcId]) {{
|
|
|
- tagNodePositions[srcId].forEach((srcPos, idx) => {{
|
|
|
- const tgtPos = personaNodePositions[tgtId];
|
|
|
- if (tgtPos) {{
|
|
|
+ tagNodePositions[srcId].forEach((srcNode, idx) => {{
|
|
|
+ const tgtNode = personaNodePositions[tgtId];
|
|
|
+ if (tgtNode) {{
|
|
|
expandedLinks.push({{
|
|
|
...link,
|
|
|
- srcPos: srcPos,
|
|
|
- tgtPos: tgtPos,
|
|
|
+ srcNode: srcNode, // 直接引用位置对象
|
|
|
+ tgtNode: tgtNode,
|
|
|
key: `${{srcId}}_${{idx}}_${{tgtId}}`
|
|
|
}});
|
|
|
}}
|
|
|
@@ -2341,13 +2490,13 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
}}
|
|
|
// 目标是层0(帖子标签),可能有多个位置
|
|
|
else if (tgtLayer === 0 && tagNodePositions[tgtId]) {{
|
|
|
- tagNodePositions[tgtId].forEach((tgtPos, idx) => {{
|
|
|
- const srcPos = personaNodePositions[srcId];
|
|
|
- if (srcPos) {{
|
|
|
+ tagNodePositions[tgtId].forEach((tgtNode, idx) => {{
|
|
|
+ const srcNode = personaNodePositions[srcId];
|
|
|
+ if (srcNode) {{
|
|
|
expandedLinks.push({{
|
|
|
...link,
|
|
|
- srcPos: srcPos,
|
|
|
- tgtPos: tgtPos,
|
|
|
+ srcNode: srcNode,
|
|
|
+ tgtNode: tgtNode,
|
|
|
key: `${{srcId}}_${{tgtId}}_${{idx}}`
|
|
|
}});
|
|
|
}}
|
|
|
@@ -2355,13 +2504,13 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
}}
|
|
|
// 两端都不是层0,正常处理
|
|
|
else {{
|
|
|
- const srcPos = personaNodePositions[srcId];
|
|
|
- const tgtPos = personaNodePositions[tgtId];
|
|
|
- if (srcPos && tgtPos) {{
|
|
|
+ const srcNode = personaNodePositions[srcId];
|
|
|
+ const tgtNode = personaNodePositions[tgtId];
|
|
|
+ if (srcNode && tgtNode) {{
|
|
|
expandedLinks.push({{
|
|
|
...link,
|
|
|
- srcPos: srcPos,
|
|
|
- tgtPos: tgtPos,
|
|
|
+ srcNode: srcNode,
|
|
|
+ tgtNode: tgtNode,
|
|
|
key: `${{srcId}}_${{tgtId}}`
|
|
|
}});
|
|
|
}}
|
|
|
@@ -2371,9 +2520,38 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
console.log("展开后边数:", expandedLinks.length);
|
|
|
|
|
|
// 绘制层间边(标签→人设、人设→人设扩展)
|
|
|
+ // 去重:同一对节点之间相同类型的边只保留一条
|
|
|
+ const seenEdges = new Set();
|
|
|
+ const dedupedLinks = expandedLinks.filter(link => {{
|
|
|
+ const srcId = link.source.id || link.source;
|
|
|
+ const tgtId = link.target.id || link.target;
|
|
|
+ const edgeKey = [srcId, tgtId].sort().join("|") + "|" + link.type;
|
|
|
+ if (seenEdges.has(edgeKey)) return false;
|
|
|
+ seenEdges.add(edgeKey);
|
|
|
+ return true;
|
|
|
+ }});
|
|
|
+
|
|
|
+ // 统计每对节点之间的边数,为多边分配不同曲线偏移
|
|
|
+ const edgePairCount = {{}};
|
|
|
+ const edgePairIndex = {{}};
|
|
|
+ dedupedLinks.forEach(link => {{
|
|
|
+ const srcId = link.source.id || link.source;
|
|
|
+ const tgtId = link.target.id || link.target;
|
|
|
+ const pairKey = [srcId, tgtId].sort().join("|");
|
|
|
+ edgePairCount[pairKey] = (edgePairCount[pairKey] || 0) + 1;
|
|
|
+ }});
|
|
|
+ dedupedLinks.forEach(link => {{
|
|
|
+ const srcId = link.source.id || link.source;
|
|
|
+ const tgtId = link.target.id || link.target;
|
|
|
+ const pairKey = [srcId, tgtId].sort().join("|");
|
|
|
+ if (!edgePairIndex[pairKey]) edgePairIndex[pairKey] = 0;
|
|
|
+ link._pairCount = edgePairCount[pairKey];
|
|
|
+ link._pairIndex = edgePairIndex[pairKey]++;
|
|
|
+ }});
|
|
|
+
|
|
|
const graphLinksGroup = g.append("g").attr("class", "graph-links");
|
|
|
const graphLinks = graphLinksGroup.selectAll(".graph-link")
|
|
|
- .data(expandedLinks)
|
|
|
+ .data(dedupedLinks)
|
|
|
.enter()
|
|
|
.append("g")
|
|
|
.attr("class", "graph-link-group");
|
|
|
@@ -2381,10 +2559,24 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
graphLinks.append("path")
|
|
|
.attr("class", "graph-link")
|
|
|
.attr("d", d => {{
|
|
|
- const srcPos = d.srcPos;
|
|
|
- const tgtPos = d.tgtPos;
|
|
|
- if (!srcPos || !tgtPos) return "";
|
|
|
- return `M${{srcPos.x}},${{srcPos.y}} C${{srcPos.x}},${{(srcPos.y + tgtPos.y) / 2}} ${{tgtPos.x}},${{(srcPos.y + tgtPos.y) / 2}} ${{tgtPos.x}},${{tgtPos.y}}`;
|
|
|
+ const src = d.srcNode;
|
|
|
+ const tgt = d.tgtNode;
|
|
|
+ if (!src || !tgt) return "";
|
|
|
+
|
|
|
+ // 如果只有一条边,用默认曲线
|
|
|
+ if (d._pairCount <= 1) {{
|
|
|
+ return `M${{src.x}},${{src.y}} C${{src.x}},${{(src.y + tgt.y) / 2}} ${{tgt.x}},${{(src.y + tgt.y) / 2}} ${{tgt.x}},${{tgt.y}}`;
|
|
|
+ }}
|
|
|
+
|
|
|
+ // 多条边时,根据索引计算水平偏移
|
|
|
+ const offsetStep = 25;
|
|
|
+ const totalOffset = (d._pairCount - 1) * offsetStep;
|
|
|
+ const offset = d._pairIndex * offsetStep - totalOffset / 2;
|
|
|
+
|
|
|
+ const midY = (src.y + tgt.y) / 2;
|
|
|
+ const midX = (src.x + tgt.x) / 2 + offset;
|
|
|
+
|
|
|
+ return `M${{src.x}},${{src.y}} Q${{midX}},${{midY}} ${{tgt.x}},${{tgt.y}}`;
|
|
|
}})
|
|
|
.attr("fill", "none")
|
|
|
.attr("stroke", d => graphEdgeColors[d.type] || "#9b59b6")
|
|
|
@@ -2395,8 +2587,8 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
// 在匹配边上显示相似度
|
|
|
graphLinks.filter(d => d.type && d.type.startsWith("匹配_") && d.similarity > 0)
|
|
|
.append("text")
|
|
|
- .attr("x", d => (d.srcPos.x + d.tgtPos.x) / 2)
|
|
|
- .attr("y", d => (d.srcPos.y + d.tgtPos.y) / 2)
|
|
|
+ .attr("x", d => (d.srcNode.x + d.tgtNode.x) / 2)
|
|
|
+ .attr("y", d => (d.srcNode.y + d.tgtNode.y) / 2)
|
|
|
.attr("dy", "0.35em")
|
|
|
.attr("text-anchor", "middle")
|
|
|
.attr("fill", "#fff")
|
|
|
@@ -2765,10 +2957,10 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
return getNodeLayer(sourceNode) !== getNodeLayer(targetNode);
|
|
|
}};
|
|
|
|
|
|
- // 设置跨层边的初始可见性(匹配边始终显示,其他跨层边默认隐藏)
|
|
|
+ // 设置跨层边的初始可见性(默认全部隐藏)
|
|
|
linkG.each(function(d) {{
|
|
|
if (d.type.startsWith("匹配_")) {{
|
|
|
- d3.select(this).style("display", "block"); // 匹配边始终显示
|
|
|
+ d3.select(this).style("display", "none"); // 匹配边默认隐藏
|
|
|
}} else if (isCrossLayerEdge(d) && !showCrossLayerEdges) {{
|
|
|
d3.select(this).style("display", "none");
|
|
|
}}
|
|
|
@@ -3692,19 +3884,13 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
- // 重置所有层级到默认配置(1级全选,2/3级只选包含)
|
|
|
+ // 重置所有层级到默认配置(所有层级边类型默认不选)
|
|
|
function resetSelection() {{
|
|
|
event.stopPropagation();
|
|
|
- // Level 1: 全选
|
|
|
- document.querySelectorAll('.cascade-edge-item input[data-level="1"]').forEach(cb => {{
|
|
|
- cb.checked = true;
|
|
|
- }});
|
|
|
- document.querySelector('.select-all input[data-level="1"]').checked = true;
|
|
|
-
|
|
|
- // Level 2 & 3: 只选包含
|
|
|
- [2, 3].forEach(level => {{
|
|
|
+ // 所有层级边类型默认不选
|
|
|
+ [1, 2, 3].forEach(level => {{
|
|
|
document.querySelectorAll(`.cascade-edge-item input[data-level="${{level}}"]`).forEach(cb => {{
|
|
|
- cb.checked = (cb.dataset.type === "包含");
|
|
|
+ cb.checked = false;
|
|
|
}});
|
|
|
document.querySelector(`.select-all input[data-level="${{level}}"]`).checked = false;
|
|
|
}});
|