|
@@ -370,8 +370,8 @@
|
|
|
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
|
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
|
|
import * as d3 from 'd3'
|
|
import * as d3 from 'd3'
|
|
|
import { useGraphStore } from '../stores/graph'
|
|
import { useGraphStore } from '../stores/graph'
|
|
|
-import { getNodeStyle, applyNodeShape, dimColors } from '../config/nodeStyle'
|
|
|
|
|
-import { getEdgeStyle, edgeTypeColors } from '../config/edgeStyle'
|
|
|
|
|
|
|
+import { getNodeStyle, applyNodeShape, getDimColors } from '../config/nodeStyle'
|
|
|
|
|
+import { getEdgeStyle, getEdgeTypeColors } from '../config/edgeStyle'
|
|
|
import { applyHighlight, applyHoverHighlight } from '../utils/highlight'
|
|
import { applyHighlight, applyHoverHighlight } from '../utils/highlight'
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
const props = defineProps({
|
|
@@ -404,6 +404,10 @@ const hiddenNodeFields = ['index', 'x', 'y', 'vx', 'vy', 'fx', 'fy', 'children',
|
|
|
|
|
|
|
|
const store = useGraphStore()
|
|
const store = useGraphStore()
|
|
|
|
|
|
|
|
|
|
+// 主题相关的颜色(响应式)
|
|
|
|
|
+const dimColors = computed(() => getDimColors(store.theme))
|
|
|
|
|
+const edgeTypeColors = computed(() => getEdgeTypeColors(store.theme))
|
|
|
|
|
+
|
|
|
const containerRef = ref(null)
|
|
const containerRef = ref(null)
|
|
|
const svgRef = ref(null)
|
|
const svgRef = ref(null)
|
|
|
|
|
|
|
@@ -577,7 +581,7 @@ const sortedMatchEdges = computed(() => {
|
|
|
|
|
|
|
|
// 获取匹配边的分数颜色
|
|
// 获取匹配边的分数颜色
|
|
|
function getScoreColor(score) {
|
|
function getScoreColor(score) {
|
|
|
- return getEdgeStyle({ type: '匹配', score }).color
|
|
|
|
|
|
|
+ return getEdgeStyle({ type: '匹配', score }, { theme: store.theme }).color
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 获取源节点颜色(帖子域节点)
|
|
// 获取源节点颜色(帖子域节点)
|
|
@@ -609,13 +613,13 @@ const displayNode = computed(() => {
|
|
|
// 显示节点的样式
|
|
// 显示节点的样式
|
|
|
const displayNodeStyle = computed(() => {
|
|
const displayNodeStyle = computed(() => {
|
|
|
if (!displayNode.value) return { color: '#888', shape: 'circle', hollow: false }
|
|
if (!displayNode.value) return { color: '#888', shape: 'circle', hollow: false }
|
|
|
- return getNodeStyle(displayNode.value)
|
|
|
|
|
|
|
+ return getNodeStyle(displayNode.value, { theme: store.theme })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
// 选中节点的样式(兼容旧代码)
|
|
// 选中节点的样式(兼容旧代码)
|
|
|
const selectedNodeStyle = computed(() => {
|
|
const selectedNodeStyle = computed(() => {
|
|
|
if (!store.selectedNode) return { color: '#888', shape: 'circle', hollow: false }
|
|
if (!store.selectedNode) return { color: '#888', shape: 'circle', hollow: false }
|
|
|
- return getNodeStyle(store.selectedNode)
|
|
|
|
|
|
|
+ return getNodeStyle(store.selectedNode, { theme: store.theme })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
// 选中节点的颜色(兼容)
|
|
// 选中节点的颜色(兼容)
|
|
@@ -914,7 +918,7 @@ function renderTree() {
|
|
|
const el = d3.select(this)
|
|
const el = d3.select(this)
|
|
|
// 帖子树节点属于帖子域(空心)
|
|
// 帖子树节点属于帖子域(空心)
|
|
|
d.data.domain = '帖子'
|
|
d.data.domain = '帖子'
|
|
|
- const style = getNodeStyle(d)
|
|
|
|
|
|
|
+ const style = getNodeStyle(d, { theme: store.theme })
|
|
|
applyNodeShape(el, style).attr('class', 'tree-shape')
|
|
applyNodeShape(el, style).attr('class', 'tree-shape')
|
|
|
nodeElements[d.data.id] = { element: this, x: d.x + 50, y: d.y + 25 }
|
|
nodeElements[d.data.id] = { element: this, x: d.x + 50, y: d.y + 25 }
|
|
|
})
|
|
})
|
|
@@ -924,9 +928,9 @@ function renderTree() {
|
|
|
.attr('dy', d => d.children ? -10 : 4)
|
|
.attr('dy', d => d.children ? -10 : 4)
|
|
|
.attr('dx', d => d.children ? 0 : 10)
|
|
.attr('dx', d => d.children ? 0 : 10)
|
|
|
.attr('text-anchor', d => d.children ? 'middle' : 'start')
|
|
.attr('text-anchor', d => d.children ? 'middle' : 'start')
|
|
|
- .attr('fill', d => getNodeStyle(d).text.fill)
|
|
|
|
|
- .attr('font-size', d => getNodeStyle(d).text.fontSize)
|
|
|
|
|
- .attr('font-weight', d => getNodeStyle(d).text.fontWeight)
|
|
|
|
|
|
|
+ .attr('fill', d => getNodeStyle(d, { theme: store.theme }).text.fill)
|
|
|
|
|
+ .attr('font-size', d => getNodeStyle(d, { theme: store.theme }).text.fontSize)
|
|
|
|
|
+ .attr('font-weight', d => getNodeStyle(d, { theme: store.theme }).text.fontWeight)
|
|
|
.text(d => {
|
|
.text(d => {
|
|
|
const name = d.data.name
|
|
const name = d.data.name
|
|
|
const maxLen = 10
|
|
const maxLen = 10
|
|
@@ -1037,10 +1041,10 @@ function renderMatchLayer(contentG, root, baseTreeHeight) {
|
|
|
.join('path')
|
|
.join('path')
|
|
|
.attr('class', 'match-link')
|
|
.attr('class', 'match-link')
|
|
|
.attr('fill', 'none')
|
|
.attr('fill', 'none')
|
|
|
- .attr('stroke', d => getEdgeStyle({ type: '匹配', score: d.score }).color)
|
|
|
|
|
- .attr('stroke-opacity', d => getEdgeStyle({ type: '匹配', score: d.score }).opacity)
|
|
|
|
|
- .attr('stroke-width', d => getEdgeStyle({ type: '匹配', score: d.score }).strokeWidth)
|
|
|
|
|
- .attr('stroke-dasharray', d => getEdgeStyle({ type: '匹配', score: d.score }).strokeDasharray)
|
|
|
|
|
|
|
+ .attr('stroke', d => getEdgeStyle({ type: '匹配', score: d.score }, { theme: store.theme }).color)
|
|
|
|
|
+ .attr('stroke-opacity', d => getEdgeStyle({ type: '匹配', score: d.score }, { theme: store.theme }).opacity)
|
|
|
|
|
+ .attr('stroke-width', d => getEdgeStyle({ type: '匹配', score: d.score }, { theme: store.theme }).strokeWidth)
|
|
|
|
|
+ .attr('stroke-dasharray', d => getEdgeStyle({ type: '匹配', score: d.score }, { theme: store.theme }).strokeDasharray)
|
|
|
.style('cursor', 'pointer')
|
|
.style('cursor', 'pointer')
|
|
|
.on('mouseenter', (e, d) => {
|
|
.on('mouseenter', (e, d) => {
|
|
|
// hover 边,显示边详情并高亮
|
|
// hover 边,显示边详情并高亮
|
|
@@ -1073,7 +1077,7 @@ function renderMatchLayer(contentG, root, baseTreeHeight) {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
// 绘制分数标签(使用 data binding)
|
|
// 绘制分数标签(使用 data binding)
|
|
|
- const scoreData = matchLinksData.filter(d => getEdgeStyle({ type: '匹配', score: d.score }).scoreText)
|
|
|
|
|
|
|
+ const scoreData = matchLinksData.filter(d => getEdgeStyle({ type: '匹配', score: d.score }, { theme: store.theme }).scoreText)
|
|
|
|
|
|
|
|
const scoreGroups = matchLinksG.selectAll('.match-score')
|
|
const scoreGroups = matchLinksG.selectAll('.match-score')
|
|
|
.data(scoreData)
|
|
.data(scoreData)
|
|
@@ -1097,9 +1101,9 @@ function renderMatchLayer(contentG, root, baseTreeHeight) {
|
|
|
scoreGroups.append('text')
|
|
scoreGroups.append('text')
|
|
|
.attr('text-anchor', 'middle')
|
|
.attr('text-anchor', 'middle')
|
|
|
.attr('dy', '0.35em')
|
|
.attr('dy', '0.35em')
|
|
|
- .attr('fill', d => getEdgeStyle({ type: '匹配', score: d.score }).color)
|
|
|
|
|
|
|
+ .attr('fill', d => getEdgeStyle({ type: '匹配', score: d.score }, { theme: store.theme }).color)
|
|
|
.attr('font-size', '8px')
|
|
.attr('font-size', '8px')
|
|
|
- .text(d => getEdgeStyle({ type: '匹配', score: d.score }).scoreText)
|
|
|
|
|
|
|
+ .text(d => getEdgeStyle({ type: '匹配', score: d.score }, { theme: store.theme }).scoreText)
|
|
|
|
|
|
|
|
// 绘制匹配节点
|
|
// 绘制匹配节点
|
|
|
const matchNodesG = contentG.append('g').attr('class', 'match-nodes')
|
|
const matchNodesG = contentG.append('g').attr('class', 'match-nodes')
|
|
@@ -1115,7 +1119,7 @@ function renderMatchLayer(contentG, root, baseTreeHeight) {
|
|
|
// 匹配节点形状(使用统一配置)
|
|
// 匹配节点形状(使用统一配置)
|
|
|
matchNodes.each(function(d) {
|
|
matchNodes.each(function(d) {
|
|
|
const el = d3.select(this)
|
|
const el = d3.select(this)
|
|
|
- const style = getNodeStyle(d, { isMatch: true })
|
|
|
|
|
|
|
+ const style = getNodeStyle(d, { isMatch: true, theme: store.theme })
|
|
|
applyNodeShape(el, style).attr('class', 'tree-shape')
|
|
applyNodeShape(el, style).attr('class', 'tree-shape')
|
|
|
nodeElements[d.id] = { element: this, x: d.x + 50, y: d.y + 25 }
|
|
nodeElements[d.id] = { element: this, x: d.x + 50, y: d.y + 25 }
|
|
|
})
|
|
})
|
|
@@ -1125,9 +1129,9 @@ function renderMatchLayer(contentG, root, baseTreeHeight) {
|
|
|
.attr('dy', 4)
|
|
.attr('dy', 4)
|
|
|
.attr('dx', 10)
|
|
.attr('dx', 10)
|
|
|
.attr('text-anchor', 'start')
|
|
.attr('text-anchor', 'start')
|
|
|
- .attr('fill', d => getNodeStyle(d, { isMatch: true }).text.fill)
|
|
|
|
|
- .attr('font-size', d => getNodeStyle(d, { isMatch: true }).text.fontSize)
|
|
|
|
|
- .attr('font-weight', d => getNodeStyle(d, { isMatch: true }).text.fontWeight)
|
|
|
|
|
|
|
+ .attr('fill', d => getNodeStyle(d, { isMatch: true, theme: store.theme }).text.fill)
|
|
|
|
|
+ .attr('font-size', d => getNodeStyle(d, { isMatch: true, theme: store.theme }).text.fontSize)
|
|
|
|
|
+ .attr('font-weight', d => getNodeStyle(d, { isMatch: true, theme: store.theme }).text.fontWeight)
|
|
|
.text(d => {
|
|
.text(d => {
|
|
|
const name = d.name
|
|
const name = d.name
|
|
|
const maxLen = 10
|
|
const maxLen = 10
|
|
@@ -1310,10 +1314,10 @@ function renderWalkedLayer() {
|
|
|
.join('path')
|
|
.join('path')
|
|
|
.attr('class', 'walked-link')
|
|
.attr('class', 'walked-link')
|
|
|
.attr('fill', 'none')
|
|
.attr('fill', 'none')
|
|
|
- .attr('stroke', d => getEdgeStyle({ type: d.type, score: d.score }).color)
|
|
|
|
|
- .attr('stroke-opacity', d => getEdgeStyle({ type: d.type, score: d.score }).opacity)
|
|
|
|
|
- .attr('stroke-width', d => getEdgeStyle({ type: d.type, score: d.score }).strokeWidth)
|
|
|
|
|
- .attr('stroke-dasharray', d => getEdgeStyle({ type: d.type, score: d.score }).strokeDasharray)
|
|
|
|
|
|
|
+ .attr('stroke', d => getEdgeStyle({ type: d.type, score: d.score }, { theme: store.theme }).color)
|
|
|
|
|
+ .attr('stroke-opacity', d => getEdgeStyle({ type: d.type, score: d.score }, { theme: store.theme }).opacity)
|
|
|
|
|
+ .attr('stroke-width', d => getEdgeStyle({ type: d.type, score: d.score }, { theme: store.theme }).strokeWidth)
|
|
|
|
|
+ .attr('stroke-dasharray', d => getEdgeStyle({ type: d.type, score: d.score }, { theme: store.theme }).strokeDasharray)
|
|
|
.style('cursor', 'pointer')
|
|
.style('cursor', 'pointer')
|
|
|
.on('mouseenter', (e, d) => {
|
|
.on('mouseenter', (e, d) => {
|
|
|
// hover 边,显示边详情并高亮
|
|
// hover 边,显示边详情并高亮
|
|
@@ -1354,7 +1358,7 @@ function renderWalkedLayer() {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
// 绘制分数标签
|
|
// 绘制分数标签
|
|
|
- const scoreData = edgesData.filter(d => getEdgeStyle({ type: d.type, score: d.score }).scoreText)
|
|
|
|
|
|
|
+ const scoreData = edgesData.filter(d => getEdgeStyle({ type: d.type, score: d.score }, { theme: store.theme }).scoreText)
|
|
|
const scoreGroups = walkedG.selectAll('.walked-score')
|
|
const scoreGroups = walkedG.selectAll('.walked-score')
|
|
|
.data(scoreData)
|
|
.data(scoreData)
|
|
|
.join('g')
|
|
.join('g')
|
|
@@ -1372,9 +1376,9 @@ function renderWalkedLayer() {
|
|
|
|
|
|
|
|
scoreGroups.append('text')
|
|
scoreGroups.append('text')
|
|
|
.attr('text-anchor', 'middle').attr('dy', '0.35em')
|
|
.attr('text-anchor', 'middle').attr('dy', '0.35em')
|
|
|
- .attr('fill', d => getEdgeStyle({ type: d.type, score: d.score }).color)
|
|
|
|
|
|
|
+ .attr('fill', d => getEdgeStyle({ type: d.type, score: d.score }, { theme: store.theme }).color)
|
|
|
.attr('font-size', '8px')
|
|
.attr('font-size', '8px')
|
|
|
- .text(d => getEdgeStyle({ type: d.type, score: d.score }).scoreText)
|
|
|
|
|
|
|
+ .text(d => getEdgeStyle({ type: d.type, score: d.score }, { theme: store.theme }).scoreText)
|
|
|
|
|
|
|
|
// ========== 绘制新节点(nodePositions 中的都是新节点) ==========
|
|
// ========== 绘制新节点(nodePositions 中的都是新节点) ==========
|
|
|
const newNodesData = []
|
|
const newNodesData = []
|
|
@@ -1405,7 +1409,7 @@ function renderWalkedLayer() {
|
|
|
// 节点形状
|
|
// 节点形状
|
|
|
walkedNodeGroups.each(function(d) {
|
|
walkedNodeGroups.each(function(d) {
|
|
|
const el = d3.select(this)
|
|
const el = d3.select(this)
|
|
|
- const style = getNodeStyle(d)
|
|
|
|
|
|
|
+ const style = getNodeStyle(d, { theme: store.theme })
|
|
|
applyNodeShape(el, style).attr('class', 'walked-shape')
|
|
applyNodeShape(el, style).attr('class', 'walked-shape')
|
|
|
nodeElements[d.id] = { element: this, x: d.x + 50, y: d.y + 25 }
|
|
nodeElements[d.id] = { element: this, x: d.x + 50, y: d.y + 25 }
|
|
|
})
|
|
})
|
|
@@ -1413,9 +1417,9 @@ function renderWalkedLayer() {
|
|
|
// 节点标签
|
|
// 节点标签
|
|
|
walkedNodeGroups.append('text')
|
|
walkedNodeGroups.append('text')
|
|
|
.attr('dy', 4).attr('dx', 10).attr('text-anchor', 'start')
|
|
.attr('dy', 4).attr('dx', 10).attr('text-anchor', 'start')
|
|
|
- .attr('fill', d => getNodeStyle(d).text.fill)
|
|
|
|
|
- .attr('font-size', d => getNodeStyle(d).text.fontSize)
|
|
|
|
|
- .attr('font-weight', d => getNodeStyle(d).text.fontWeight)
|
|
|
|
|
|
|
+ .attr('fill', d => getNodeStyle(d, { theme: store.theme }).text.fill)
|
|
|
|
|
+ .attr('font-size', d => getNodeStyle(d, { theme: store.theme }).text.fontSize)
|
|
|
|
|
+ .attr('font-weight', d => getNodeStyle(d, { theme: store.theme }).text.fontWeight)
|
|
|
.text(d => {
|
|
.text(d => {
|
|
|
const name = d.name
|
|
const name = d.name
|
|
|
const maxLen = 10
|
|
const maxLen = 10
|
|
@@ -2082,6 +2086,13 @@ watch(() => store.currentPostGraph, () => {
|
|
|
})
|
|
})
|
|
|
}, { immediate: false })
|
|
}, { immediate: false })
|
|
|
|
|
|
|
|
|
|
+// 监听主题变化,重新渲染
|
|
|
|
|
+watch(() => store.theme, () => {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ renderTree()
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
// 监听 selectedPostIndex 变化,同步下拉框
|
|
// 监听 selectedPostIndex 变化,同步下拉框
|
|
|
watch(() => store.selectedPostIndex, (newIdx) => {
|
|
watch(() => store.selectedPostIndex, (newIdx) => {
|
|
|
selectedPostIdx.value = newIdx
|
|
selectedPostIdx.value = newIdx
|