diff --git a/src/assets/images/linkPrediction/title/analysis-title.png b/src/assets/images/linkPrediction/title/analysis-title.png index 12b296a..383f320 100644 Binary files a/src/assets/images/linkPrediction/title/analysis-title.png and b/src/assets/images/linkPrediction/title/analysis-title.png differ diff --git a/src/assets/images/linkPrediction/title/characters-hidden-interaction-analysis-title.png b/src/assets/images/linkPrediction/title/characters-hidden-interaction-analysis-title.png new file mode 100644 index 0000000..e2cc7cd Binary files /dev/null and b/src/assets/images/linkPrediction/title/characters-hidden-interaction-analysis-title.png differ diff --git a/src/assets/images/linkPrediction/title/graph2-title.png b/src/assets/images/linkPrediction/title/graph2-title.png new file mode 100644 index 0000000..800e8d0 Binary files /dev/null and b/src/assets/images/linkPrediction/title/graph2-title.png differ diff --git a/src/assets/images/linkPrediction/title/graph3-title.png b/src/assets/images/linkPrediction/title/graph3-title.png new file mode 100644 index 0000000..8fa2db5 Binary files /dev/null and b/src/assets/images/linkPrediction/title/graph3-title.png differ diff --git a/src/assets/images/time-point.png b/src/assets/images/time-point.png new file mode 100644 index 0000000..5e37dfd Binary files /dev/null and b/src/assets/images/time-point.png differ diff --git a/src/store/keyNodeStore2.js b/src/store/keyNodeStore2.js index 4f59ebc..2b9e417 100644 --- a/src/store/keyNodeStore2.js +++ b/src/store/keyNodeStore2.js @@ -250,7 +250,7 @@ export const useKeyNodeStore2 = defineStore("keyNode2", () => { const posts = ref([ { id: 1, - timestamp: "2023-10-07 15:08:27", + timestamp: "2023-10-07 08:08:27", author: "President Biden Archived", influence: 69304.0, highlighted: false, @@ -352,7 +352,7 @@ export const useKeyNodeStore2 = defineStore("keyNode2", () => { }, { id: 10, - timestamp: "2023-10-15 00:00:00", + timestamp: "2023-10-14 15:00:00", author: "Jackson Hinkle 🇺🇸", influence: 22546, highlighted: false, @@ -468,7 +468,7 @@ export const useKeyNodeStore2 = defineStore("keyNode2", () => { ]) // 当前激活的时间点 - const activeTimePoint = ref(1) + const activeTimePoint = ref(0) // 所有时间点数据 const timePoints = ref([]) const activeLeader = ref(null) diff --git a/src/views/GroupEvolution/components/groupGraph.vue b/src/views/GroupEvolution/components/groupGraph.vue index 05f50f9..708a5f8 100644 --- a/src/views/GroupEvolution/components/groupGraph.vue +++ b/src/views/GroupEvolution/components/groupGraph.vue @@ -119,6 +119,7 @@ const handlePointerDown = (time) => { const registEvents = () => { const simulation = graphVis.getSimulationLayout() forceSimulator = simulation.forceSimulation() + //全局记录包裹层元素 const containerDom = document.getElementById("container") graphVis.registEventListener("node", "mouseOver", function (event, node) { @@ -166,10 +167,10 @@ const highLightAboutNodesOrLinks = (type) => { graphVis.cancelAllSelected() const { newNodes, newLinks } = graph.value console.log(graphVis.nodes) - + console.log("进来highLightAboutNodesOrLinks") if (type == "nodes") { //实现高亮节点逻辑 - + console.log("进来if") graphVis.nodes.forEach((node) => newNodes.forEach((newNode) => { if (node.id === newNode.name) { @@ -194,14 +195,17 @@ const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => { function handleLayoutSuccess() { //处理四个关系图的差异函数 const handleGroupDiscoveryDiff = () => { + console.log("进来handleGroupDiscoveryDiff") return } const handleGroupStructureDiff = () => { + console.log("进来handleGroupStructureDiff") highLightAboutNodesOrLinks("links") } const handleGroupMemberDiff = () => { + console.log("进来handleGroupMemberDiff") highLightAboutNodesOrLinks("nodes") } @@ -296,7 +300,7 @@ const clusterAnalyze = () => { } // 仅对“异常群体模块”生效:时间变化时强制重绘一次 -if (storeId === "anomalousGroup") { +if (storeId == "anomalousGroup") { watch( () => props.store.currentUtc, () => { diff --git a/src/views/KeyNodeDiscern/bridgeCommunication/components/GraphPanel.vue b/src/views/KeyNodeDiscern/bridgeCommunication/components/GraphPanel.vue index 63e91a7..bb66225 100644 --- a/src/views/KeyNodeDiscern/bridgeCommunication/components/GraphPanel.vue +++ b/src/views/KeyNodeDiscern/bridgeCommunication/components/GraphPanel.vue @@ -19,7 +19,7 @@
2023.10.07 00:00:00 -
+
-
+
2023.10.15 00:00:00
@@ -78,6 +76,148 @@ const bridgeGraphRef = ref(null) const currentComponent = ref("BridgeCommunityNode") const currentSelectedCommunityId = ref(null) +// 用于存储点击位置 +const clickedPosition = ref(0) + +// 添加定时器相关变量 +let animationTimer = null +const isAutoPlaying = ref(false) + +// 开始自动播放方法 +const startAutomaticPlay = () => { + // 如果已经在播放,先清除 + if (animationTimer) { + clearInterval(animationTimer) + } + + isAutoPlaying.value = true + + // 每300ms移动2% + animationTimer = setInterval(() => { + // 计算新位置 + const newPosition = clickedPosition.value + 2 + + // 检查是否到达终点 + if (newPosition >= 100) { + clickedPosition.value = 100 + props.stopAutomaticPlay() + return + } + + // 更新位置 + clickedPosition.value = newPosition + + // 根据当前位置更新activeTimePoint + updateActiveTimePointByPosition(newPosition) + }, 300) +} + +// 根据位置更新activeTimePoint +const updateActiveTimePointByPosition = (position) => { + // 获取排序后的时间点 + const sortedPoints = [...store.timePoints].sort((a, b) => { + return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + }) + + // 计算每个时间点的原始位置百分比(不考虑重叠调整) + const startTime = new Date("2023-10-07T00:00:00").getTime() + const endTime = new Date("2023-10-15T00:00:00").getTime() + const timeRange = endTime - startTime + + const originalPositions = {} + sortedPoints.forEach((point) => { + const pointTime = new Date(point.timestamp).getTime() + const pointPos = ((pointTime - startTime) / timeRange) * 100 + originalPositions[point.id] = pointPos + }) + // 找到当前位置对应的时间点 + let targetPointId = null + if (sortedPoints.length > 0 && position < originalPositions[sortedPoints[0].id]) { + store.setActiveTimePoint(0) + } else { + for (let i = 0; i < sortedPoints.length; i++) { + const point = sortedPoints[i] + const pointPosition = originalPositions[point.id] + // 如果位置大于等于当前时间点位置,则选中当前时间点 + if (position >= pointPosition) { + targetPointId = point.id + } else { + break + } + } + } + + // 如果找到了对应的时间点且与当前activeTimePoint不同,则更新 + if (targetPointId && targetPointId !== store.activeTimePoint) { + store.setActiveTimePoint(targetPointId) + } +} + +const handleTrackClick = (event) => { + // 停止自动播放 + props.stopAutomaticPlay() + + // 清除内部定时器 + if (animationTimer) { + clearInterval(animationTimer) + animationTimer = null + isAutoPlaying.value = false + } + + // 获取时间轴轨道的DOM元素和尺寸信息 + const trackElement = event.currentTarget + const rect = trackElement.getBoundingClientRect() + + // 计算点击位置在时间轴上的百分比 + const clickPosition = ((event.clientX - rect.left) / rect.width) * 100 + // 保存点击位置 + clickedPosition.value = clickPosition + + // 获取排序后的时间点 + const sortedPoints = [...store.timePoints].sort((a, b) => { + return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() + }) + + // 计算每个时间点的原始位置百分比(不考虑重叠调整) + const startTime = new Date("2023-10-07T00:00:00").getTime() + const endTime = new Date("2023-10-15T00:00:00").getTime() + const timeRange = endTime - startTime + + const originalPositions = {}; + sortedPoints.forEach((point) => { + const pointTime = new Date(point.timestamp).getTime(); + const position = ((pointTime - startTime) / timeRange) * 100; + originalPositions[point.id] = position; + }); + + // 特殊情况:如果点击位置小于所有时间点的位置,则设置activeTimePoint为0 + if (sortedPoints.length > 0 && clickPosition < originalPositions[sortedPoints[0].id]) { + store.setActiveTimePoint(0); + return; // 直接返回,不再执行后续逻辑 + } + + // 找到点击位置对应的时间点 + let targetPointId = null; + for (let i = 0; i < sortedPoints.length; i++) { + const point = sortedPoints[i]; + const pointPosition = originalPositions[point.id]; + + // 如果点击位置大于等于当前时间点位置,且小于下一个时间点位置,则选中当前时间点 + if ( + clickPosition >= pointPosition && + (i === sortedPoints.length - 1 || clickPosition < originalPositions[sortedPoints[i + 1].id]) + ) { + targetPointId = point.id; + break; + } + } + + // 如果找到了对应的时间点,则更新activeTimePoint + if (targetPointId) { + store.setActiveTimePoint(targetPointId); + } +}; + const handleGraphNodeClick = (leaderData) => { store.openLeaderDetail(leaderData) } @@ -120,29 +260,29 @@ const calculatePositions = (timestamp) => { const position = ((pointTime - startTime) / timeRange) * 100 pointPositions[point.id] = Math.max(0, Math.min(100, position)) }) - // 处理重叠 (假设每个点宽度约为20px,时间轴总宽度为可用宽度) - const pointWidthPercentage = 5 // 估算的点宽度百分比 - const minSpacing = pointWidthPercentage // 最小间距百分比 + // // 处理重叠 (假设每个点宽度约为20px,时间轴总宽度为可用宽度) + // const pointWidthPercentage = 5 // 估算的点宽度百分比 + // const minSpacing = pointWidthPercentage // 最小间距百分比 - // 调整重叠的点 - for (let i = 1; i < sortedPoints.length; i++) { - const prevPoint = sortedPoints[i - 1] - const currPoint = sortedPoints[i] + // // 调整重叠的点 + // for (let i = 1; i < sortedPoints.length; i++) { + // const prevPoint = sortedPoints[i - 1] + // const currPoint = sortedPoints[i] - const prevPosition = pointPositions[prevPoint.id] - const currPosition = pointPositions[currPoint.id] + // const prevPosition = pointPositions[prevPoint.id] + // const currPosition = pointPositions[currPoint.id] - // 检查是否重叠 - if (currPosition - prevPosition < minSpacing) { - // 调整当前点位置 - pointPositions[currPoint.id] = prevPosition + minSpacing + // // 检查是否重叠 + // if (currPosition - prevPosition < minSpacing) { + // // 调整当前点位置 + // pointPositions[currPoint.id] = prevPosition + minSpacing - // 确保不超出范围 - if (pointPositions[currPoint.id] > 100) { - pointPositions[currPoint.id] = 100 - } - } - } + // // 确保不超出范围 + // if (pointPositions[currPoint.id] > 100) { + // pointPositions[currPoint.id] = 100 + // } + // } + // } return pointPositions } @@ -161,21 +301,53 @@ watch( // 计算时间轴轨道样式 const trackStyle = computed(() => { - if (!store.activeTimePoint) return {} - const activePosition = pointPositions.value[store.activeTimePoint] || 0 + // 优先使用clickedPosition,如果没有则使用activeTimePoint对应的位置 + const activePosition = + clickedPosition.value !== null + ? clickedPosition.value + : pointPositions.value[store.activeTimePoint] || 0 + return { background: `linear-gradient(90deg, #3B7699 0%, #00F3FF ${activePosition}%, #3B7699 ${activePosition}%, #3B7699 100%)` } }) -defineExpose({ highlightNode }) -// 添加时间点点击事件处理函数 +// 为了保证点击时间点也能更新clickedPosition,修改handleTimePointClick方法 const handleTimePointClick = (pointId) => { // 停止自动播放 props.stopAutomaticPlay() + + // 清除内部定时器 + if (animationTimer) { + clearInterval(animationTimer) + animationTimer = null + isAutoPlaying.value = false + } + // 设置当前激活的时间点 store.setActiveTimePoint(pointId) + + // 查找该时间点的原始位置 + const point = store.timePoints.find((p) => p.id === pointId) + if (point) { + const startTime = new Date("2023-10-07T00:00:00").getTime() + const endTime = new Date("2023-10-15T00:00:00").getTime() + const timeRange = endTime - startTime + const pointTime = new Date(point.timestamp).getTime() + const position = ((pointTime - startTime) / timeRange) * 100 + clickedPosition.value = position + } } + +// 在组件卸载时清除定时器 +import { onUnmounted } from "vue" +onUnmounted(() => { + if (animationTimer) { + clearInterval(animationTimer) + } +}) + +defineExpose({ highlightNode, startAutomaticPlay }) diff --git a/src/views/KeyNodeDiscern/bridgeCommunication/components/graph/bridgeCommunityGraph.vue b/src/views/KeyNodeDiscern/bridgeCommunication/components/graph/bridgeCommunityGraph.vue index 39d4331..eafdebc 100644 --- a/src/views/KeyNodeDiscern/bridgeCommunication/components/graph/bridgeCommunityGraph.vue +++ b/src/views/KeyNodeDiscern/bridgeCommunication/components/graph/bridgeCommunityGraph.vue @@ -43,6 +43,13 @@ const processData = async () => { // 批量将头像转换成base64 const bridgeNodes = keyNodeStore2.bridgeNodes + if (props.timestamp === 0) { + const result = { + nodes: [], + links: [] + } + return result + } // 根据timestamp过滤桥梁节点 const filteredBridgeNodes = bridgeNodes.filter((node) => node.postsId <= props.timestamp) for (const node of filteredBridgeNodes) { @@ -543,13 +550,14 @@ onUnmounted(() => { onMounted(() => { setTimeout(async () => { await initChart() - }, 100) + }, 500) resizeChart() }) watch( () => props.timestamp, async (newValue) => { + console.log("newValue:", newValue) await initChart() } ) diff --git a/src/views/KeyNodeDiscern/bridgeCommunication/index.vue b/src/views/KeyNodeDiscern/bridgeCommunication/index.vue index e2a3dec..73581f2 100644 --- a/src/views/KeyNodeDiscern/bridgeCommunication/index.vue +++ b/src/views/KeyNodeDiscern/bridgeCommunication/index.vue @@ -89,6 +89,11 @@ const stopAutomaticPlay = () => { clearInterval(timer) timer = null } + // 调用GraphPanel组件的内部方法停止自动播放 + if (graphPanelRef.value) { + // 通过直接清除定时器的方式停止,因为我们在GraphPanel内部也实现了停止逻辑 + // 这里不需要额外调用,因为GraphPanel中的handleTrackClick和handleTimePointClick已经会停止自动播放 + } } let timer = null @@ -96,34 +101,19 @@ let timer = null // 根据不同的时间间隔生成对应的延时数据 const intervalTimes = [500, 500, 1000, 1000, 1000, 1000, 1000, 1000, 4000, 2000] const automaticPlay = () => { - let index = 1 - if (timer) clearInterval(timer) - - // 创建一个函数来处理定时逻辑 - const playNext = () => { - store.setActiveTimePoint(index) - - if (index >= store.allLeaderData.length) { - clearInterval(timer) - timer = null - return - } - - // 根据当前index获取对应的时间间隔,如果超出数组长度则使用默认值 - const interval = intervalTimes[index] || 1000 - timer = setTimeout(playNext, interval) - index++ + // 不再使用原来的定时器逻辑,而是使用GraphPanel组件内部的定时器 + if (graphPanelRef.value) { + graphPanelRef.value.startAutomaticPlay() } - - // 启动自动播放,使用第一个时间间隔 - const initialInterval = intervalTimes[0] || 1000 - timer = setTimeout(playNext, initialInterval) } // 调用 store.initializeTimePoints() 初始化时间点 onMounted(() => { store.initializeTimePoints() - automaticPlay() + // 延迟调用,确保graphPanelRef已经挂载 + setTimeout(() => { + automaticPlay() + }, 1000) }) onUnmounted(() => { diff --git a/src/views/LinkPrediction/charactersHiddenInteraction/index.vue b/src/views/LinkPrediction/charactersHiddenInteraction/index.vue index 083ff48..f8723fc 100644 --- a/src/views/LinkPrediction/charactersHiddenInteraction/index.vue +++ b/src/views/LinkPrediction/charactersHiddenInteraction/index.vue @@ -123,8 +123,8 @@ import Graph from "../components/graph.vue" import WordsCloud from "../components/cloudWords.vue" import { useCharacterHiddenStore } from "@/store/linkPrediction/index" import userPanelTitleImg from "@/assets/images/linkPrediction/title/characters-hidden-interaction-user-title.png" -import graphTitleImg from "@/assets/images/linkPrediction/title/graph1-title.png" -import analysisTitleImg from "@/assets/images/linkPrediction/title/analysis-title.png" +import graphTitleImg from "@/assets/images/linkPrediction/title/graph3-title.png" +import analysisTitleImg from "@/assets/images/linkPrediction/title/characters-hidden-interaction-analysis-title.png" import { storeToRefs } from "pinia" const characterHiddenStore = useCharacterHiddenStore() diff --git a/src/views/LinkPrediction/socialGroups/index.vue b/src/views/LinkPrediction/socialGroups/index.vue index 43ac36b..711bdfb 100644 --- a/src/views/LinkPrediction/socialGroups/index.vue +++ b/src/views/LinkPrediction/socialGroups/index.vue @@ -146,7 +146,7 @@ import { Icon } from "@iconify/vue" import { useSocialGroupsStore } from "@/store/linkPrediction/index" import userPanelTitleImg from "@/assets/images/linkPrediction/title/social-group-user-title.png" import userChartTitleImg from "@/assets/images/linkPrediction/title/interaction-strenth-title.png" -import graphTitleImg from "@/assets/images/linkPrediction/title/graph1-title.png" +import graphTitleImg from "@/assets/images/linkPrediction/title/graph2-title.png" import analysisTitleImg from "@/assets/images/linkPrediction/title/social-group-analysis-title.png" import { getAvatarUrl } from "@/utils/transform" import { storeToRefs } from "pinia"