CN-1548 feat: 调整节点样式

This commit is contained in:
chenjinsong
2024-06-18 15:40:20 +08:00
parent e884b20882
commit 4b769e8cd6
4 changed files with 284 additions and 262 deletions

View File

@@ -49,7 +49,6 @@ import { useRoute } from 'vue-router'
import { ref, shallowRef } from 'vue' import { ref, shallowRef } from 'vue'
import ForceGraph from 'force-graph' import ForceGraph from 'force-graph'
import { Algorithm } from '@antv/g6' import { Algorithm } from '@antv/g6'
// import ForceGraph3D from '3d-force-graph'
import * as d3 from 'd3' import * as d3 from 'd3'
import Node, { nodeType } from './entityGraph/node' import Node, { nodeType } from './entityGraph/node'
import Link, { linkType } from './entityGraph/link' import Link, { linkType } from './entityGraph/link'
@@ -71,9 +70,8 @@ export default {
links: [], links: [],
graph: shallowRef(null), graph: shallowRef(null),
defaultChargeStrength: -20, // 之前的设置-20 defaultChargeStrength: -20, // 之前的设置-20
defaultLinkDistance: 40, defaultLinkDistance: 80,
defaultMargin: 2, // 图像与箭头的距离 defaultMargin: 2, // 图像与箭头的距离
clickNode: null,
rootNode: null rootNode: null
} }
}, },
@@ -82,110 +80,193 @@ export default {
try { try {
const initialData = await this.generateInitialData() const initialData = await this.generateInitialData()
this.initialData = _.cloneDeep(initialData) // 初始化数据 this.initialData = _.cloneDeep(initialData) // 初始化数据
console.info(initialData)
let hoverNode = null let hoverNode = null
this.graph = ForceGraph()(document.getElementById('entityGraph')) this.graph = ForceGraph()(document.getElementById('entityGraph'))
.graphData(initialData) .graphData(initialData)
.nodeCanvasObject((node, ctx) => { .nodeCanvasObject((node, ctx) => {
/*
* 共有4种 nodeType3种 entityType
* */
const nodeStyle = this.getNodeStyle(node.type, node.data.entityType) const nodeStyle = this.getNodeStyle(node.type, node.data.entityType)
const iconWidth = nodeStyle.iconWidth / 2 switch (node.type) {
const iconHeight = nodeStyle.iconHeight / 2 case nodeType.rootNode: {
const x = node.x - iconWidth / 2 // 如果是鼠标点击高亮的,最外层加上第三层圆环
const y = node.y - iconHeight / 2 if (node === this.clickNode) {
ctx.beginPath()
ctx.arc(node.x, node.y, nodeStyle.selectedShadowR, 0, 2 * Math.PI, false)
ctx.closePath()
ctx.fillStyle = nodeStyle.selectedShadowColor
ctx.fill()
}
if (node.type === nodeType.rootNode) { // 第二层圆环
if (this.clickNode && this.clickNode.type === nodeType.rootNode) {
ctx.beginPath() ctx.beginPath()
ctx.lineWidth = 1 ctx.arc(node.x, node.y, nodeStyle.shadowR, 0, 2 * Math.PI, false)
ctx.strokeStyle = 'transparent' ctx.closePath()
ctx.arc(node.x, node.y, nodeStyle.selectedShadowR / 2, 0, Math.PI * 2) ctx.fillStyle = node === this.clickNode || node === hoverNode ?
ctx.fillStyle = nodeStyle.selectedShadowColor// 先画圆形,才能填色 nodeStyle.hoveredShadowColor :
nodeStyle.shadowColor
ctx.fill()
// 内部挖空
ctx.beginPath()
ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false)
ctx.closePath()
ctx.fillStyle = nodeStyle.fillStyle
ctx.fill() ctx.fill()
ctx.stroke()
// 第一层圆环
ctx.beginPath() ctx.beginPath()
ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false)
ctx.closePath()
ctx.lineWidth = 1 ctx.lineWidth = 1
ctx.strokeStyle = 'transparent' ctx.strokeStyle = nodeStyle.borderColor
ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2)
ctx.fillStyle = nodeStyle.shadowColor
ctx.fill()
ctx.stroke()
} else if (hoverNode && hoverNode.id === node.id) {
ctx.beginPath()
ctx.lineWidth = 1
ctx.strokeStyle = 'transparent'
ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2)
ctx.fillStyle = nodeStyle.hoveredShadowColor
ctx.fill()
ctx.stroke()
} else {
ctx.beginPath()
ctx.lineWidth = 1
ctx.strokeStyle = 'transparent'
ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2)
ctx.fillStyle = nodeStyle.shadowColor
ctx.fill()
ctx.stroke() ctx.stroke()
// 图片
ctx.drawImage(node.img, node.x - nodeStyle.iconWidth / 2, node.y - nodeStyle.iconHeight / 2, nodeStyle.iconWidth, nodeStyle.iconHeight);
// 文字
ctx.font = nodeStyle.font
const textWidth = ctx.measureText(node.label).width
const bckgDimensions = [textWidth, 9].map(n => n + 9 * 0.2)
ctx.fillStyle = nodeStyle.fontBgColor
ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2 + 31, ...bckgDimensions) // 文字的白底
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillStyle = nodeStyle.fontColor
ctx.fillText(node.label, node.x, node.y + 30)
break
} }
} else if (node.type === nodeType.listNode) { case nodeType.listNode: {
if (this.clickNode && this.clickNode.id === node.id) { // 如果是鼠标点击或者悬停的,有一层圆环
if (node === this.clickNode || node === hoverNode) {
ctx.beginPath()
ctx.arc(node.x, node.y, nodeStyle.shadowR, 0, 2 * Math.PI, false)
ctx.closePath()
if (node === this.currentSelectedNode) {
ctx.fillStyle = nodeStyle.selectedShadowColor
} else {
ctx.fillStyle = nodeStyle.hoveredShadowColor
}
ctx.fill()
}
// 内部填白
ctx.beginPath() ctx.beginPath()
ctx.lineWidth = 1 ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false)
ctx.strokeStyle = 'transparent' ctx.closePath()
ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2) ctx.fillStyle = nodeStyle.fillStyle
ctx.fillStyle = nodeStyle.selectedShadowColor
ctx.fill() ctx.fill()
ctx.stroke()
} else if (hoverNode && hoverNode.id === node.id) { // 第一层圆环
ctx.beginPath() ctx.beginPath()
ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false)
ctx.closePath()
ctx.lineWidth = 1 ctx.lineWidth = 1
ctx.strokeStyle = 'transparent' ctx.strokeStyle = nodeStyle.borderColor
ctx.arc(node.x, node.y, nodeStyle.shadowR / 2, 0, Math.PI * 2)
ctx.fillStyle = nodeStyle.hoveredShadowColor
ctx.fill()
ctx.stroke() ctx.stroke()
// 图片
ctx.drawImage(node.img, node.x - nodeStyle.iconWidth / 2, node.y - nodeStyle.iconHeight / 2, nodeStyle.iconWidth, nodeStyle.iconHeight)
// 文字
ctx.font = nodeStyle.font
const textWidth = ctx.measureText(node.label).width
const bckgDimensions = [textWidth, 8].map(n => n + 8 * 0.2)
ctx.fillStyle = nodeStyle.fontBgColor
ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2 + 24, ...bckgDimensions) // 文字的白底
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillStyle = nodeStyle.fontColor
ctx.fillText(node.label, node.x, node.y + 24)
break
}
case nodeType.entityNode: {
// 先画个白底圆环,避免 link 穿过不好看
ctx.beginPath()
ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false)
ctx.closePath()
ctx.fillStyle = nodeStyle.fillStyle
ctx.fill()
// 如果是鼠标点击或者悬停的,有一层圆环
if (node === this.clickNode || node === hoverNode) {
ctx.beginPath()
ctx.arc(node.x, node.y, nodeStyle.shadowR, 0, 2 * Math.PI, false)
ctx.closePath()
if (node === this.clickNode) {
ctx.fillStyle = nodeStyle.selectedShadowColor
} else {
ctx.fillStyle = nodeStyle.hoveredShadowColor
}
ctx.fill()
}
// 图片
ctx.drawImage(node.img, node.x - nodeStyle.iconWidth / 2, node.y - nodeStyle.iconHeight / 2, nodeStyle.iconWidth, nodeStyle.iconHeight)
break
}
case nodeType.tempNode: {
// 先画个白底圆环,避免 link 穿过不好看
ctx.beginPath()
ctx.arc(node.x, node.y, nodeStyle.innerR, 0, 2 * Math.PI, false)
ctx.closePath()
ctx.fillStyle = nodeStyle.fillStyle
ctx.fill()
// 画透明度0.7的图标
ctx.globalAlpha = 0.7
ctx.drawImage(node.img, node.x - nodeStyle.iconWidth / 2, node.y - nodeStyle.iconHeight / 2, nodeStyle.iconWidth, nodeStyle.iconHeight)
ctx.globalAlpha = 1
break
} }
} }
ctx.beginPath()
ctx.lineWidth = 0.5
ctx.strokeStyle = nodeStyle.borderColor// 边的颜色
ctx.arc(node.x, node.y, nodeStyle.innerR / 2, 0, Math.PI * 2)
ctx.fillStyle = nodeStyle.fill// 中间的填充色
ctx.fill()
ctx.stroke()
ctx.drawImage(node.img, x, y, iconWidth, iconHeight)
if (node.type === nodeType.rootNode) {
ctx.font = nodeStyle.font
ctx.fillStyle = nodeStyle.fillStyle
ctx.textAlign = nodeStyle.textAlign
ctx.textBaseline = nodeStyle.textBaseline
ctx.fillText(node.label, node.x, node.y + nodeStyle.selectedShadowR / 2 + 5)
} else if (node.type === nodeType.listNode || node.type === nodeType.tempNode) {
ctx.beginPath()
ctx.lineWidth = 1
ctx.strokeStyle = '#FFFFFF'// 边的颜色
ctx.rect(node.x - 19, node.y + nodeStyle.innerR / 2 + 8 - 6, 38, 12)
ctx.fillStyle = '#FFFFFF'
ctx.fill()
ctx.stroke()
ctx.font = nodeStyle.font
ctx.fillStyle = nodeStyle.fillStyle
ctx.textAlign = nodeStyle.textAlign
ctx.textBaseline = nodeStyle.textBaseline
ctx.fillText(node.label, node.x, node.y + nodeStyle.innerR / 2 + 8)
}
})
.nodePointerAreaPaint((node, color, ctx) => { // 鼠标hover起作用的范围
const nodeStyle = this.getNodeStyle(node.type, node.data.entityType)
const size = nodeStyle.innerR
ctx.fillStyle = color
ctx.fillRect(node.x - size / 2, node.y - size / 2, size, size) // draw square as pointer trap
}) })
.linkCanvasObject((link, ctx) => { .linkCanvasObject((link, ctx) => {
if (link.source.x !== undefined && link.source.y !== undefined && link.target.x !== undefined && link.target.y !== undefined) { const start = link.source
const end = link.target
const width = 1 // 线宽
const arrowSize = 3 // 箭头大小
const shortenedLength = 20 // link 末端缩短长度
// 计算箭头角度
const dx = end.x - start.x
const dy = end.y - start.y
const angle = Math.atan2(dy, dx) // 计算与x轴的角度弧度
const lineEndX = end.x - shortenedLength * Math.cos(angle)
const lineEndY = end.y - shortenedLength * Math.sin(angle)
const arrowEndX = lineEndX + arrowSize * Math.cos(angle)
const arrowEndY = lineEndY + arrowSize * Math.sin(angle)
// 绘制线
let color
ctx.beginPath()
if (link.type === linkType.temp) {
ctx.setLineDash([2, 2])
color = 'rgba(119,131,145,0.2)' // 虚线颜色
} else {
ctx.setLineDash([])
if (this.clickNode === link.source || this.clickNode === link.target) {
color = 'rgba(119,131,145,0.8)' // 高亮线颜色
} else {
color = 'rgba(119,131,145,0.3)' // 普通线颜色
}
}
ctx.moveTo(start.x, start.y)
ctx.lineTo(lineEndX, lineEndY)
ctx.strokeStyle = color
ctx.lineWidth = width
ctx.stroke()
// 绘制箭头
ctx.save() // 保存当前状态以便之后恢复
ctx.translate(arrowEndX, arrowEndY) // 将坐标原点移动到箭头末端
ctx.rotate(angle) // 根据链接方向旋转坐标系
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(-arrowSize, arrowSize) // 绘制箭头的一边
ctx.lineTo(-arrowSize, -arrowSize) // 绘制箭头的另一边
ctx.closePath()
ctx.fillStyle = color
ctx.fill()
ctx.restore() // 恢复之前保存的状态
/*if (link.source.x !== undefined && link.source.y !== undefined && link.target.x !== undefined && link.target.y !== undefined) {
const nodeStyle = this.getNodeStyle(link.target.type, link.target.data.entityType) const nodeStyle = this.getNodeStyle(link.target.type, link.target.data.entityType)
ctx.lineWidth = 0.5 ctx.lineWidth = 0.5
@@ -213,7 +294,7 @@ export default {
ctx.fill() ctx.fill()
ctx.closePath() ctx.closePath()
ctx.stroke() ctx.stroke()
} }*/
}) })
.autoPauseRedraw(false) // keep redrawing after engine has stopped如果您有依赖于画布的不断重绘的自定义动态对象建议关闭此选项。 .autoPauseRedraw(false) // keep redrawing after engine has stopped如果您有依赖于画布的不断重绘的自定义动态对象建议关闭此选项。
.onNodeHover(node => { .onNodeHover(node => {
@@ -223,6 +304,7 @@ export default {
} }
}) })
.centerAt(0, 30)// 设置中心节点位置 .centerAt(0, 30)// 设置中心节点位置
.zoom(1)
.onNodeClick(async (node, e) => { .onNodeClick(async (node, e) => {
this.clickNode = node || null this.clickNode = node || null
if (node.type !== 'tempNode') { if (node.type !== 'tempNode') {
@@ -256,7 +338,7 @@ export default {
this.getIconUrl(k, false, false) this.getIconUrl(k, false, false)
) )
const tempLink = new Link(node, tempNode, 'temp', this.defaultLinkDistance, 3) const tempLink = new Link(node, tempNode, linkType.temp)
toAddNodes.push(tempNode) toAddNodes.push(tempNode)
toAddLinks.push(tempLink) toAddLinks.push(tempLink)
} }
@@ -265,7 +347,7 @@ export default {
if (toAddNodes.length || toAddLinks.length) { if (toAddNodes.length || toAddLinks.length) {
this.addItems(toAddNodes, toAddLinks) this.addItems(toAddNodes, toAddLinks)
} }
this.rightBox.node = _.cloneDeep(node) this.rightBox.node = node
this.rightBox.mode = 'detail' this.rightBox.mode = 'detail'
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@@ -274,17 +356,17 @@ export default {
this.rightBox.loading = false this.rightBox.loading = false
} }
} else if (node.type === nodeType.listNode) { } else if (node.type === nodeType.listNode) {
this.rightBox.node = _.cloneDeep(node) this.rightBox.node = node
this.rightBox.mode = 'list' this.rightBox.mode = 'list'
} else if (node.type === nodeType.rootNode) { } else if (node.type === nodeType.rootNode) {
this.rightBox.node = _.cloneDeep(node) this.rightBox.node = node
this.rightBox.mode = 'detail' this.rightBox.mode = 'detail'
} }
} else { } else {
// 点击tempNode根据source生成listNode和entityNode以及对应的edge。查完entityNode的接口再删除临时node和edge。 // 点击tempNode根据source生成listNode和entityNode以及对应的edge。查完entityNode的接口再删除临时node和edge。
// 若已达第六层则只生成listNode不再展开entityNode // 若已达第六层则只生成listNode不再展开entityNode
const nodes = [] const nodes = []
const edges = [] const links = []
const sourceNode = node.getSourceNode(this.graph.graphData()) const sourceNode = node.getSourceNode(this.graph.graphData())
const listNode = new Node( const listNode = new Node(
nodeType.listNode, nodeType.listNode,
@@ -299,12 +381,12 @@ export default {
this.getIconUrl(node.data.entityType, false, false) this.getIconUrl(node.data.entityType, false, false)
) )
nodes.push(listNode) nodes.push(listNode)
edges.push(new Link(sourceNode, listNode, null, this.defaultLinkDistance, 2)) links.push(new Link(sourceNode, listNode, null))
// 判断listNode的sourceNode层级若大于等于10即第6层listNode则不继续拓展entity node并给用户提示。否则拓展entity node // 判断listNode的sourceNode层级若大于等于10即第6层listNode则不继续拓展entity node并给用户提示。否则拓展entity node
const level = this.getNodeLevel(listNode.sourceNode.id) const level = this.getNodeLevel(listNode.sourceNode.id)
if (level < 10) { if (level < 10) {
// this.rightBox.loading = true this.rightBox.loading = true
try { try {
const entities = await sourceNode.queryRelatedEntities(listNode.data.entityType) const entities = await sourceNode.queryRelatedEntities(listNode.data.entityType)
sourceNode.data.relatedEntities[listNode.data.entityType].list.push(...entities.list) sourceNode.data.relatedEntities[listNode.data.entityType].list.push(...entities.list)
@@ -314,9 +396,9 @@ export default {
entityName: entity.vertex, entityName: entity.vertex,
x: e.x + Math.random() * 100 - 50, x: e.x + Math.random() * 100 - 50,
y: e.y + Math.random() * 100 - 50 y: e.y + Math.random() * 100 - 50
}, listNode, this.defaultChargeStrength, this.getIconUrl(listNode.data.entityType, true, false)) }, listNode, this.defaultChargeStrength, this.getIconUrl(listNode.data.entityType, false, false))
nodes.push(entityNode) nodes.push(entityNode)
edges.push(new Link(listNode, entityNode, null, this.defaultLinkDistance, 2)) links.push(new Link(listNode, entityNode, null))
}) })
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@@ -327,7 +409,7 @@ export default {
} else { } else {
this.$message.warning(this.$t('tip.maxExpandDepth')) this.$message.warning(this.$t('tip.maxExpandDepth'))
} }
this.addItems(nodes, edges) this.addItems(nodes, links)
this.cleanTempItems() this.cleanTempItems()
// this.graph.layout() // this.graph.layout()
@@ -361,7 +443,10 @@ export default {
.d3Force('link', d3.forceLink().id(link => link.id) .d3Force('link', d3.forceLink().id(link => link.id)
.distance(link => { .distance(link => {
return link.distance if (link.source.type === nodeType.rootNode) {
return 160
}
return 80
}) })
.strength(link => { .strength(link => {
return link.strength return link.strength
@@ -541,27 +626,26 @@ export default {
return this.getEntityNodeStyle(entityType) return this.getEntityNodeStyle(entityType)
} }
case nodeType.tempNode: { case nodeType.tempNode: {
return this.getTempNodeStyle(entityType) return this.getEntityNodeStyle(entityType)
} }
} }
}, },
getRootNodeStyle (entityType) { getRootNodeStyle (entityType) {
const nodeStyle = { const nodeStyle = {
innerR: 26, innerR: 19,
fill: '#FFFFFF', fillStyle: '#FFFFFF',
shadowR: 31.5, shadowR: 25,
selectedShadowR: 38.5, selectedShadowR: 36,
font: '8px NotoSansSChineseRegular', font: '9px NotoSansSChineseRegular',
textAlign: 'center', fontColor: '#353636',
textBaseline: 'middle', fontBgColor: 'rgba(255,255,255,0.8)'
fillStyle: '#353636'
} }
switch (entityType) { switch (entityType) {
case 'ip': { case 'ip': {
return { return {
...nodeStyle, ...nodeStyle,
iconWidth: 26, iconWidth: 24,
iconHeight: 23, iconHeight: 21,
borderColor: 'rgba(126,159,84,1)', borderColor: 'rgba(126,159,84,1)',
shadowColor: 'rgba(126,159,84,0.21)', shadowColor: 'rgba(126,159,84,0.21)',
hoveredShadowColor: 'rgba(126,159,84,0.36)', hoveredShadowColor: 'rgba(126,159,84,0.36)',
@@ -571,7 +655,7 @@ export default {
case 'domain': { case 'domain': {
return { return {
...nodeStyle, ...nodeStyle,
iconWidth: 28, iconWidth: 24,
iconHeight: 24, iconHeight: 24,
borderColor: 'rgba(56,172,210,1)', borderColor: 'rgba(56,172,210,1)',
shadowColor: 'rgba(56,172,210,0.21)', shadowColor: 'rgba(56,172,210,0.21)',
@@ -582,8 +666,8 @@ export default {
case 'app': { case 'app': {
return { return {
...nodeStyle, ...nodeStyle,
iconWidth: 24, iconWidth: 21,
iconHeight: 26, iconHeight: 24,
borderColor: 'rgba(229,162,25,1)', borderColor: 'rgba(229,162,25,1)',
shadowColor: 'rgba(229,162,25,0.21)', shadowColor: 'rgba(229,162,25,0.21)',
hoveredShadowColor: 'rgba(229,162,25,0.36)', hoveredShadowColor: 'rgba(229,162,25,0.36)',
@@ -593,8 +677,8 @@ export default {
} }
return { return {
...nodeStyle, ...nodeStyle,
iconWidth: 26, iconWidth: 24,
iconHeight: 23, iconHeight: 21,
borderColor: 'rgba(126,159,84,1)', borderColor: 'rgba(126,159,84,1)',
shadowColor: 'rgba(126,159,84,0.21)', shadowColor: 'rgba(126,159,84,0.21)',
hoveredShadowColor: 'rgba(126,159,84,0.36)', hoveredShadowColor: 'rgba(126,159,84,0.36)',
@@ -602,153 +686,68 @@ export default {
} }
}, },
getListNodeStyle (entityType) { getListNodeStyle (entityType) {
const nodeStyle = { let iconWidth = 20
innerR: 21, let iconHeight = 18
shadowR: 26.5,
fill: '#FFFFFF',
font: '8px NotoSansSChineseRegular',
textAlign: 'center',
textBaseline: 'middle',
fillStyle: '#353636'
}
switch (entityType) { switch (entityType) {
case 'ip': { case 'ip': {
return { iconWidth = 20
...nodeStyle, iconHeight = 18
iconWidth: 24, break
iconHeight: 21,
borderColor: 'rgba(119,131,145,0.6)',
selectedBorderColor: 'rgba(126,159,84,1)',
hoveredShadowColor: 'rgba(151,151,151,0.21)',
selectedShadowColor: 'rgba(126,159,84,0.21)'
}
} }
case 'domain': { case 'domain': {
return { iconWidth = 20
...nodeStyle, iconHeight = 20
iconWidth: 24, break
iconHeight: 24,
borderColor: 'rgba(119,131,145,0.6)',
selectedBorderColor: 'rgba(56,172,210,1)',
hoveredShadowColor: 'rgba(151,151,151,0.21)',
selectedShadowColor: 'rgba(56,172,210,0.21)'
}
} }
case 'app': { case 'app': {
return { iconWidth = 18
...nodeStyle, iconHeight = 20
iconWidth: 22, break
iconHeight: 24,
borderColor: 'rgba(119,131,145,0.6)',
selectedBorderColor: 'rgba(229,162,25,1)',
hoveredShadowColor: 'rgba(151,151,151,0.21)',
selectedShadowColor: 'rgba(229,162,25,0.21)'
}
} }
} }
return { return {
...nodeStyle, innerR: 16,
iconWidth: 24, shadowR: 22,
iconHeight: 21, fillStyle: '#FFFFFF',
borderColor: 'rgba(119,131,145,0.6)', font: '9px NotoSansSChineseRegular',
selectedBorderColor: 'rgba(126,159,84,1)', fontColor: '#353636',
fontBgColor: 'rgba(255,255,255,0.8)',
iconWidth,
iconHeight,
borderColor: 'rgba(119,131,145,0.5)',
selectedBorderColor: 'rgba(119,131,145,0.8)',
hoveredShadowColor: 'rgba(151,151,151,0.21)', hoveredShadowColor: 'rgba(151,151,151,0.21)',
selectedShadowColor: 'rgba(126,159,84,0.21)' selectedShadowColor: 'rgba(151,151,151,0.4)'
}
},
getTempNodeStyle (entityType) {
const nodeStyle = {
innerR: 14,
fill: 'transparent',
font: '8px NotoSansSChineseRegular',
textAlign: 'center',
textBaseline: 'middle',
fillStyle: '#353636'
}
switch (entityType) {
case 'ip': {
return {
...nodeStyle,
iconWidth: 24,
iconHeight: 21,
borderColor: '#FFFFFF',
selectedBorderColor: 'rgba(126,159,84,1)',
hoveredShadowColor: 'rgba(151,151,151,0.21)',
selectedShadowColor: 'rgba(126,159,84,0.21)'
}
}
case 'domain': {
return {
...nodeStyle,
iconWidth: 24,
iconHeight: 24,
borderColor: '#FFFFFF',
selectedBorderColor: 'rgba(56,172,210,1)',
hoveredShadowColor: 'rgba(151,151,151,0.21)',
selectedShadowColor: 'rgba(56,172,210,0.21)'
}
}
case 'app': {
return {
...nodeStyle,
iconWidth: 22,
iconHeight: 24,
borderColor: '#FFFFFF',
selectedBorderColor: 'rgba(229,162,25,1)',
hoveredShadowColor: 'rgba(151,151,151,0.21)',
selectedShadowColor: 'rgba(229,162,25,0.21)'
}
}
}
return {
...nodeStyle,
iconWidth: 24,
iconHeight: 21,
borderColor: '#FFFFFF',
selectedBorderColor: 'rgba(126,159,84,1)',
hoveredShadowColor: 'rgba(151,151,151,0.21)',
selectedShadowColor: 'rgba(126,159,84,0.21)'
} }
}, },
getEntityNodeStyle (entityType) { getEntityNodeStyle (entityType) {
const nodeStyle = { let iconWidth = 20
innerR: 12, let iconHeight = 18
fill: 'transparent'
}
switch (entityType) { switch (entityType) {
case 'ip': { case 'ip': {
return { iconWidth = 20
...nodeStyle, iconHeight = 18
iconWidth: 22, break
iconHeight: 20,
selectedShadowColor: 'rgba(126,159,84,0.1)'
}
} }
case 'domain': { case 'domain': {
return { iconWidth = 20
...nodeStyle, iconHeight = 20
iconWidth: 22, break
iconHeight: 22,
selectedShadowColor: 'rgba(56,172,210,0.1)'
}
} }
case 'app': { case 'app': {
return { iconWidth = 18
...nodeStyle, iconHeight = 20
iconWidth: 21, break
iconHeight: 24,
selectedShadowColor: 'rgba(229,162,25,0.1)'
}
} }
} }
return { return {
...nodeStyle, shadowR: 15,
iconWidth: 22, innerR: 10,
iconHeight: 20, fillStyle: '#FFFFFF',
borderColor: 'rgba(119,131,145,1)', iconWidth,
selectedBorderColor: 'rgba(126,159,84,1)', iconHeight,
hoveredShadowColor: 'rgba(151,151,151,0.21)', hoveredShadowColor: 'rgba(151,151,151,0.12)',
selectedShadowColor: 'rgba(126,159,84,0.1)' selectedShadowColor: 'rgba(151,151,151,0.24)'
} }
}, },
/** /**
@@ -822,7 +821,7 @@ export default {
l.source = l.source.id l.source = l.source.id
l.target = l.target.id l.target = l.target.id
}) })
const info = findShortestPath(g6FormatData, this.entity.entityName, id) const info = findShortestPath(g6FormatData, this.entity.entityName + nodeType.rootNode, id)
return info.length return info.length
}, },
generateTempNodeCoordinate (sourceNode, event) { generateTempNodeCoordinate (sourceNode, event) {
@@ -847,7 +846,7 @@ export default {
cleanTempItems () { cleanTempItems () {
const { nodes, links } = this.graph.graphData() const { nodes, links } = this.graph.graphData()
const newNodes = nodes.filter(n => n.type !== nodeType.tempNode) const newNodes = nodes.filter(n => n.type !== nodeType.tempNode)
const newLinks = links.filter(l => l.level !== 3) const newLinks = links.filter(l => l.type !== linkType.temp)
if (newNodes.length !== nodes.length || newLinks.length !== links.length) { if (newNodes.length !== nodes.length || newLinks.length !== links.length) {
this.graph.graphData({ nodes: newNodes, links: newLinks }) this.graph.graphData({ nodes: newNodes, links: newLinks })
} }
@@ -895,7 +894,7 @@ export default {
this.getIconUrl(k, false, false) this.getIconUrl(k, false, false)
) )
listNodes.push(listNode) listNodes.push(listNode)
links.push(new Link(rootNode, listNode, null, 60, 1)) links.push(new Link(rootNode, listNode, null))
} }
}) })
// entityNode // entityNode
@@ -913,10 +912,10 @@ export default {
}, },
listNode, listNode,
this.defaultChargeStrength, this.defaultChargeStrength,
this.getIconUrl(listNode.data.entityType, true, false) this.getIconUrl(listNode.data.entityType, false, false)
) )
entityNodes.push(entityNode) entityNodes.push(entityNode)
links.push(new Link(listNode, entityNode, null, this.defaultLinkDistance, 2)) links.push(new Link(listNode, entityNode, null))
}) })
} }
nodes.push(...listNodes, ...entityNodes) nodes.push(...listNodes, ...entityNodes)
@@ -946,14 +945,14 @@ export default {
const toAddNode = new Node(nodeType.entityNode, entity.vertex, { const toAddNode = new Node(nodeType.entityNode, entity.vertex, {
entityType: expandType, entityType: expandType,
entityName: entity.vertex entityName: entity.vertex
}, node, this.defaultChargeStrength, this.getIconUrl(node.data.entityType, true, false)) }, node, this.defaultChargeStrength, this.getIconUrl(node.data.entityType, false, false))
toAddNodes.push(toAddNode) toAddNodes.push(toAddNode)
const toAddLink = new Link(node, toAddNode, null, this.defaultLinkDistance) const toAddLink = new Link(node, toAddNode, null)
toAddLinks.push(toAddLink) toAddLinks.push(toAddLink)
}) })
this.addItems(toAddNodes, toAddLinks) this.addItems(toAddNodes, toAddLinks)
this.rightBox.node = _.cloneDeep(node) this.rightBox.node = node
} catch (e) { } catch (e) {
console.error(e) console.error(e)
this.$message.error(this.errorMsgHandler(e)) this.$message.error(this.errorMsgHandler(e))
@@ -986,7 +985,7 @@ export default {
let listNode = neighbors.targetNodes.find(n => n.data.entityType === expandType) let listNode = neighbors.targetNodes.find(n => n.data.entityType === expandType)
if (!listNode) { if (!listNode) {
listNode = new Node(nodeType.listNode, `${node.id}__${expandType}-list`, { entityType: expandType }, node, this.defaultChargeStrength, this.getIconUrl(expandType, false, false)) listNode = new Node(nodeType.listNode, `${node.id}__${expandType}-list`, { entityType: expandType }, node, this.defaultChargeStrength, this.getIconUrl(expandType, false, false))
const link = new Link(node, listNode, null, this.defaultLinkDistance) const link = new Link(node, listNode, null)
toAddNodes.push(listNode) toAddNodes.push(listNode)
toAddLinks.push(link) toAddLinks.push(link)
} }
@@ -994,9 +993,9 @@ export default {
const entityNode = new Node(nodeType.entityNode, entity.vertex, { const entityNode = new Node(nodeType.entityNode, entity.vertex, {
entityType: expandType, entityType: expandType,
entityName: entity.vertex entityName: entity.vertex
}, listNode, this.defaultChargeStrength, this.getIconUrl(expandType, true, false)) }, listNode, this.defaultChargeStrength, this.getIconUrl(expandType, false, false))
toAddNodes.push(entityNode) toAddNodes.push(entityNode)
toAddLinks.push(new Link(listNode, entityNode, null, this.defaultLinkDistance)) toAddLinks.push(new Link(listNode, entityNode, null))
}) })
this.addItems(toAddNodes, toAddLinks) this.addItems(toAddNodes, toAddLinks)
this.rightBox.node = _.cloneDeep(node) this.rightBox.node = _.cloneDeep(node)
@@ -1053,10 +1052,12 @@ export default {
node: null, node: null,
loading: true loading: true
}) })
const clickNode = shallowRef(null)
return { return {
entity, entity,
rightBox, rightBox,
graph graph,
clickNode
} }
} }
} }

View File

@@ -176,6 +176,7 @@ export default {
node: { node: {
deep: true, deep: true,
handler (n) { handler (n) {
console.info(n)
this.handleDetailData(n) this.handleDetailData(n)
} }
} }
@@ -269,6 +270,8 @@ export default {
return location || '-' return location || '-'
}, },
handleDetailData (node) { handleDetailData (node) {
console.info(node)
console.info(_.get(node, 'data.relatedEntities.ip.list', []))
const n = node const n = node
const type = _.get(n, 'data.entityType', '') const type = _.get(n, 'data.entityType', '')
switch (type) { switch (type) {
@@ -432,6 +435,7 @@ export default {
] ]
} }
} }
console.info(this.relationList)
} }
} }
} }

View File

@@ -1,17 +1,19 @@
import { nodeType } from './node'
export default class Link { export default class Link {
constructor (sourceNode, targetNode, type = linkType.normal, distance, level = 1) { constructor (sourceNode, targetNode, type) {
this.source = sourceNode.id this.source = sourceNode.id
this.target = targetNode.id this.target = targetNode.id
this.type = type this.type = type || linkType.normal
this.distance = distance || 20// 连接中心节点的线的distance为60其他节点为20 this.strength = strengthHandler(sourceNode, targetNode)
this.strength = level === 1 ? 0.5 : (level === 2 ? 1 : 1)// level2设置为0.5-1 }
this.color = '#c8c8cc'// 未点击线所连接的节点时线的颜色 }
this.clickColor = '#7b7b99'// 点击此线所连接的节点后,线的颜色
// this.width = 1//未点击线所连接的节点时线的宽度 function strengthHandler (sourceNode, targetNode) {
// this.arrowColor = '#c8c8cc' if (sourceNode.type === nodeType.rootNode) {
// this.clickArrowColor = '#7b7b99' return 0.5
// this.clickWidth = 1//点击节点后,此节点所连接的线的宽度 } else {
this.level = level// 1:source为中心节点的线是直线; 2:source为listNode节点的线是直线; 3:target为临时节点的线是虚线 return 1
} }
} }

View File

@@ -16,6 +16,8 @@ export default class Node {
this.type = type this.type = type
this.vx = 0 this.vx = 0
this.vy = 0 this.vy = 0
this.x = null
this.y = null
this.fx = sourceNode ? null : 0// 设置为0即可固定中心节点。0为中心节点1为中心节点的子节点2为第三级节点 this.fx = sourceNode ? null : 0// 设置为0即可固定中心节点。0为中心节点1为中心节点的子节点2为第三级节点
this.fy = sourceNode ? null : 0// 设置为0即可固定中心节点。0为中心节点1为中心节点的子节点2为第三级节点 this.fy = sourceNode ? null : 0// 设置为0即可固定中心节点。0为中心节点1为中心节点的子节点2为第三级节点
this.preDragX = null this.preDragX = null
@@ -30,6 +32,20 @@ export default class Node {
} }
this.label = generateLabel(type, id, data, sourceNode) this.label = generateLabel(type, id, data, sourceNode)
this.name = builtTooltip(this) this.name = builtTooltip(this)
// val 值决定鼠标触发 hover 的半径
switch (type) {
case nodeType.rootNode:
this.val = 19
break
case nodeType.listNode:
this.val = 16
break
case nodeType.entityNode:
case nodeType.tempNode:
this.val = 10
break
}
} }
// listNode和entityNode专用查询实体信息 // listNode和entityNode专用查询实体信息
@@ -99,7 +115,6 @@ export default class Node {
if (this.data.entityType === entityType.domain && targetEntityType === entityType.domain) { if (this.data.entityType === entityType.domain && targetEntityType === entityType.domain) {
_targetEntityType = 'subdomain' _targetEntityType = 'subdomain'
} }
console.info(this.data, targetEntityType)
const url = `${api.entity.entityGraph[`${this.data.entityType}Related${_.upperFirst(_targetEntityType)}`]}?resource=${this.data.entityName}&pageSize=10&pageNo=${this.data.relatedEntities[targetEntityType].pageNo + 1}` const url = `${api.entity.entityGraph[`${this.data.entityType}Related${_.upperFirst(_targetEntityType)}`]}?resource=${this.data.entityName}&pageSize=10&pageNo=${this.data.relatedEntities[targetEntityType].pageNo + 1}`
const response = await axios.get(url).catch(e => { const response = await axios.get(url).catch(e => {
console.error(e) console.error(e)