options.js
import './index.less'
export const useEchartsOptionFun = ({ nodeDataList, getNodeLinksDataList, getLinesCoordsFun }) => {
const option = {
title: {
text: '拓扑关系图',
top: 'top',
left: 'center',
},
itemStyle: {
normal: {
color: '#67C23A',
},
shadowBlur: 0,
},
textStyle: {
color: '#444',
fontSize: 16,
fontWeight: 600,
},
legend: [
{
tooltip: {
show: false,
},
selectedMode: 'false',
bottom: 20,
},
],
animationDuration: 500,
animationEasingUpdate: 'quinticInOut',
xAxis: {
show: false,
max: 500,
type: 'value',
},
yAxis: {
show: false,
type: 'value',
max: 500,
},
tooltip: {
formatter(params) {
const { itemInfo = [] } = params.data || {}
if (itemInfo.length === 0) return null
let itemInfoStr = ''
itemInfo.map(item => {
itemInfoStr += `${item.name} class='topoEchartsBox_tooltip_title'>${item.name}:${item.value}
`
})
const str = ` ${itemInfoStr}
`
return str
},
},
series: [
{
id: 'nodes',
type: 'graph',
layout: 'none',
coordinateSystem: 'cartesian2d',
legendHoverLink: false,
hoverAnimation: true,
nodeScaleRatio: false,
edgeSymbol: ['circle', 'none'],
edgeSymbolSize: [2, 15],
edgeLabel: {
show: true,
normal: {
show: true,
position: 'middle',
textStyle: {
fontSize: 12,
},
},
},
emphasis: {
scale: true,
},
cursor: 'pointer',
roam: true,
draggable: true,
label: {
normal: {
position: 'bottom',
show: true,
textStyle: {
fontSize: 12,
},
},
},
itemStyle: {
normal: {
color: '#409eff',
},
shadowBlur: 0,
},
data: nodeDataList,
links: getNodeLinksDataList(nodeDataList),
lineStyle: {
normal: {
curveness: 0,
color: '#67c23a',
width: 2,
},
emphasis: {
color: 'red',
width: 3,
type: 'dashed',
},
},
},
].concat([...getLinesCoordsFun()]),
}
return {
option,
}
}
topoEchartsBox.js
import React, { useCallback, useState, useEffect, useRef } from 'react'
import { Row, Col, Select, Button, Spin, Input, Modal, modal, message, Form, Radio, Tooltip, Descriptions, DatePicker } from 'antd'
import RsFlowSearch from '@/components/RsFlowSearch'
import { TooltipBox } from '@/components/utils/common'
import TableTooltip from '@/components/TableTooltip'
import * as IPServe from '@/serve/IPServe/IPServe'
import _ from 'lodash-es'
import Chart from '@/components/Chart-Topo'
import * as echarts from 'echarts'
import { useEchartsOptionFun } from './echarts/index.js'
import './index.less'
import bnc from '@/assets/ComImg/topoImg/bnc.png'
import jiaoHuanJi from '@/assets/ComImg/topoImg/jiaoHuanJi.png'
import olt from '@/assets/ComImg/topoImg/olt.png'
const imgae_ = 'image://'
let errorEffectSymbol =
'path://M671.830688 511.699001l319.059377-319.059377c43.945914-43.945914 43.945914-115.583774 0-159.529688-43.945914-43.945914-115.583774-43.945914-159.529688 0l-319.059377 319.059377-319.059377-319.059377c-43.945914-43.945914-115.583774-43.945914-159.529688 0-43.945914 43.945914-43.945914 115.583774 0 159.529688l319.059377 319.059377-319.059377 319.059377c-43.945914 43.945914-43.945914 115.583774 0 159.529688 43.945914 43.945914 115.583774 43.945914 159.529688 0l319.059377-319.059377 319.059377 319.059377c43.945914 43.945914 115.583774 43.945914 159.529688 0 43.945914-43.945914 43.945914-115.583774 0-159.529688L671.830688 511.699001z'
export default function (props) {
const [nodeDataList, setnodeDataList] = useState([
{
name: 'liuqing',
id: '1',
linkTargetName: ['2', '3', '4'],
linkValue: '好好学习',
coordConfig: { level: 0 },
symbolSize: 40,
symbol: imgae_ + bnc,
value: [250, 450],
itemInfo: [
{ name: 'liuqing', value: '12台' },
{ name: 'liuqing', value: '260个' },
{ name: 'liuqing', value: '10%' },
{ name: 'liuqing', value: '10%' },
{ name: 'liuqing', value: '10%' },
{ name: 'liuqing', value: '10%' },
{ name: 'liuqing liuqing 连接数', value: '100' },
{ name: 'liuqing liuqing', value: '10%' },
{ name: 'IP liuqing', value: '10%' },
],
},
{
name: '交换机',
id: '2',
linkTargetName: ['5', '6'],
linkValue: '好好学习 ',
coordConfig: {
level: '1',
},
symbol: imgae_ + jiaoHuanJi,
symbolSize: 40,
value: [160, 350],
},
{
name: '交换机',
id: '3',
linkTargetName: ['5', '6', '8'],
linkValue: '111',
coordConfig: {
level: '1',
},
symbol: imgae_ + jiaoHuanJi,
symbolSize: 40,
value: [250, 350],
},
{
name: '智能城域网',
id: '4',
linkTargetName: ['6', '7'],
linkValue: '好好学习 ',
coordConfig: {
level: '1',
},
symbol: imgae_ + jiaoHuanJi,
symbolSize: 40,
value: [340, 350],
},
{
name: 'liuqing-1',
id: '5',
linkTargetName: [],
linkValue: '好好学习 ',
coordConfig: {
level: '2-error',
effect: {
show: false,
smooth: false,
trailLength: 0,
symbol: errorEffectSymbol,
color: '#fb3f3f',
symbolSize: 10,
period: 3,
delay: 1500,
loop: true,
},
lineStyle: {
normal: {
curveness: 0,
color: '#fb3f3f',
width: 2,
},
},
},
value: [90, 100],
symbol: imgae_ + olt,
symbolSize: 40,
itemStyle: {
color: '#fb3f3f',
},
},
{
name: 'liuqing-2',
id: '6',
linkTargetName: [],
linkValue: ' 好好学习',
coordConfig: {
level: '2',
},
value: [190, 100],
symbol: imgae_ + olt,
symbolSize: 40,
},
{
name: 'liuqing-3',
id: '7',
linkTargetName: [],
linkValue: '好好学习 ',
coordConfig: {
level: '2',
},
value: [250, 100],
fixed: true,
symbol: imgae_ + olt,
symbolSize: 40,
},
{
name: 'liuqing-4',
id: '8',
linkTargetName: [],
linkValue: ' 好好学习',
coordConfig: {
level: '2',
},
value: [350, 100],
symbol: imgae_ + olt,
symbolSize: 40,
},
])
const [boxHeight, setboxHeight] = useState('300px')
const [myChart, setmyChart] = useState(null)
const resizeFun = () => {
const box = document.querySelector('.rsflowSearchContent .topoEcharts')
const boxTop = box?.getBoundingClientRect()?.top
setboxHeight(`calc(${window.innerHeight}px - ${boxTop}px - 30px)`)
}
useEffect(() => {
if (parseFloat(boxHeight) < 300) {
setboxHeight('300px')
}
}, [boxHeight])
useEffect(() => {
window.addEventListener('resize', resizeFun)
setTimeout(() => {
resizeFun()
})
if (myChart) {
let currentLinks = getNodeLinksDataList(nodeDataList)
myChart &&
myChart.setOption({
series: [
{
id: 'nodes',
data: nodeDataList,
links: currentLinks,
},
].concat([...getLinesCoordsFun()]),
})
}
return () => {
window.removeEventListener('resize', resizeFun)
}
}, [nodeDataList])
const getNodeLinksDataList = function (nodeDataList) {
let coordData = []
nodeDataList.map(item => {
item.linkTargetName.map(i => {
const { id, name } = nodeDataList.find(i_find => i === i_find.id)
coordData = [
...coordData,
{
symbol: ['none', 'arrow'],
symbolSize: [4, 8],
label: {
show: false,
position: 'middle',
formatter: item.name + '--' + name,
},
source: item.id,
target: id,
roam: true,
focusNodeAdjacency: true,
id: item.name + '---to---' + name,
},
]
})
})
return coordData
}
const getLinesCoordsFun = function () {
let coorDataDict = {}
let defaultConfig = {
type: 'lines',
coordinateSystem: 'cartesian2d',
z: 1,
effect: {
show: true,
smooth: true,
trailLength: 0,
symbol: 'arrow',
color: '#67c23a',
width: 20,
symbolSize: 10,
period: 3,
delay: 1500,
loop: true,
},
lineStyle: {
width: 2,
color: '#67c23a',
},
data: [],
}
nodeDataList.map(item => {
if (item.coordConfig !== undefined) {
if (!(item.coordConfig.level in coorDataDict)) {
let coorConfig = JSON.parse(JSON.stringify(defaultConfig))
if (item.coordConfig.lineStyle !== undefined) {
coorConfig.lineStyle = item.coordConfig.lineStyle
}
if (item.coordConfig.effect !== undefined) {
coorConfig.effect = item.coordConfig.effect
}
coorDataDict[item.coordConfig.level] = coorConfig
}
item.linkTargetName.map(i => {
const { value, name } = nodeDataList.find(i_find => i === i_find.id)
coorDataDict[item.coordConfig.level].data.push({
coords: [item.value, value],
})
})
}
})
return Object.values(coorDataDict)
}
const onChartdrag = ({ draggingNode, dataCoord }) => {
const nodeDataListNew = nodeDataList.map(n => {
if (n.id === draggingNode.data.id) {
n.value = dataCoord
}
return n
})
setnodeDataList(nodeDataListNew)
}
const returnMyChartFun = myChart => {
const { option } = useEchartsOptionFun({ nodeDataList, getNodeLinksDataList, getLinesCoordsFun })
setmyChart(myChart)
myChart.setOption(option)
}
return (
<div className="topoEchartsBox">
<RsFlowSearch title="BNC/BRAS跨域综合分析拓扑关系图" isShowRightIcon={false}>
<Chart className={'topoEcharts'} onChartdrag={onChartdrag} returnMyChartFun={returnMyChartFun} style={{ height: boxHeight, width: '100%' }} />
</RsFlowSearch>
</div>
)
}
Chat-Topo.js
import React, { useEffect, useRef, useState } from 'react'
import useEchartsSize from '@/components/useEchartsSize'
var echarts = require('echarts')
const debounce = () => {
let timer = null
const newDebounce = function (fn, wait, ...args) {
return new Promise((resolve, reject) => {
if (timer !== null) {
clearTimeout(timer)
}
timer = setTimeout(_ => {
try {
resolve(fn(...args))
} catch (e) {
reject(e)
}
}, wait)
})
}
return newDebounce
}
const newDebounce = debounce()
let draggingNode = null
function chart(props) {
const { style, className, onChartClick, onChartdrag, returnMyChartFun } = props
const chartRef = useRef(null)
const [barChart, setBarChart] = useState()
useEchartsSize(barChart)
useEffect(() => {
const chartDom = chartRef.current
const myChart = echarts.init(chartDom)
myChart.clear()
myChart.resize()
returnMyChartFun(myChart)
setBarChart(myChart)
onChartClick &&
myChart.on('click', params => {
onChartClick(params.name)
})
myChart.on('mousedown', params => {
if (params.componentType === 'series' && params.seriesType === 'graph' && params.dataType === 'node') {
draggingNode = params
}
})
myChart.getDom().addEventListener('mouseup', params => {
newDebounce(() => {
if (draggingNode) {
const pixel = [params.layerX, params.layerY]
const dataCoord = myChart.convertFromPixel({ seriesIndex: 0 }, pixel)
onChartdrag({ draggingNode, dataCoord, myChart })
draggingNode = null
}
}, 16)
})
}, [])
return <div className={className} ref={chartRef} style={style || { width: '100%', height: '300px' }} />
}
export default chart