This commit is contained in:
qumeng039@126.com 2025-08-26 17:32:26 +08:00
commit 3b024cb6cd
12 changed files with 248 additions and 79 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

View File

@ -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)

View File

@ -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,
() => {

View File

@ -19,7 +19,7 @@
</div>
<div class="timeline-container">
<span class="time-label">2023.10.07 00:00:00</span>
<div class="timeline-track" :style="trackStyle">
<div class="timeline-track" :style="trackStyle" @click="handleTrackClick">
<div
v-for="point in store.timePoints"
:key="point.id"
@ -43,13 +43,11 @@
trigger="click"
content="发布贴文"
>
<template #reference>
<div class="active-pin"></div>
</template>
</el-popover>
</div>
</el-tooltip>
</div>
<div class="active-pin" :style="{ left: `${clickedPosition}%` }"></div>
</div>
<span class="time-label">2023.10.15 00:00:00</span>
</div>
@ -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
// 300ms2%
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;
});
// activeTimePoint0
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 })
//
// clickedPositionhandleTimePointClick
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 })
</script>
<style scoped lang="scss">
@ -250,6 +422,7 @@ const handleTimePointClick = (pointId) => {
position: relative;
display: flex;
align-items: center;
cursor: pointer;
}
.timeline-point-wrapper {
display: flex;
@ -261,11 +434,10 @@ const handleTimePointClick = (pointId) => {
transform: translateX(-50%);
}
.timeline-point {
width: vw(18);
width: 4px;
height: vh(18);
background-color: transparent;
border-radius: 50%;
border: 1.6px solid #ffe5a4;
background: linear-gradient(180deg, #fee39e 0%, #f9bd25 100%);
border-radius: 10px;
transition: transform 0.3s ease;
position: relative;
display: flex;
@ -273,29 +445,24 @@ const handleTimePointClick = (pointId) => {
justify-content: center;
}
// ::after
.timeline-point::after {
content: "";
width: vw(10);
height: vh(10);
background-color: #f9bd25;
border-radius: 50%;
position: absolute;
display: none;
}
.timeline-point-wrapper:hover .timeline-point {
transform: scale(1.5);
}
// active
.timeline-point.active {
background-color: transparent;
border-color: #ffe5a4;
transform: scale(1.3);
background: linear-gradient(180deg, #fee39e 0%, #f9bd25 100%);
transform: scaleX(1.5);
}
.active-pin {
width: vw(30);
height: vh(34);
background-image: url("@/assets/images/point.png");
width: vw(14);
height: vh(14);
background-image: url("@/assets/images/time-point.png");
background-size: cover;
bottom: vh(6);
position: absolute;
bottom: vh(-3);
transform: translateX(-50%); //
z-index: 10; //
}
</style>

View File

@ -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()
}
)

View File

@ -89,6 +89,11 @@ const stopAutomaticPlay = () => {
clearInterval(timer)
timer = null
}
// GraphPanel
if (graphPanelRef.value) {
// GraphPanel
// GraphPanelhandleTrackClickhandleTimePointClick
}
}
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(() => {

View File

@ -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()

View File

@ -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"