GraphVis官网:GraphVisGraphVis节点的属性和事件 (yuque.com)
官方示例:
<template>
<div style="height: 100%;">
<div class="graph-nav">
<el-row :gutter="20">
<el-col :span="8"><div class="grid-content bg-purple">图谱名称:{{knowlegeInfo.name}}</div></el-col>
<el-col :span="8"><div class="grid-content bg-purple">图谱标识:{{knowlegeInfo.tag}}</div></el-col>
<el-col :span="8"><div class="grid-content bg-purple">图谱描述:{{knowlegeInfo.desc}}</div></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8"><div class="grid-content bg-purple">实体数量:{{knowlegeInfo.nodeNum}}</div></el-col>
<el-col :span="8"><div class="grid-content bg-purple">关系数量:{{knowlegeInfo.relationNum}}</div></el-col>
<el-col :span="8"><div class="grid-content bg-purple">图谱权限:{{knowlegeInfo.permission}}</div></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="grid-content bg-purple">属性展开:
<el-switch
v-model="properShow"
active-color="#13ce66"
active-text="开"
inactive-text="关"
@change="changeProperShow()">
</el-switch>
</div>
</el-col>
</el-row>
</div >
<!--图显示区域-->
<div class="graph-area" v-loading="loading">
<!-- 绘图面板区域 -->
<div id="graph-panel" style="width: 100%;height: 100%;"></div>
<!-- 图例区域 -->
<div id="graph-legend" class="legend-wrap">
<div v-for="(item,index) in graphLegend" :key="index" class="legend-item">
<div class="item-dot" :style="{'background-color': item.show ? item.color : 'rgb(210,210,210)' }" @click="showOrHideType(index)"></div>
<div class="item-label" :style="{'color': item.show ? '' : 'rgb(210,210,210)' }">{{ item.type }}</div>
</div>
</div>
<!-- 工具栏 -->
<div class="toolbar">
<div class="toolbar-item" @click="toolBarEvent('layout','fastFR')" title="网络布局"><i class="el-icon-cpu"></i></div>
<div class="toolbar-item" @click="toolBarEvent('layout','hubsize')" title="层次布局"><i class="el-icon-share"></i></div>
<div class="toolbar-item" @click="toolBarEvent('zoom','zoomOut')" title="放大"><i class="el-icon-zoom-in"></i></div>
<div class="toolbar-item" @click="toolBarEvent('zoom','auto')" title="适中"><i class="el-icon-help"></i></div>
<div class="toolbar-item" @click="toolBarEvent('zoom','zoomIn')" title="缩小"><i class="el-icon-zoom-out"></i></div>
</div>
</div>
<!-- 节点右键操作菜单 -->
<div id="nodeRightMenuPanel" class="right-menu-layer">
<button @click="showNodeDeteail()"><i class="el-icon-notebook-2"></i>节点属性</button>
<button @click="hideNode()"><i class="el-icon-s-release"></i>隐藏</button>
</div>
<!-- 连线右键操作对话栏 -->
<div id="linkRightMenuPanel" class="right-menu-layer">
<button @click="showLinkDetail()"><i class="el-icon-notebook-2"></i>关系属性</button>
<button @click="hideLink()"><i class="el-icon-s-release"></i>隐藏</button>
</div>
<!-- 节点属性信息显示弹出层 -->
<el-drawer title="节点属性信息" :modal-append-to-body="false" :wrapperClosable="false" :visible.sync="showNodeInfoDialog" size="380px" ref="drawer" >
<div style="padding:5px 10px;">
<div class="box-card">
<el-form :model="currentNode" label-width="80px" size="mini">
<el-form-item label="节点ID">
<el-input type="text" v-model="currentNode.id" label="节点ID" disabled></el-input>
</el-form-item>
<el-form-item label="节点名称">
<el-input type="text" v-model="currentNode.label" label="节点名称" disabled></el-input>
</el-form-item>
<el-form-item label="节点类型">
<el-input type="text" v-model="currentNode.type" label="节点类型" disabled></el-input>
</el-form-item>
<el-form-item label="颜色(rgb)">
<el-color-picker v-model="currentNode.color" :color-format="'rgb'" :show-alpha="false" disabled></el-color-picker>
</el-form-item>
</el-form>
</div>
<div class="box-card">
<el-table
:data="attrbutes"
border
size="small"
style="width:100%;">
<el-table-column prop="name" label="属性"></el-table-column>
<el-table-column prop="type" label="数据类型" width="100"></el-table-column>
<el-table-column prop="unit" label="单位" width="100"></el-table-column>
</el-table>
</div>
</div>
</el-drawer>
<!-- 属性节点显示弹出层 -->
<el-drawer title="属性信息" :modal-append-to-body="false" :wrapperClosable="false" :visible.sync="showProperNodeInfo" size="380px" ref="drawer" >
<div style="padding:5px 10px;">
<div class="box-card">
<el-form :model="properNode" label-width="80px" size="mini">
<el-form-item label="名称">
<el-input type="text" v-model="properNode.name" label="名称" disabled></el-input>
</el-form-item>
<el-form-item label="数据类型">
<el-input type="text" v-model="properNode.type" label="数据类型" disabled></el-input>
</el-form-item>
<el-form-item label="单位">
<el-input type="text" v-model="properNode.unit" label="单位" disabled></el-input>
</el-form-item>
</el-form>
</div>
</div>
</el-drawer>
<!-- 连线属性信息显示弹出层 -->
<el-drawer title="关系属性信息" :modal-append-to-body="false" :wrapperClosable="false" :visible.sync="showLinkInfoDialog" size="380px" ref="drawer" >
<div style="padding:5px 10px;">
<div class="box-card">
<el-form :model="currentLink" label-width="80px" size="mini">
<el-form-item label="关系ID">
<el-input type="text" v-model="currentLink.id" label="关系ID" disabled></el-input>
</el-form-item>
<el-form-item label="关系类型">
<el-input type="text" v-model="currentLink.type" label="关系类型" disabled></el-input>
</el-form-item>
</el-form>
</div>
<div class="box-card">
<el-table
:data="attrbutes"
border
size="small"
style="width:100%;">
<el-table-column prop="name" label="属性"></el-table-column>
<el-table-column prop="type" label="数据类型" width="100"></el-table-column>
<el-table-column prop="unit" label="单位" width="100"></el-table-column>
</el-table>
</div>
</div>
</el-drawer>
<!-- 节点或连线属性提示 -->
<div id="tip-layer" class="tip-wrap" v-show="tipLayer.show">
<div class="tip-header">{{tipLayer.header}}</div>
<div class="tip-body">
<el-table
:data="tipLayer.data"
border
size="small"
style="width:100%;">
<el-table-column prop="name" label="属性"></el-table-column>
<el-table-column prop="type" label="数据类型" width="100"></el-table-column>
<el-table-column prop="unit" label="单位" width="100"></el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
import VisGraph from '@/assets/js/graphvis.20230812.js'
import LayoutFactory from '@/assets/js/graphvis.layout.min.js'
import { config } from '@/assets/defaultConfig.js'
import { demoData } from '@/assets/demo2.js'
export default {
data () {
return {
// visGraph实例对象
visGraph: null,
visLayout: null, // 布局对象
layoutLoopName: null, // 布局循环对象
// visGraph可视化交互配置
config,
// 示例数据
demoData,
circleBgImage: require('@/assets/images/circle.png'), // 节点背景图片
// visGraph创建节点和连线数据集
graphData: {
nodes: [],
links: []
},
graphLegend: [
// {type:'图例一',color:'rgb(233,120,120)',show:true}
], // 图例数组,根据数据的节点类型生成
knowlegeInfo: {// 图谱信息,需要服务端查询后赋值
name: '公司网络图谱',
tag: '公司',
desc: '某某公司图谱信息',
nodeNum: 7,
relationNum: 9,
permission: '私有'
},
properShow: false, // 属性展示配置
tipLayer: { // 提示层配置
show: false, // 是否显示提示层
header: '提示信息', // 提示表头
data: [] // 提示内部的数据
},
currentNode: {}, // 选中的节点对象
attrbutes: [], // 选中节点或连线的属性列表
currentLink: {}, // 选中的连线对象
// 节点详细信息弹框
showNodeInfoDialog: false,
// 关系连线信息弹层
showLinkInfoDialog: false,
// 属性节点详细弹层控制开关
showProperNodeInfo: false,
properNode: {}, // 属性节点
currentLayoutType: 'fastFR', // 当前布局类型,用于区分是否可以拖动
loading: true
}
},
methods: {
// 获取所有的知识节点信息
async drawGraphData (id) {
this.graphData = this.demoData
if (this.visGraph === null) {
this.createGraph()
this.refreshGraphData()
} else {
this.refreshGraphData()
}
this.loading = false
},
// 创建全局绘图客户端对象
createGraph () {
this.visGraph = new VisGraph(document.getElementById('graph-panel'), this.config)
},
// 刷新知识图谱数据
refreshGraphData () {
this.visGraph.drawData(this.graphData)
this.visGraph.moveCenter() // 移动至中心位置
this.generateLegend()// 生成图例
this.reLayout()
},
generateLegend () { // 生成图例
var legendMap = new Map()
this.graphLegend.forEach((item) => {
legendMap.set(item.type, item)
})
this.visGraph.nodes.forEach((node) => {
if (!legendMap.get(node.type)) {
legendMap.set(node.type, {
type: node.type,
color: `rgb(${node.fillColor})`,
show: true
})
}
})
this.graphLegend = []
for (var legend of legendMap.values()) {
this.graphLegend.push(legend) // 加入图例记录
}
},
changeProperShow () { // 属性展示开关
if (this.properShow) {
this.showProperNodes()
} else {
this.hideProperNodes()
}
},
showProperNodes () { // 展示属性节点
var that = this
var properNodes = []// 属性节点定义
var relations = [] // 关系定义
that.visGraph.nodes.forEach((node) => {
if (node.visible) {
var attributes = node.properties.attributes || []// 节点属性
attributes.forEach((attr, index) => {
properNodes.push({
id: `${node.id}-${index}`, // 虚拟节点
label: attr.name,
type: 'proper-virtual', // 定义一个虚拟类型,用于后续处理
x: node.x,
y: node.y,
color: '240,240,240', // 属性节点的颜色
fontColor: '50,50,50', // 属性节点的字体颜色
borderWidth: 4, // 属性节点边框宽度
borderColor: '110,110,240', // 属性节点的边框颜色
properties: {
attr: attr // 自定义属性定义
}
})
relations.push({
id: `edge-${node.id}-${index}`,
source: node.id,
target: `${node.id}-${index}`,
label: '属性',
type: 'proper-virtual',
lineDash: [3, 5], // 属性设置虚线
properties: {
type: 'virtual' // 自定义属性定义
}
})
})
}
})
that.visGraph.addNodes(properNodes)// 添加属性节点
that.visGraph.addEdges(relations)// 添加属性关系
that.reLayout()
},
hideProperNodes () { // 隐藏属性节点
var that = this
var properNodes = that.visGraph.nodes.filter((node) => {
return node.type == 'proper-virtual'
})
if (properNodes.length > 0) {
that.visGraph.deleteNodes(properNodes)
}
},
// 执行布局算法
reLayout (alpha) {
var that = this
if (alpha == null) {
that.visLayout = null
that.visLayout = new LayoutFactory(this.visGraph.getGraphData()).createLayout('fastFR')
that.visLayout.resetConfig({
friction: 0.8,
linkDistance: 200,
linkStrength: 0.2,
charge: -250,
gravity: 0.01,
noverlap: false,
size: [that.visGraph.stage.width, that.visGraph.stage.height]
})
} else {
that.visLayout.alpha += (alpha > 1 ? 0.2 : alpha) // 继续运动
}
runLayout()// 开始继续动画执行
// 通过动画帧控制控制布局算法的执行,有动画效果
function runLayout () {
cancelAnimationFrame(that.layoutLoopName)// 停止动画控制
that.visLayout.runLayout() // 运行布局算法
that.visGraph.refresh()
if (that.visLayout.alpha > 0.05) {
that.layoutLoopName = requestAnimationFrame(runLayout)
} else {
if (that.visGraph.currentNode && that.visGraph.currentNode.isDragging) {
that.visLayout.alpha = 0.1 // 继续运动
that.layoutLoopName = requestAnimationFrame(runLayout)
} else {
that.visLayout.alpha = 0 // 停止运动
cancelAnimationFrame(that.layoutLoopName)
}
}
}
},
// 显示节点信息
showNodeDeteail () {
if (this.currentNode.type == 'proper-virtual') {
this.properNode = this.currentNode.properties.attr || {}
this.showProperNodeInfo = true// 显示属性节点信息
} else {
this.showNodeInfoDialog = true// 显示实体节点信息
}
this.cancelNodeRightMenu()
},
showLinkDetail () {
this.showLinkInfoDialog = true
this.cancelLinkRightMenu()
},
// 关闭节点提示工具栏
cancelNodeRightMenu () {
let nodeRightMenuLayer = document.getElementById('nodeRightMenuPanel')
nodeRightMenuLayer.style.display = 'none'
},
// 关闭知识联系操作栏
cancelLinkRightMenu () {
let linkRightMenuLayer = document.getElementById('linkRightMenuPanel')
linkRightMenuLayer.style.display = 'none'
},
showOrHideType (itemIndex) { // 图例的点击事件
var legend = this.graphLegend[itemIndex]
if (legend != null) {
if (legend.show) { // 需要隐藏类型节点
legend.show = false
} else { // 需要显示类型节点
legend.show = true
}
this.graphLegend[itemIndex] = legend
this.renderNodesByLegend()// 重新渲染节点图例
}
},
renderNodesByLegend () { // 根据节点图例渲染
var legendMap = new Map()
this.graphLegend.forEach((item) => {
legendMap.set(item.type, item)
})
var that = this
var legend
var hideNodes = []
this.visGraph.nodes.forEach((node) => {
legend = legendMap.get(node.type) || {show: true}
that.resetNodesVisible(node, true)
if (!legend.show) {
hideNodes.push(node)
}
})
hideNodes.forEach((node) => {
that.resetNodesVisible(node, false)
})
},
resetNodesVisible (node, visible = true) { // 显示或隐藏节点及其关系
node.visible = visible;
(node.inLinks || []).forEach((link) => {
link.visible = visible
});
(node.outLinks || []).forEach((link) => {
link.visible = visible
// 如果是虚拟属性节点,则跟着实体节点显示或隐藏
if (link.target.type == 'proper-virtual') {
link.target.visible = visible
}
})
this.visGraph.refresh()
},
showTipLayer (event) { // 显示提示层
this.tipLayer.show = true
const tipDom = document.getElementById('tip-layer')
tipDom.style.top = event.clientY + 5 + 'px'
tipDom.style.left = event.clientX + 10 + 'px'
},
hideNode () { // 隐藏节点(画布上的删除)
var properNodes = (this.visGraph.currentNode.outLinks || []).map(function (link) {
return link.target.type == 'proper-virtual' ? link.target : null
}).filter((node) => {
return node != null
})
var that = this
if (properNodes.length > 0) {
that.visGraph.deleteNodes(properNodes)// 删除属性节点
}
this.visGraph.deleteNode(this.visGraph.currentNode)// 删除当前节点
this.cancelNodeRightMenu()
},
hideLink () { // 隐藏连线
this.visGraph.deleteLink(this.visGraph.currentLink)
this.cancelLinkRightMenu()
},
toolBarEvent (eventType, type) { // 工具栏事件
if (eventType == 'zoom') {
this.visGraph.setZoom(type)
} else if (eventType == 'layout') {
cancelAnimationFrame(this.layoutLoopName)
if (type == 'hubsize') {
this.sortLayout()
} else {
this.forceLayout()
}
}
},
sortLayout () { // 树形结构布局
this.currentLayoutType = 'hubsize'// 设置当前布局类型
var tempLayout = new LayoutFactory(this.visGraph.getGraphData()).createLayout('hubsize')
tempLayout.resetConfig({
'layerDistance': 150,
'nodeDistance': 100,
'sortMethod': 'hubsize',
'direction': 'LR'
})
tempLayout.boolTransition = false
tempLayout.runLayout()
this.visGraph.nodes.forEach(function (node) {
node.fixed = true
})
this.visGraph.moveCenter()
},
forceLayout () { // 力导向布局
this.currentLayoutType = 'fastFR'// 设置当前布局类型
this.visGraph.nodes.forEach((node) => {
node.fixed = false
})
var tempLayout = new LayoutFactory(this.visGraph.getGraphData()).createLayout('fastFR')
tempLayout.resetConfig({
friction: 0.8,
linkDistance: 200,
linkStrength: 0.2,
charge: -250,
gravity: 0.01,
noverlap: false,
size: [this.visGraph.stage.width, this.visGraph.stage.height]
})
var count = 0
while (count++ <= 100) {
tempLayout.runLayout()
}
this.visGraph.moveCenter()
},
expandNode (node) { // 双击扩展节点数据
// TODO 需要从服务端查询节点数据,数据结构如demo2格式,以下为构造数据示例
var newNodes = [], newRelations = []
for (var i = 1; i <= 10; i++) {
var tempId = Math.round(Math.random() * 999999999)
newNodes.push({
id: 'expandnode-' + tempId,
label: '扩展节点-' + i,
type: '扩展节点类型'
// color: this.randomColor()
})
newRelations.push({
id: 'exedge-' + tempId,
label: '扩展',
source: node.id,
target: 'expandnode-' + tempId
})
}
// 计算新节点的旋转坐标
this.visGraph.incremaNodesCodinate(newNodes)
this.visGraph.addNodes(newNodes)
this.visGraph.addEdges(newRelations)
this.reLayout()// 执行动态布局计算
},
contractChildNode (node) { // 收起节点的叶子节点
var leafNodes = [], relations = [];
(node.outLinks || []).forEach(function (l) {
if ((l.target.outLinks || []).length == 0 && (l.target.inLinks || []).length == 1) {
leafNodes.push(l.target)
relations.push(l)
}
})
var that = this
that.visGraph.deleteNodes(leafNodes)// 删除叶子节点
that.visGraph.deleteLinks(relations)// 删除叶子连线
this.reLayout()// 执行动态布局计算
},
randomColor () {
return Math.floor(255 * Math.random()) + ',' +
Math.floor(180 * Math.random()) + ',' +
Math.floor(255 * Math.random())
},
drawDefinedNode (node) { // 绘制自定义节点
node.drawNode = function (ctx) { // 绘制自定义节点
if (!this.openAnimation) { // 通过openAnimation控制是否绘制自定义
this.drawOriginalNode(ctx)// 系统内置绘制方法
} else {
// 以下部分为自定义绘制部分,支持动画效果
this.animate = this.animate > 50 ? 10 : this.animate
ctx.save()
ctx.beginPath()
this.paintShadow(ctx)
ctx.arc(0, 0, this.radius, 0, 2 * Math.PI)
ctx.fillStyle = `rgba(${this.fillColor},${this.alpha})`
ctx.fill()
ctx.strokeStyle = `rgba(${this.borderColor},${this.alpha})`
ctx.stroke()
ctx.closePath()
ctx.restore()
if (this.image) { // 有图片的场景,需要绘制图片
var b = ctx.globalAlpha
ctx.save()
ctx.globalAlpha = this.alpha
this.drawNodeImg(ctx, this.image, -this.radius, -this.radius, this.radius)
ctx.globalAlpha = b
ctx.restore()
}
// 内环 - 间隔实线环
this.angleStart = (this.angleStart == null || this.angleStart >= 0.51) ? 0 : this.angleStart += 0.01
ctx.save()
ctx.strokeStyle = `rgba(${this.selectedBorderColor},${this.alpha})`
for (var i = 0; i < 4; i++) {
ctx.beginPath()
ctx.arc(0, 0, this.radius + 3, Math.PI * (0.5 * i), Math.PI * (this.angleStart + (0.5 * i)))
ctx.stroke()
ctx.closePath()
}
ctx.restore()
// 外环-虚线圆环
ctx.save()
ctx.beginPath()
ctx.arc(0, 0, this.radius + 8, 0, 2 * Math.PI)
ctx.strokeStyle = `rgba(${this.selectedBorderColor},${this.alpha})`
ctx.setLineDash([3, 2])
ctx.lineDashOffset = this.animate++
ctx.stroke()
ctx.closePath()
ctx.restore()
// 绘制文字,调用系统内置绘制文字方法
this.paintText(ctx)
}
}
},
drawAnimateCircleNode (node) { // 绘制旋转背景图
var that = this
// 实例化图片,作为背景图
if (node.circleBgImage == null) {
var circleImg = new Image()
circleImg.setAttribute('crossOrigin', 'Anonymous')
circleImg.src = that.circleBgImage // 图片资源
circleImg.onload = function () {
node.circleBgImage = circleImg // 图片加载完成赋值
}
}
// 绘制自定义节点方法重写
node.drawNode = function (ctx) {
if (this.openAnimation) { // 通过openAnimation控制是否绘制自定义
this.animate = this.animate > 360 ? 0 : this.animate += 0.02
var globleAlpha = ctx.globalAlpha
ctx.save()
ctx.rotate(this.animate) // 图片旋转动画控制
ctx.globalAlpha = this.alpha
this.drawNodeImg(ctx, this.circleBgImage, -this.radius - 15, -this.radius - 15, this.radius + 15)// 绘制背景图
ctx.globalAlpha = globleAlpha
ctx.restore()
}
this.drawOriginalNode(ctx)// 调用系统内置绘制方法
}
}
},
created () {
var that = this
// 节点的点击事件
this.config.node.onClick = function (event, node) {
node.color = 'rgb(' + node.fillColor + ')'
that.currentNode = node
that.tipLayer.header = node.label || ''
that.tipLayer.data = node.properties.attributes || [] // 节点属性列表
that.attrbutes = node.properties.attributes || [] // 节点属性列表
that.showTipLayer(event)
}
// 节点的双击事件
this.config.node.ondblClick = function (event, node) {
if (!node.isExpand) {
node.fixed = true // 固定位置
node.isExpand = true // 展开标识
node.openAnimation = true // 启用节点动画特效
that.expandNode(node)// 节点双击展开
that.drawDefinedNode(node) // 方案1:绘制自定义动画
// that.drawAnimateCircleNode(node); //方案2:绘制指定背景图
that.visGraph.switchAnimate(true) // 开启全局动画开关(耗性能,按需开启和关闭)
} else {
node.fixed = false // 解除锁定
node.isExpand = false // 展开标识关闭
that.contractChildNode(node)// 节点双击收起
node.openAnimation = false // 关闭节点动画特效
node.drawNode = null // 去掉自定义节点绘制
that.visGraph.switchAnimate(false) // 关闭全局动画(减小性能开销)
}
that.tipLayer.show = false // 关闭提示层
that.generateLegend()// 重新渲染图例类型
}
// 节点鼠标弹起事件
this.config.node.onMouseUp = function (event, node) {
if (that.currentLayoutType == 'fastFR') { // 如果当前为力导向布局时才可触发力导计算
that.reLayout(0.2)
}
}
// 节点拖拽事件
this.config.node.onMousedrag = function (event, node) {
that.tipLayer.show = false // 关闭提示层
if (that.currentLayoutType == 'fastFR') { // 如果当前为力导向布局时才可触发力导计算
that.reLayout(0.05)
}
}
// 连线的点击事件
this.config.link.onClick = function (event, link) { // 节点点击事件回调
that.currentLink = link
that.tipLayer.header = link.label || ''
that.tipLayer.data = link.properties.attributes || [] // 关系属性列表
that.attrbutes = link.properties.attributes || [] // 关系属性列表
that.showTipLayer(event)
}
// 空白处的点击事件
this.config.noElementClick = function (event) {
that.currentNode = {}
that.currentLink = {}
that.tipLayer.show = false // 关闭提示层
}
// 右键配置
this.config.rightMenu = {
nodeMenu: { // 节点右键菜单
nodeRightMenuLayer: null,
init: function (_graphvis) {
if (!that.nodeRightMenuLayer) {
that.nodeRightMenuLayer = document.getElementById('nodeRightMenuPanel')
}
},
show: function (event, _graphvis) {
this.init()
if (that.nodeRightMenuLayer) {
that.nodeRightMenuLayer.style.left = (event.clientX + 10) + 'px'
that.nodeRightMenuLayer.style.top = (event.clientY - 5) + 'px'
that.nodeRightMenuLayer.style.display = 'block'
}
var node = _graphvis.currentNode
node.color = 'rgb(' + node.fillColor + ')'
that.currentNode = node
that.attrbutes = node.properties.attributes || []
},
hide: function () {
if (that.nodeRightMenuLayer) {
that.nodeRightMenuLayer.style.display = 'none'
}
}
},
linkMenu: { // 连线右键菜单
linkDialog: null,
init: function (_graphvis) {
if (!that.linkDialog) {
that.linkDialog = document.getElementById('linkRightMenuPanel')
}
},
show: function (event, _graphvis) {
this.init()
if (that.linkDialog) {
that.linkDialog.style.left = (event.clientX + 10) + 'px'
that.linkDialog.style.top = (event.clientY - 5) + 'px'
that.linkDialog.style.display = 'block'
}
var link = _graphvis.currentLink
that.currentLink = link
that.attrbutes = link.properties.attributes || []
},
hide: function () {
if (that.linkDialog) {
that.linkDialog.style.display = 'none'
}
}
}
}
},
mounted () {
// 初始化加载绘图
this.drawGraphData()
},
destroyed () {
cancelAnimationFrame(this.layoutLoopName)
}
}
</script>
<style scoped>
/*****页面主要布局样式定义******/
.graph-nav{
height: 80px;
background-color: #fff;
line-height: 26px;
font-size: 13px;
padding: 10px 20px;
}
.graph-area {
position: relative;
height: calc(100% - 105px);
margin: 0 5px 5px 5px;
padding: 0;
background-color: #fafafa;
border: 1px solid #ddd;
}
/******工具栏*******/
.toolbar{
position: absolute;
right: 0px;
top: 0px;
background: #efefef;
}
.toolbar .toolbar-item{
width:40px;
height:40px;
line-height:40px;
font-size:18px;
text-align:center;
color:#888;
border-bottom:1px solid #ddd;
cursor: pointer;
}
.toolbar .toolbar-item:hover{
color:deepskyblue;
}
/*******图例区域样式定义*****/
.legend-wrap{
position: absolute;
left:10px;
bottom: 10px;
}
.legend-wrap > .legend-item{
height:24px;
line-height: 24px;
font-size: 12px;
}
.legend-item > .item-dot{
float: left;
display: inline-block;
height:20px;
width: 20px;
border-radius: 10px;
background-color: rgb(210,210,210);
cursor: pointer;
}
.legend-item > .item-label{
display: inline-block;
float: left;
margin-left: 5px;
max-width: 120px;
}
/*****节点弹出鼠标提示层样式*****/
.tip-wrap{
position: absolute;
width: 350px;
height: auto;
min-height: 150px;
background: #fff;
box-shadow: 0px 0px 10px #999;
font-size: 14px;
}
.tip-wrap > .tip-header{
height:30px;
line-height: 30px;
padding: 5px 10px;
border-bottom: 1px solid #ddd;
}
.tip-wrap > .tip-body{
padding: 0 10px 10px;
}
/*****右键菜单样式******/
.right-menu-layer {
position: absolute;
width: 100px;
z-index: 5;
display: none;
border-radius: 3px;
overflow: hidden;
background: #fafafa;
border: 1px solid #e1e2e2;
box-shadow: 0 0 5px #ddd;
padding: 5px 3px;
}
.right-menu-layer button {
display: block;
height:24px;
line-height: 24px;
background: transparent;
border: none;
color: #444;
text-align:center;
cursor: pointer;
}
.right-menu-layer button > i{
margin-right: 5px;
}
.right-menu-layer button:hover {
color: slateblue;
}
.right-menu-layer button:focus {
outline:0;
}
.box-card{
box-shadow: 0 0 3px #d4c8c8;
padding: 10px;
margin-bottom: 10px;
}
</style>
js配件:graphvis.20230812.js 和 graphvis.layout.min.js 官网下载
默认defaultConfig配置文件:
export const config = {
node: { // 节点的默认配置
label: { // 标签配置
show: true, // 是否显示
color: '50,50,50', // 字体颜色
font: '12px 微软雅黑', // 字体大小及类型
wrapText: false, // 节点包裹文字 (节点大小由文字大小决定,绘制较耗性能)
textPosition: 'Bottom_Center',// 文字位置 Middle_Center,Bottom_Center
//textOffsetY:8, //文字竖向偏移距离
//background:'250,250,250', //文字背景颜色
//borderColor:'100,120,220' //背景边框
},
shape: 'circle', // 节点形状 circle
color: '50,155,230', // 节点颜色
borderColor: '255,255,255', // 边框颜色
borderWidth: 0, // 边框宽度,
lineDash: [0], // 边框虚线间隔,borderWidth>0时生效
alpha: 1, // 节点透明度
size: 66, // 节点大小
selected: { // 选中时的样式设置
borderColor: '50,120,230', // 选中时边框颜色
borderAlpha: 1, // 选中时的边框透明度
borderWidth: 4, // 选中是的边框宽度
showShadow: true, // 是否展示阴影
shadowBlur:30, //阴影范围大小
shadowColor: '50,80,250'// 选中是的阴影颜色
}
},
link: { // 连线的默认配置
label: { // 连线标签
show: true, // 是否显示
color: '50,50,50', // 字体颜色
font: '12px 微软雅黑',// 字体大小及类型
background:'250,250,250',
borderColor:'250,250,250'
},
lineType: 'direct', // 连线类型,direct,curver,
arrowType:'thired',
colorType: 'defined', // 连线颜色类型 source:继承source颜色,target:继承target颜色 both:用双边颜色,defined:自定义
color: '145,145,155', // 连线颜色
alpha: 1, // 连线透明度
lineWidth: 2, // 连线宽度
lineDash: [0], // 虚线间隔样式如:[5,8]
showArrow: true, // 显示箭头
selected: { // 选中时的样式设置
color: '50,120,230', // 选中时的颜色
alpha: 1,
showShadow: false, // 是否展示阴影
shadowColor: '250,40,30'// 选中连线时的阴影颜色
}
},
highLightNeiber: true, // 相邻节点高亮开关
wheelZoom: 0.8// 滚轮缩放开关,不使用时不设置[0,1]
}
demo2数据:
export const demoData = {
"nodes":[
{
"id":"123456",
"label":"爱妃术有限公司",
"color":"rgb(233,120,120)",
"type":"公司",
"properties":{
"attributes":[
{"name":"属性一","type":"字符型","unit":"项"},
{"name":"属性二","type":"数值型","unit":"个"},
{"name":"属性三","type":"字符型","unit":"次"}
]
}
},
{
"id":"2222",
"label":"山东暗访十年有限公司",
"color":"rgb(233,120,120)",
"type":"公司",
"properties":{
"attributes":[
{"name":"属性一","type":"字符型","unit":"项"},
{"name":"属性二","type":"数值型","unit":"个"},
{"name":"属性三","type":"字符型","unit":"次"}
]
}
},
{
"id":"3333",
"label":"山东阿萨德集团有限公司",
"color":"rgb(233,120,120)",
"type":"公司",
"properties":{
"attributes":[
{"name":"属性一","type":"字符型","unit":"项"},
{"name":"属性二","type":"数值型","unit":"个"},
{"name":"属性三","type":"字符型","unit":"次"}
]
}
},
{
"id":"4444",
"label":"贾墀臭",
"color":"rgb(120,120,233)",
"type":"公司员工",
"properties":{
"attributes":[
{"name":"属性一","type":"字符型","unit":"项"}
]
}
},
{
"id":"5555",
"label":"邓愍摸",
"color":"rgb(120,233,120)",
"type":"公司高管",
"properties":{
"attributes":[
{"name":"属性一","type":"字符型","unit":"项"},
{"name":"属性二","type":"数值型","unit":"个"}
]
}
},
{
"id":"6666",
"label":"邓哿薪",
"color":"rgb(120,233,120)",
"type":"公司高管",
"properties":{
"attributes":[
{"name":"属性一","type":"字符型","unit":"项"},
{"name":"属性二","type":"数值型","unit":"个"}
]
}
},
{
"id":"7777",
"label":"孟断簏",
"color":"rgb(120,120,233)",
"type":"公司员工",
"properties":{
"attributes":[
{"name":"属性一","type":"字符型","unit":"项"}
]
}
}
],
"links":[
{
"id":"e111",
"source":"3333",
"target":"123456",
"type":"子公司",
"properties":{
"attributes":[
{"name":"关系属性","type":"字符型","unit":"项"}
]
}
},
{
"id":"e222",
"source":"3333",
"target":"4444",
"type":"雇员",
"properties":{
"attributes":[
{"name":"关系属性","type":"字符型","unit":"项"}
]
}
},
{
"id":"e333",
"source":"2222",
"target":"7777",
"type":"员工",
"properties":{
"attributes":[
{"name":"关系属性","type":"字符型","unit":"项"}
]
}
},
{
"id":"e444",
"source":"3333",
"target":"7777",
"type":"员工",
"properties":{
"attributes":[
{"name":"关系属性","type":"字符型","unit":"项"}
]
}
},
{
"id":"e555",
"source":"3333",
"target":"2222",
"type":"投资",
"properties":{
"attributes":[
{"name":"关系属性","type":"字符型","unit":"项"}
]
}
},
{
"id":"e666",
"source":"2222",
"target":"3333",
"type":"子公司"
},
{
"id":"e777",
"source":"2222",
"target":"6666",
"type":"股东"
},
{
"id":"e888",
"source":"3333",
"target":"6666",
"type":"董事长",
"properties":{
"attributes":[
{"name":"关系属性","type":"字符型","unit":"项"}
]
}
},
{
"id":"e999",
"source":"2222",
"target":"5555",
"type":"总经理"
}
]
};