249 lines
6.9 KiB
Vue
249 lines
6.9 KiB
Vue
<template>
|
||
<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"
|
||
:is-auto-play="isPlay"
|
||
:start-time="new Date(timeList[0])"
|
||
:end-time="new Date(timeList[timeList.length - 1])"
|
||
@click:pointerDown="handlePointerDown"
|
||
@slide:pointerUp="handlePointerDown"
|
||
></TimeAxis>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { defineProps, defineEmits, onUnmounted, ref, toRaw, watch } from "vue"
|
||
import { storeToRefs } from "pinia"
|
||
import { convertToUtcIsoString } from "@/utils/transform"
|
||
import { paintNodeFunction, paintLineFunction } from "@/utils/customePaint"
|
||
import TimeAxis from "@/components/timeAxis.vue"
|
||
import GraphVis from "@/assets/package/graphvis.esm.min.js"
|
||
const props = defineProps({
|
||
store: {
|
||
required: true
|
||
}
|
||
})
|
||
const emit = defineEmits(["click:pointerDownAndSlide"])
|
||
const { timeList, graph } = storeToRefs(props.store)
|
||
let isPlay = ref(false)
|
||
let graphVis = null
|
||
let forceSimulator = null
|
||
let currentSelectNode = ref(null)
|
||
const defaultConfig = {
|
||
node: {
|
||
label: {
|
||
show: true,
|
||
font: "normal 14px KaiTi",
|
||
color: "250,250,250",
|
||
textPosition: "Bottom_Center", //Middle_Center
|
||
textOffsetY: 10
|
||
},
|
||
shape: "circle",
|
||
size: 30,
|
||
borderColor: "200,50,50",
|
||
borderWidth: 0,
|
||
selected: {
|
||
borderWidth: 2,
|
||
borderColor: "100,250,100",
|
||
showShadow: true, // 是否展示阴影
|
||
shadowBlur: 10, //阴影范围大小
|
||
shadowColor: "50,250,30" // 选中是的阴影颜色
|
||
}
|
||
},
|
||
link: {
|
||
label: {
|
||
// 连线标签
|
||
show: false, // 是否显示
|
||
color: "245,245,245", // 字体颜色
|
||
font: "normal 11px KaiTi", // 字体大小及类型
|
||
background: "255,255,255" //文字背景色(设置后文字居中,一般与画布背景色一致)
|
||
},
|
||
lineType: "straight", // curver
|
||
showArrow: false,
|
||
lineWidth: 2,
|
||
colorType: "both",
|
||
color: "240,240,240",
|
||
selected: {
|
||
color: "100,250,100"
|
||
}
|
||
},
|
||
highLightNeiber: true // 相邻节点高亮开关
|
||
}
|
||
const registCustomePaintFunc = () => {
|
||
graphVis.definedNodePaintFunc(paintNodeFunction) //自定义节点绘图方法
|
||
graphVis.definedLinkPaintFunc(paintLineFunction) //自定义关系绘图方法
|
||
}
|
||
// 处理时间轴点击事件和拉动
|
||
const handlePointerDown = (time) => {
|
||
console.log("检查点击1:",time)
|
||
const utcTime = convertToUtcIsoString(time)
|
||
console.log("检查点击2:",utcTime)
|
||
emit("click:pointerDownAndSlide", utcTime)
|
||
}
|
||
|
||
const registEvents = () => {
|
||
//全局记录包裹层元素
|
||
const containerDom = document.getElementById("container")
|
||
graphVis.registEventListener("node", "mouseOver", function (event, node) {
|
||
containerDom.style.cursor = "pointer"
|
||
})
|
||
graphVis.registEventListener("node", "mouseOut", function (event, node) {
|
||
containerDom.style.cursor = ""
|
||
})
|
||
//节点开始拖动
|
||
graphVis.registEventListener("node", "mousedrag", function (event, node) {
|
||
currentSelectNode.value = node
|
||
|
||
//开始拖动,必须要设置的属性
|
||
currentSelectNode.value.fx = node.x
|
||
currentSelectNode.value.fy = node.y
|
||
forceSimulator.alphaTarget(0.3).restart()
|
||
})
|
||
|
||
graphVis.registEventListener("node", "dblClick", function (event, node) {
|
||
node.fx = null
|
||
node.fy = null
|
||
forceSimulator.alphaTarget(0.3).restart()
|
||
})
|
||
|
||
//拖动中
|
||
graphVis.registEventListener("scene", "mouseDraging", function (event, client) {
|
||
if (currentSelectNode.value != null) {
|
||
currentSelectNode.value.fx = currentSelectNode.value.x
|
||
currentSelectNode.value.fy = currentSelectNode.value.y
|
||
}
|
||
})
|
||
|
||
graphVis.registEventListener("scene", "mouseDragEnd", function (event, client) {
|
||
if (currentSelectNode.value != null) {
|
||
forceSimulator.alphaTarget(0)
|
||
//如果拖动结束需要固定拖拽的节点,则注释下面两行,保留最后拖动的位置即可
|
||
//that.currentSelectNode.fx = null;
|
||
//that.currentSelectNode.fy = null;
|
||
currentSelectNode.value = null
|
||
}
|
||
})
|
||
}
|
||
const runForceLayout = (layoutConfig, layoutType, isAsync) => {
|
||
//执行异步布局计算
|
||
graphVis.excuteWorkerLayout(
|
||
graphVis.getGraphData(),
|
||
layoutType,
|
||
layoutConfig,
|
||
isAsync,
|
||
function () {
|
||
isPlay.value = true
|
||
graphVis.zoomFit() //布局结束缩放居中
|
||
}
|
||
)
|
||
}
|
||
// 根据节点的cluster属性进行分组
|
||
const clusterAnalyze = () => {
|
||
graphVis.removeAllGroup() // 清除原有分组
|
||
// 创建cluster到nodes的映射
|
||
const clusterNodesMap = new Map()
|
||
graphVis.nodes.forEach((node) => {
|
||
const cluster = parseInt(node.type)
|
||
if (!clusterNodesMap.has(cluster)) {
|
||
clusterNodesMap.set(cluster, [])
|
||
}
|
||
clusterNodesMap.get(cluster).push(node)
|
||
})
|
||
const colorMap = {
|
||
0: "50,141,120", // 绿色
|
||
1: "133,129,48", // 黄色
|
||
6: "12,112,144" // 蓝色
|
||
}
|
||
clusterNodesMap.forEach((nodes, cluster) => {
|
||
const color = colorMap[cluster]
|
||
nodes.forEach((node) => {
|
||
node.fillColor = color
|
||
node.color = color
|
||
})
|
||
let group = graphVis.addNodesInGroup(nodes, {
|
||
shape: "polygon", //circle|rect|polygon|bubbleset
|
||
color: color,
|
||
alpha: 0.3
|
||
})
|
||
group.smoothPath = false //拟合平滑曲线(耗性能)
|
||
})
|
||
graphVis.autoGroupLayout(graphVis.nodes)
|
||
}
|
||
|
||
const createGraph = () => {
|
||
if (!graphVis) {
|
||
graphVis = new GraphVis({
|
||
container: document.getElementById("container"),
|
||
licenseKey: "hbsy",
|
||
config: defaultConfig
|
||
})
|
||
}
|
||
graphVis.setDragHideLine(false) //拖拽时隐藏连线
|
||
graphVis.setShowDetailScale(0.1) //展示细节的比例
|
||
graphVis.setZoomRange(0.1, 5) //缩放区间
|
||
registCustomePaintFunc() //注册自定义绘图方法
|
||
registEvents()
|
||
}
|
||
|
||
const initChart = () => {
|
||
createGraph()
|
||
graphVis.addGraph({ ...toRaw(graph.value) })
|
||
clusterAnalyze()
|
||
runForceLayout({ strength: -500, ajustCluster: true }, "simulation", true)
|
||
}
|
||
|
||
// 添加更新图表的函数
|
||
const updateChart = (newGraphData) => {
|
||
if (!graphVis) {
|
||
initChart()
|
||
return
|
||
}
|
||
graphVis.clearAll()
|
||
graphVis.addGraph({ ...toRaw(newGraphData) })
|
||
graphVis.zoomFit()
|
||
// 重新运行力导向布局
|
||
clusterAnalyze()
|
||
runForceLayout({ strength: -500, ajustCluster: true }, "simulation", true)
|
||
}
|
||
|
||
let lastLength = 0 //记录上一次的长度
|
||
watch(
|
||
graph,
|
||
(newValue) => {
|
||
if (newValue && newValue.nodes.length > lastLength) {
|
||
updateChart(newValue)
|
||
}
|
||
lastLength = newValue.nodes.length
|
||
},
|
||
{ deep: true }
|
||
)
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.groupGraph-component {
|
||
width: 100%;
|
||
height: 100%;
|
||
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>
|