关系图
This commit is contained in:
parent
90124585f1
commit
68e8d78100
62
src/assets/customeGraph/customePaint.js
Normal file
62
src/assets/customeGraph/customePaint.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
//自定义绘制节点的方法
|
||||
export const paintNodeFunction = function (ctx, onlyShape) {
|
||||
|
||||
//默认绘制数据类型图片
|
||||
if(this.properties.typeIcon){
|
||||
if(this.selected || this.showSelected){
|
||||
this.drawShape(ctx, onlyShape);
|
||||
}
|
||||
|
||||
if(this.alpha < 1){
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.alpha;
|
||||
ctx.drawImage(this.properties.typeIcon,-this.width/2, -this.height/2,this.width,this.height);
|
||||
ctx.restore();
|
||||
}else{
|
||||
ctx.drawImage(this.properties.typeIcon,-this.width/2, -this.height/2,this.width,this.height);
|
||||
}
|
||||
}else{
|
||||
this.drawShape(ctx, onlyShape);
|
||||
}
|
||||
|
||||
//按节点类型绘制节点边框
|
||||
if(this.properties.type != 'normal'){
|
||||
ctx.beginPath();
|
||||
ctx.arc(0,0,this.radius+6,0,Math.PI*2);
|
||||
ctx.lineWidth = 8;
|
||||
ctx.strokeStyle = `rgba(${this.fillColor},${this.alpha * 0.4 })`;
|
||||
ctx.stroke();
|
||||
}else{
|
||||
ctx.beginPath();
|
||||
ctx.arc(0,0,this.radius+8,0,Math.PI*2);
|
||||
ctx.lineWidth = 12;
|
||||
ctx.setLineDash([4,4]);
|
||||
ctx.strokeStyle = `rgba(${this.fillColor},${this.alpha * 0.6 })`;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
this.paintText(ctx); //调用内部方法绘制文字
|
||||
}
|
||||
|
||||
|
||||
//自定义连线的方法
|
||||
export const paintLineFunction = function (ctx, needHideText) {
|
||||
this.drawOriginalLine(ctx, needHideText); //内置默认的绘制方法
|
||||
|
||||
//指定路径,用于鼠标检测选中
|
||||
// this.path = [
|
||||
// { x: this.source.cx, y: this.source.cy },
|
||||
// { x: this.target.cx, y: this.target.cy }
|
||||
// ];
|
||||
|
||||
// //绘制路径
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(this.source.cx,this.source.cy);
|
||||
// ctx.lineTo(this.target.cx,this.target.cy);
|
||||
// this.setLineStyle(ctx);
|
||||
// ctx.stroke();
|
||||
|
||||
// //绘制连线上文字(内部方法)
|
||||
// this.paintTextOnLineWithAngle(ctx, this.source, this.target);
|
||||
|
||||
}
|
||||
1300
src/assets/customeGraph/data.js
Normal file
1300
src/assets/customeGraph/data.js
Normal file
File diff suppressed because it is too large
Load Diff
10
src/assets/package/graphvis.esm.min.js
vendored
Normal file
10
src/assets/package/graphvis.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -205,10 +205,22 @@ export const useGroupDiscoveryStore = defineStore("groupDiscovery", {
|
|||
|
||||
//根据时间参数获取节点数据
|
||||
async initialGraphByUtcTime(utcTime = "") {
|
||||
const setColor = (groupId) => {
|
||||
const colorMap = {
|
||||
0: "50,141,120",
|
||||
1: "133,129,48",
|
||||
6: "12,112,144"
|
||||
}
|
||||
return colorMap[parseInt(groupId)]
|
||||
}
|
||||
const res = await getRelationGraphByUtcTime(utcTime)
|
||||
if (res.code != 200) return
|
||||
const newSet = new Set()
|
||||
this.graph["links"] = res.data.links
|
||||
this.graph["links"] = res.data.links.map((link) => ({
|
||||
source: link.source,
|
||||
target: link.target,
|
||||
color: setColor(link.type)
|
||||
}))
|
||||
this.graph["nodes"] = res.data.nodes
|
||||
.filter((node) => {
|
||||
if (!newSet.has(node.name)) {
|
||||
|
|
@ -219,8 +231,8 @@ export const useGroupDiscoveryStore = defineStore("groupDiscovery", {
|
|||
})
|
||||
.map((node) => ({
|
||||
id: node.name,
|
||||
name: node.name,
|
||||
groupId: node.groupId
|
||||
label: node.name,
|
||||
color: setColor(node.groupId)
|
||||
}))
|
||||
},
|
||||
|
||||
|
|
|
|||
71
src/utils/customePaint.js
Normal file
71
src/utils/customePaint.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
//自定义绘制节点的方法
|
||||
export const paintNodeFunction = function (ctx, onlyShape) {
|
||||
//默认绘制数据类型图片
|
||||
if (this.properties.typeIcon) {
|
||||
if (this.selected || this.showSelected) {
|
||||
this.drawShape(ctx, onlyShape)
|
||||
}
|
||||
|
||||
if (this.alpha < 1) {
|
||||
ctx.save()
|
||||
ctx.globalAlpha = this.alpha
|
||||
ctx.drawImage(
|
||||
this.properties.typeIcon,
|
||||
-this.width / 2,
|
||||
-this.height / 2,
|
||||
this.width,
|
||||
this.height
|
||||
)
|
||||
ctx.restore()
|
||||
} else {
|
||||
ctx.drawImage(
|
||||
this.properties.typeIcon,
|
||||
-this.width / 2,
|
||||
-this.height / 2,
|
||||
this.width,
|
||||
this.height
|
||||
)
|
||||
}
|
||||
} else {
|
||||
this.drawShape(ctx, onlyShape)
|
||||
}
|
||||
|
||||
//按节点类型绘制节点边框
|
||||
if (this.properties.type != "normal") {
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, this.radius + 6, 0, Math.PI * 2)
|
||||
ctx.lineWidth = 8
|
||||
ctx.strokeStyle = `rgba(${this.fillColor},${this.alpha * 0.4})`
|
||||
ctx.stroke()
|
||||
} else {
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, this.radius + 8, 0, Math.PI * 2)
|
||||
ctx.lineWidth = 12
|
||||
ctx.setLineDash([4, 4])
|
||||
ctx.strokeStyle = `rgba(${this.fillColor},${this.alpha * 0.6})`
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
this.paintText(ctx) //调用内部方法绘制文字
|
||||
}
|
||||
|
||||
//自定义连线的方法
|
||||
export const paintLineFunction = function (ctx, needHideText) {
|
||||
this.drawOriginalLine(ctx, needHideText) //内置默认的绘制方法
|
||||
|
||||
//指定路径,用于鼠标检测选中
|
||||
// this.path = [
|
||||
// { x: this.source.cx, y: this.source.cy },
|
||||
// { x: this.target.cx, y: this.target.cy }
|
||||
// ];
|
||||
|
||||
// //绘制路径
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(this.source.cx,this.source.cy);
|
||||
// ctx.lineTo(this.target.cx,this.target.cy);
|
||||
// this.setLineStyle(ctx);
|
||||
// ctx.stroke();
|
||||
|
||||
// //绘制连线上文字(内部方法)
|
||||
// this.paintTextOnLineWithAngle(ctx, this.source, this.target);
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ const initChart = () => {
|
|||
群体二: "#00d6da",
|
||||
群体三: "#fddc33"
|
||||
}
|
||||
if(props.moduleName == "群体成员演化分析"){
|
||||
if (props.moduleName == "群体成员演化分析") {
|
||||
color = {
|
||||
分裂指数: "#2AB8FD",
|
||||
合并指数: "#02D7DA",
|
||||
|
|
@ -147,11 +147,11 @@ const initChart = () => {
|
|||
扩展指数: "#EB57B0"
|
||||
}
|
||||
}
|
||||
if(props.moduleName == "异常行为分析"){
|
||||
if (props.moduleName == "异常行为分析") {
|
||||
color = {
|
||||
社团组一: "#2AB8FD",
|
||||
社团组二: "#02D7DA",
|
||||
社团组三: "#FFDA09",
|
||||
社团组三: "#FFDA09"
|
||||
}
|
||||
}
|
||||
const listHtml = params
|
||||
|
|
@ -242,7 +242,8 @@ const initChart = () => {
|
|||
}
|
||||
},
|
||||
// 判断是否为异常群体模块:isAbnormal默认为false===>正常模块:异常群体模块
|
||||
series: !props.isAbnormal ? props.chartData.seriesList.map((series) => ({
|
||||
series: !props.isAbnormal
|
||||
? props.chartData.seriesList.map((series) => ({
|
||||
...series,
|
||||
type: "line",
|
||||
itemStyle: {
|
||||
|
|
@ -265,7 +266,7 @@ const initChart = () => {
|
|||
color: series.themeColor,
|
||||
borderColor: series.themeColor, // 使用线条颜色作为边框色
|
||||
borderWidth: 1,
|
||||
borderRadius: [8,8,0,0]
|
||||
borderRadius: [8, 8, 0, 0]
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
@ -287,7 +288,7 @@ watch(
|
|||
)
|
||||
|
||||
onMounted(() => {
|
||||
console.log("111");
|
||||
console.log("111")
|
||||
|
||||
console.log(props.moduleName)
|
||||
// 获取容器宽度
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<TimeAxis
|
||||
v-if="timeList.length"
|
||||
:time-list="timeList"
|
||||
:is-auto-play="true"
|
||||
:is-auto-play="false"
|
||||
:start-time="new Date(timeList[0])"
|
||||
:end-time="new Date(timeList[timeList.length - 1])"
|
||||
@click:pointerDown="handlePointerDown"
|
||||
|
|
@ -17,151 +17,225 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, onMounted, watch, nextTick } from "vue"
|
||||
import {
|
||||
defineProps,
|
||||
defineEmits,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
nextTick,
|
||||
ref,
|
||||
toRaw,
|
||||
watchEffect,
|
||||
watch
|
||||
} from "vue"
|
||||
import { storeToRefs } from "pinia"
|
||||
import * as echarts from "echarts"
|
||||
import TimeAxis from "@/components/timeAxis.vue"
|
||||
import { convertToUtcIsoString } from "@/utils/transform"
|
||||
import nodeHoverImg from "@/assets/images/nodeHover.png"
|
||||
import { paintNodeFunction, paintLineFunction } from "@/utils/customePaint"
|
||||
import { demoData } from "@/assets/customeGraph/data"
|
||||
import TimeAxis from "@/components/timeAxis.vue"
|
||||
import GraphVis from "@/assets/package/graphvis.esm.min.js"
|
||||
|
||||
const props = defineProps({
|
||||
store: {
|
||||
required: true
|
||||
}
|
||||
})
|
||||
let chart = null
|
||||
const emit = defineEmits(["click:pointerDownAndSlide"])
|
||||
// 解构 store 中的 state
|
||||
const { timeList, graph } = storeToRefs(props.store)
|
||||
|
||||
let graphVis = null
|
||||
let forceSimulator = null
|
||||
let currentSelectNode = ref(null)
|
||||
let isGraphVisReady = false // 添加一个标志位
|
||||
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,
|
||||
color: "120,120,240",
|
||||
borderColor: "200,50,50",
|
||||
borderWidth: 0,
|
||||
selected: {
|
||||
borderWidth: 2,
|
||||
borderColor: "100,250,100",
|
||||
showShadow: true, // 是否展示阴影
|
||||
shadowBlur: 30, //阴影范围大小
|
||||
shadowColor: "50,250,30" // 选中是的阴影颜色
|
||||
}
|
||||
},
|
||||
link: {
|
||||
label: {
|
||||
// 连线标签
|
||||
show: false, // 是否显示
|
||||
color: "245,245,245", // 字体颜色
|
||||
font: "normal 11px KaiTi", // 字体大小及类型
|
||||
background: "255,255,255" //文字背景色(设置后文字居中,一般与画布背景色一致)
|
||||
},
|
||||
lineType: "curver", //straight
|
||||
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) => {
|
||||
const utcTime = convertToUtcIsoString(time)
|
||||
emit("click:pointerDownAndSlide", utcTime)
|
||||
}
|
||||
|
||||
const initChart = () => {
|
||||
if (chart == null) {
|
||||
chart = echarts.init(document.getElementById("container"))
|
||||
}
|
||||
if (Object.keys(graph.value).length === 0) {
|
||||
chart.clear()
|
||||
return
|
||||
}
|
||||
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
|
||||
|
||||
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)]
|
||||
}
|
||||
//开始拖动,必须要设置的属性
|
||||
currentSelectNode.value.fx = node.x
|
||||
currentSelectNode.value.fy = node.y
|
||||
forceSimulator.alphaTarget(0.3).restart()
|
||||
})
|
||||
|
||||
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
|
||||
},
|
||||
graphVis.registEventListener("node", "dblClick", function (event, node) {
|
||||
node.fx = null
|
||||
node.fy = null
|
||||
forceSimulator.alphaTarget(0.3).restart()
|
||||
})
|
||||
|
||||
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)"
|
||||
//拖动中
|
||||
graphVis.registEventListener("scene", "mouseDraging", function (event, client) {
|
||||
if (currentSelectNode.value != null) {
|
||||
currentSelectNode.value.fx = currentSelectNode.value.x
|
||||
currentSelectNode.value.fy = currentSelectNode.value.y
|
||||
}
|
||||
})),
|
||||
links: graph.value?.links,
|
||||
lineStyle: {
|
||||
color: "#37ACD7",
|
||||
width: 1
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
chart.setOption(option)
|
||||
})
|
||||
}
|
||||
const runForceLayout = () => {
|
||||
let viewCenter = graphVis.getViewCenter()
|
||||
let simulation = graphVis.getSimulationLayout()
|
||||
let curForceSimulator = simulation.forceSimulation()
|
||||
curForceSimulator
|
||||
.nodes(graphVis.nodes)
|
||||
.force("center", simulation.forceCenter(viewCenter.x, viewCenter.y))
|
||||
.force("charge", simulation.forceManyBody().strength(-550).theta(0.85)) // manyBodyReuse|forceManyBody .distanceMin(300).distanceMax(400).theta(0.9)
|
||||
.force("link", simulation.forceLink(graphVis.links).distance(120).strength(0.35)) //.distance(120).strength(0.15)
|
||||
.force(
|
||||
"collide",
|
||||
simulation.forceCollide().radius((d) => d.radius + 5)
|
||||
) //.iterations(5)
|
||||
.force("x", simulation.forceX())
|
||||
.force("y", simulation.forceY())
|
||||
.alphaDecay(0.02) //设置 alpha 衰减率.迭代150,默认0.0228
|
||||
//.alphaMin(0.005) //须要在 [0, 1] 之间。若是没有指定 min 则返回当前的最小 alpha 值,默认为 0.001. 在仿真内部,会不断的减少 alpha 值直到 alpha 值小于 最小 alpha
|
||||
.velocityDecay(0.15) //默认为 0.4,较低的衰减系数可使得迭代次数更多,其布局结果也会更理性,可是可能会引发数值不稳定从而致使震荡。
|
||||
|
||||
curForceSimulator.alpha(1).restart()
|
||||
|
||||
curForceSimulator.on("tick", () => {
|
||||
graphVis.refreshView() //刷新视图
|
||||
})
|
||||
|
||||
curForceSimulator.on("end", () => {
|
||||
curForceSimulator.alpha(0)
|
||||
curForceSimulator.stop()
|
||||
})
|
||||
forceSimulator = curForceSimulator
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initChart()
|
||||
})
|
||||
let lastPostsLength = 0 //当列表更新时,记录上一次的长度
|
||||
watch(
|
||||
() => graph.value,
|
||||
(newValue) => {
|
||||
if (newValue.nodes.length > lastPostsLength) {
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
const initChart = () => {
|
||||
const getGroupCenters = (groupCount, width, height, radius = 200) => {
|
||||
// 三组分布在三角形顶点
|
||||
const angleStep = (2 * Math.PI) / groupCount
|
||||
const centerX = width / 2
|
||||
const centerY = height / 2
|
||||
return Array.from({ length: groupCount }).map((_, i) => ({
|
||||
x: centerX + Math.cos(i * angleStep) * radius,
|
||||
y: centerY + Math.sin(i * angleStep) * radius
|
||||
}))
|
||||
}
|
||||
const assignNodePositions = (nodes, groupCenters) => {
|
||||
const groupMap = {}
|
||||
nodes.forEach((node) => {
|
||||
const groupIdx = node.groupId || 0
|
||||
if (!groupMap[groupIdx]) groupMap[groupIdx] = []
|
||||
groupMap[groupIdx].push(node)
|
||||
})
|
||||
Object.entries(groupMap).forEach(([groupIdx, groupNodes]) => {
|
||||
const center = groupCenters[groupIdx]
|
||||
groupNodes.forEach((node, i) => {
|
||||
// 每组节点在各自中心附近随机分布
|
||||
node.x = center.x + Math.random() * 80 - 40
|
||||
node.y = center.y + Math.random() * 80 - 40
|
||||
})
|
||||
})
|
||||
}
|
||||
lastPostsLength = newValue.nodes.length //实现按需更新
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
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 groupCount = 3 // 分组数量
|
||||
const width = 900,
|
||||
height = 500 // 画布大小
|
||||
const groupCenters = getGroupCenters(groupCount, width, height)
|
||||
assignNodePositions(graph.value.nodes, groupCenters)
|
||||
createGraph()
|
||||
graphVis.addGraph({ ...toRaw(graph.value) })
|
||||
runForceLayout()
|
||||
}
|
||||
onMounted(async () => {
|
||||
await nextTick() // 等待 DOM 更新
|
||||
initChart()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (graphVis) {
|
||||
graphVis.destroy() // 如果 GraphVis 提供了销毁方法
|
||||
graphVis = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user