第三个模块时间轴自动推进展示每个时间切片的关系图

This commit is contained in:
qumeng039@126.com 2025-08-28 10:14:35 +08:00
parent 6230c865e3
commit c9e63e815c
9 changed files with 91 additions and 72 deletions

BIN
dist.zip

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View File

@ -11,7 +11,7 @@
>
<div
class="time-section"
:style="{ left: getTimeSectionLeft(time) + 'px' }"
:style="{ left: nowSize(getTimeSectionLeft(time)) + 'px' }"
@pointerdown.stop="handleSectionPointerDown(time)"
></div>
</el-tooltip>
@ -33,7 +33,7 @@
<div
class="timeLine-point"
@pointerdown.stop="handlePointPointerDown"
:style="{ left: `${currentPosition - 9}px` }"
:style="{ left: `${nowSize(currentPosition - 9)}px` }"
></div>
</el-tooltip>
</div>
@ -53,12 +53,12 @@ const props = defineProps({
},
startTime: {
//
type: Date,
default: new Date("2024-05-16 16:56:04")
},
endTime: {
//
type: Date,
default: new Date("2024-05-23 10:16:56")
},
initPosition: {
@ -81,7 +81,7 @@ const props = defineProps({
const startTime = ref(props.startTime) //
const endTime = ref(props.endTime) //
const timeList = ref(props.timeList) //
const currentTime = ref(new Date("2024-05-16 16:56:04")) //
const currentTime = ref(props.startTime) //
const currentPosition = ref(props.initPosition) //
const isPlaying = ref(props.isAutoPlay) //
const axisRef = ref(null) //
@ -90,7 +90,7 @@ const axisWidth = nowSize(415) // 轴的长度(px)
const startTimeMs = startTime.value.getTime() //
const endTimeMs = endTime.value.getTime() //
const totalDuration = endTimeMs - startTimeMs //
const step = 4 // (px)
const step = nowSize(4) // (px)
let playTimer = null //
const emit = defineEmits(["click:pointerDown", "slide:pointerUp"])
@ -138,7 +138,7 @@ watch(
// active-needle timeList
const showHidden = computed(() => {
if (!timeList.value || timeList.value.length === 0) return {}
const left = getTimeSectionLeft.value(timeList.value[timeList.value.length - 1]) + 5 // +5px time-section
const left = getTimeSectionLeft.value(timeList.value[timeList.value.length - 1]) + nowSize(5) // +5px time-section
return { left: `${left}px` }
})
@ -153,17 +153,41 @@ const pause = () => {
}
//
//
let processedIndices = new Set()
//
const resetProcessedIndices = () => {
processedIndices.clear()
}
const play = () => {
if (!isPlaying.value) return
if (!isPlaying.value || timeList.value.length === 0) return
//
playTimer = setInterval(() => {
if (currentPosition.value >= axisWidth) {
pause()
return
}
//
currentPosition.value = Math.min(axisWidth, currentPosition.value + step)
currentTime.value = getTimeFromPosition(currentPosition.value)
emit("slide:pointerUp", currentTime.value)
}, 300) // 300ms
//
for (let i = 0; i < timeList.value.length; i++) {
if (processedIndices.has(i)) continue
const time = timeList.value[i]
const left = getTimeSectionLeft.value(time)
//
if (
currentPosition.value >= left - nowSize(2) &&
currentPosition.value <= left + nowSize(2)
) {
currentTime.value = time // 使
emit("slide:pointerUp", time) // timeList
processedIndices.add(i)
}
}
//
if (currentPosition.value >= axisWidth) {
pause()
}
}, 500) // 500ms
}
//
@ -177,6 +201,7 @@ const getTimeFromPosition = (position) => {
const handlePointerDown = (e) => {
if (e.target.classList.contains("timeLine-point")) return
pause() //
resetProcessedIndices() //
const rect = axisRef.value.getBoundingClientRect()
const position = Math.max(0, Math.min(axisWidth, e.clientX - rect.left))
currentPosition.value = position
@ -189,6 +214,7 @@ const handlePointPointerDown = (e) => {
e.stopPropagation()
e.preventDefault()
isDragging.value = true
resetProcessedIndices() //
//
const rect = axisRef.value.getBoundingClientRect()
const axisLeft = rect.left
@ -216,9 +242,10 @@ const handlePointPointerDown = (e) => {
document.addEventListener("pointerup", handlePointerUp)
}
const timeSectionWidth = 4 //
const timeSectionWidth = nowSize(4) //
const handleSectionPointerDown = (time) => {
pause()
resetProcessedIndices() //
//
const left = getTimeSectionLeft.value(time) + timeSectionWidth / 2
currentPosition.value = left
@ -232,6 +259,7 @@ const reset = () => {
currentTime.value = getTimeFromPosition(props.initPosition)
//
pause()
resetProcessedIndices() //
//
if (props.isAutoPlay) {
isPlaying.value = true

View File

@ -186,8 +186,6 @@ export const useGroupDiscoveryStore = defineStore("groupDiscovery", {
//获取群体列表数据
async initializeGroupList(time = "") {
const res = await getGroupEvolutionGroupList(time)
console.log("群体列表:", res)
if (res.code != 200) return
const iconMap = {
节点数: nodePrefix,

View File

@ -30,7 +30,7 @@
</template>
<script setup>
import { ref, toRaw, watch } from "vue"
import { onMounted, ref, toRaw, watch } from "vue"
import { storeToRefs } from "pinia"
import { convertToUtcIsoString } from "@/utils/transform"
import { paintNodeFunction, paintLineFunction } from "@/utils/customePaint"
@ -59,6 +59,12 @@ const legendsMap = {
{ icon: abnormalLeg, text: "异常社团" }
]
}
const zoomSize = new Map([
["groupDiscovery", 0.15],
["groupStructure", 0.15],
["groupMember", 0.15],
["anomalousGroup", 0.7]
])
const emit = defineEmits(["click:pointerDownAndSlide"])
const { timeList, graph } = storeToRefs(props.store)
const graphTitle = props.store.graphTitle
@ -166,11 +172,8 @@ const registEvents = () => {
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) {
@ -195,62 +198,41 @@ 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")
}
const handleAnomalousGroup = () => {
// store T0
// store
const now = props.store.currentUtc
const TA = "2024-06-19T08:57:55Z" // A
const TB = "2024-06-19T10:58:03Z" // B
const TC = "2024-06-19T12:58:04Z" // C
// colorMap
const GROUP_ALPHA = 0.3
const RED = "220,50,60"
const greenRed = "85, 125, 15"
const blueRed = "15, 106, 125"
const yellowRed = "125, 114, 15"
//
const shouldA = now >= TA
const shouldB = now >= TB
const shouldC = now >= TC
if (shouldA) {
graphVis.nodes = graphVis.nodes.map((n) => {
if (props.store.graphAbnormalData.groupA.includes(n.id)) {
// n.fillColor = RED
n.fillColor = greenRed
const abnormalData = props.store.graphAbnormalData
const abnormalGroupConfigs = [
{ timeThreshold: "2024-06-19T08:57:55Z", groupKey: "groupA", color: "85, 125, 15" }, // 绿
{ timeThreshold: "2024-06-19T10:58:03Z", groupKey: "groupB", color: "125, 114, 15" }, //
{ timeThreshold: "2024-06-19T12:58:04Z", groupKey: "groupC", color: "15, 106, 125" } //
]
//
const activeConfigs = abnormalGroupConfigs.filter((config) => now >= config.timeThreshold)
// map
graphVis.nodes = graphVis.nodes.map((node) => {
//
for (const config of activeConfigs) {
const abnormalIds = abnormalData[config.groupKey]
//
if (abnormalIds && abnormalIds.includes(node.id)) {
node.fillColor = config.color
break //
}
return n
})
}
if (shouldB) {
graphVis.nodes = graphVis.nodes.map((n) => {
if (props.store.graphAbnormalData.groupB.includes(n.id)) {
n.fillColor = yellowRed
}
return n
})
}
if (shouldC) {
graphVis.nodes = graphVis.nodes.map((n) => {
if (props.store.graphAbnormalData.groupC.includes(n.id)) {
n.fillColor = blueRed
}
return n
})
}
}
return node
})
}
new Map([
@ -259,11 +241,10 @@ const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => {
["groupMember", () => handleGroupMemberDiff()],
["anomalousGroup", () => handleAnomalousGroup()]
]).get(storeId)?.()
// isPlay.value = true
graphVis.zoomFit() //
isPlay.value = true
graphVis.moveCenter(zoomSize.get(storeId)) //
}
graphVis.excuteLocalLayout(layoutType, layoutConfig, isAsync, () => handleLayoutSuccess())
// graphVis.autoGroupLayout(graphVis.getGraphData())
graphVis.excuteLocalLayout(layoutType, layoutConfig, isAsync, handleLayoutSuccess())
}
// cluster
const clusterAnalyze = () => {
@ -324,7 +305,7 @@ const createGraph = () => {
}
graphVis.setDragHideLine(false) //线
graphVis.setShowDetailScale(0.1) //
graphVis.setZoomRange(0.1, 5) //
graphVis.setZoomRange(0.1, 10) //
registCustomePaintFunc(storeId) //
registEvents()
}
@ -333,9 +314,21 @@ const initChart = () => {
createGraph()
graphVis.addGraph({ ...toRaw(graph.value) })
clusterAnalyze()
runDiffForceLayout({ strength: -500, ajustCluster: true }, "simulation", true)
runDiffForceLayout({ strength: -300, ajustCluster: true }, "simulation", false)
}
onMounted(() => {
//
if (
timeList.value.length > 0 &&
(!graph.value || !graph.value.nodes || graph.value.nodes.length === 0)
) {
const firstTime = timeList.value[0]
const utcTime = convertToUtcIsoString(firstTime)
emit("click:pointerDownAndSlide", utcTime)
}
})
//
const updateChart = (newGraphData) => {
if (!graphVis) {
@ -346,7 +339,7 @@ const updateChart = (newGraphData) => {
graphVis.addGraph({ ...toRaw(newGraphData) })
//
clusterAnalyze()
runDiffForceLayout({ strength: -500, ajustCluster: true }, "simulation", true)
runDiffForceLayout({ strength: -300, ajustCluster: true }, "simulation", false)
}
watch(

View File

@ -301,7 +301,7 @@ provide("statisticsList", characterHiddenStore.statisticsList)
.top-container {
width: 100%;
height: vh(110);
background-image: url(@/assets/images/linkPrediction/title/page-title.png);
background-image: url("@/assets/images/head/hiddenTitle.png");
background-repeat: no-repeat;
background-size: 100% 100%;
fill: linear-gradient(270deg, rgba(6, 61, 113, 0.1) 0%, rgba(8, 30, 56, 0.38) 100%);

View File

@ -332,7 +332,7 @@ provide("statisticsList", socialGroupsStore.statisticsList)
.top-container {
width: 100%;
height: vh(110);
background-image: url(@/assets/images/linkPrediction/title/page-title.png);
background-image: url("@/assets/images/head/linkedPredictionStruct.png");
background-repeat: no-repeat;
background-size: 100% 100%;
fill: linear-gradient(270deg, rgba(6, 61, 113, 0.1) 0%, rgba(8, 30, 56, 0.38) 100%);