SocialNetworks_duan/src/views/GroupEvolution/component/groupGraph.vue

189 lines
4.9 KiB
Vue
Raw Normal View History

<template>
2025-08-05 14:23:32 +08:00
<div class="groupGraph-component">
<img src="@/assets/images/groupEvolution/graph-title.png" class="titleImage" />
<div class="container" id="container"></div>
<div class="timeList">
<TimeAxis
v-if="timeList.length"
:time-list="timeList"
2025-08-05 15:25:40 +08:00
:is-auto-play="true"
2025-08-05 14:23:32 +08:00
:start-time="new Date(timeList[0])"
:end-time="new Date(timeList[timeList.length - 1])"
@click:pointerDown="handlePointerDown"
@slide:pointerUp="handlePointerDown"
></TimeAxis>
</div>
</div>
</template>
2025-08-05 14:23:32 +08:00
<script setup>
2025-08-06 15:01:12 +08:00
import { defineProps, defineEmits, onMounted, watch, nextTick } from "vue"
2025-08-05 14:23:32 +08:00
import { storeToRefs } from "pinia"
2025-08-06 15:01:12 +08:00
import * as echarts from "echarts"
2025-08-05 14:23:32 +08:00
import TimeAxis from "@/components/timeAxis.vue"
import { convertToUtcIsoString } from "@/utils/transform"
2025-08-06 15:01:12 +08:00
import nodeHoverImg from "@/assets/images/nodeHover.png"
2025-08-05 14:23:32 +08:00
const props = defineProps({
store: {
required: true
}
})
2025-08-06 15:01:12 +08:00
let chart = null
2025-08-05 14:23:32 +08:00
const emit = defineEmits(["click:pointerDownAndSlide"])
// 解构 store 中的 state
2025-08-06 15:01:12 +08:00
const { timeList, graph } = storeToRefs(props.store)
2025-08-05 14:23:32 +08:00
// 处理时间轴点击事件和拉动
const handlePointerDown = (time) => {
const utcTime = convertToUtcIsoString(time)
emit("click:pointerDownAndSlide", utcTime)
}
2025-08-06 15:01:12 +08:00
const initChart = () => {
if (chart == null) {
chart = echarts.init(document.getElementById("container"))
}
if (Object.keys(graph.value).length === 0) {
chart.clear()
return
}
function variableColorAndPos(groupId) {
const resultMap = {
0: { color: "#1f8473", x: 100, y: 100 },
1: { color: "#807d2c", x: 300, y: 300 },
6: { color: "#0c7090", x: 600, y: 600 }
}
return resultMap[parseInt(groupId)]
}
const option = {
//hover上去的窗口
tooltip: {
trigger: "item",
backgroundColor: "rgba(0,0,0,0)", // 透明背景
borderColor: "rgba(0,0,0,0)", // 透明边框
borderWidth: 0,
extraCssText: "box-shadow:none;padding:0;",
formatter: function (params) {
if (params.dataType === "node") {
return `<div
style="
padding:10px 15px;
height: 68px;
border-radius: 4px;
background: url('${nodeHoverImg}');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
">
<div style="color:#fff;letter-spacing: 0.14px;">
<div >用户名${params.data.name}</div>
<div >组ID${params.data.groupId}</div>
</div>
</div>`
}
return ""
}
},
emphasis: {
edgeLabel: {
show: true,
color: "#fff",
fontSize: 18,
textShadowColor: "#fff",
textShadowBlur: 0,
textShadowOffsetX: 0,
textShadowOffsetY: 0
}
},
series: [
{
type: "graph",
layout: "force",
animation: false,
draggable: true,
roam: true,
zoom: 0.1,
force: {
initLayout: "circular", // 初始布局使用圆形
edgeLength: 6000,
repulsion: 5000,
gravity: 0.1,
friction: 0.02,
coolingFactor: 0.1
},
animationDurationUpdate: 3500, // 节点移动更平滑
data: graph.value?.nodes.map((node) => ({
...node,
symbol: "circle",
x: variableColorAndPos(node.groupId).x,
y: variableColorAndPos(node.groupId).y,
symbolSize: 40,
itemStyle: {
color: variableColorAndPos(node.groupId).color,
opacity: 1,
borderColor: "#46C6AD",
borderWidth: 1,
shadowBlur: 4,
borderType: "solid",
shadowColor: "rgba(19, 27, 114, 0.25)"
}
})),
links: graph.value?.links,
lineStyle: {
color: "#37ACD7",
width: 1
}
}
]
}
chart.setOption(option)
}
onMounted(() => {
initChart()
})
let lastPostsLength = 0 //当列表更新时,记录上一次的长度
watch(
() => graph.value,
(newValue) => {
if (newValue.nodes.length > lastPostsLength) {
nextTick(() => {
initChart()
})
}
lastPostsLength = newValue.nodes.length //实现按需更新
},
{ deep: true }
)
2025-08-05 14:23:32 +08:00
</script>
<style scoped lang="less">
.groupGraph-component {
width: 100%;
height: 100%;
2025-08-05 14:23:32 +08:00
position: relative;
.titleImage {
margin: 0 auto;
}
.container {
width: 100%;
height: 503px;
}
.timeList {
width: 95%;
height: 42px;
position: absolute;
left: 20px;
bottom: 20px;
z-index: 1;
}
}
</style>