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

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

View File

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

View File

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

View File

@ -301,7 +301,7 @@ provide("statisticsList", characterHiddenStore.statisticsList)
.top-container { .top-container {
width: 100%; width: 100%;
height: vh(110); 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-repeat: no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
fill: linear-gradient(270deg, rgba(6, 61, 113, 0.1) 0%, rgba(8, 30, 56, 0.38) 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 { .top-container {
width: 100%; width: 100%;
height: vh(110); 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-repeat: no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
fill: linear-gradient(270deg, rgba(6, 61, 113, 0.1) 0%, rgba(8, 30, 56, 0.38) 100%); fill: linear-gradient(270deg, rgba(6, 61, 113, 0.1) 0%, rgba(8, 30, 56, 0.38) 100%);