echarts使用graph、lines实现拓扑,可以拖动增加effect效果

options.js

// import React from 'react'
// import * as echarts from 'echarts'

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: [
            {
                // formatter: function (name) {
                //     return echarts.format.truncateText(name, 200, '12px', '…')
                // },
                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: [ { // focusNodeAdjacency:true, id: 'nodes', type: 'graph', // layout: 'force', layout: 'none', // roam: true, //鼠标缩放及平移 coordinateSystem: 'cartesian2d', // 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, }, // formatter: '{c}', }, // formatter: '{c}', }, emphasis: { scale: true, }, cursor: 'pointer', roam: true, draggable: true, // focusNodeAdjacency: 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([]), ].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,
            // draggable: true,
            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%' },
            ],
        },
        //交换机,C,D ....n
        {
            name: '交换机',
            id: '2',
            linkTargetName: ['5', '6'],
            linkValue: '好好学习 ',
            coordConfig: {
                level: '1',
            },
            symbol: imgae_ + jiaoHuanJi,
            symbolSize: 40,
            // draggable: true,
            value: [160, 350],
        },
        {
            name: '交换机',
            id: '3',
            linkTargetName: ['5', '6', '8'],
            linkValue: '111',
            coordConfig: {
                level: '1',
            },
            symbol: imgae_ + jiaoHuanJi,
            symbolSize: 40,
            // draggable: true,
            value: [250, 350],
        },
        {
            name: '智能城域网',
            id: '4',
            linkTargetName: ['6', '7'],
            linkValue: '好好学习 ',
            coordConfig: {
                level: '1',
            },
            symbol: imgae_ + jiaoHuanJi,
            symbolSize: 40,
            // draggable: true,
            value: [340, 350],
        },

        {
            name: 'liuqing-1', // 节点名
            id: '5', // 节点名
            linkTargetName: [], // 连线目标节点
            linkValue: '好好学习 ', // 连线介绍
            coordConfig: {
                level: '2-error',
                effect: {
                    // show: true,
                    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],
            // draggable: true,
            // fixed: true,
            symbol: imgae_ + olt,
            symbolSize: 40,
            itemStyle: {
                color: '#fb3f3f',
            },
        },
        {
            name: 'liuqing-2',
            id: '6',
            linkTargetName: [],
            linkValue: ' 好好学习',
            coordConfig: {
                level: '2',
            },
            value: [190, 100],
            // draggable: true,
            // fixed: true,
            symbol: imgae_ + olt,
            symbolSize: 40,
        },
        {
            name: 'liuqing-3',
            id: '7',
            linkTargetName: [],
            linkValue: '好好学习 ',
            coordConfig: {
                level: '2',
            },
            value: [250, 100],
            // draggable: true,
            fixed: true,
            symbol: imgae_ + olt,
            symbolSize: 40,
        },
        {
            name: 'liuqing-4',
            id: '8',
            linkTargetName: [],
            linkValue: ' 好好学习',
            coordConfig: {
                level: '2',
            },
            value: [350, 100],
            // draggable: true,
            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()
        })
        // nodeDataList 改变的时候 说明是拖动页面元素的时候
        // 重新 setOption
        if (myChart) {
            let currentLinks = getNodeLinksDataList(nodeDataList) // 或者更高效地只更新受影响的 link
            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
    }
    // type === lines 的线条
    const getLinesCoordsFun = function () {
        let coorDataDict = {}
        let defaultConfig = {
            type: 'lines', //块1,2...n到节点A,B...N
            coordinateSystem: 'cartesian2d',
            z: 1,
            effect: {
                show: true,
                smooth: true,
                trailLength: 0,
                symbol: 'arrow',
                color: '#67c23a',
                width: 20,
                symbolSize: 10,
                period: 3,
                delay: 1500,
                // loop: false,
                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))
                    // 自定义好的样式 lineStyle
                    if (item.coordConfig.lineStyle !== undefined) {
                        coorConfig.lineStyle = item.coordConfig.lineStyle
                    }
                    // 自定义好的样式 effect
                    if (item.coordConfig.effect !== undefined) {
                        coorConfig.effect = item.coordConfig.effect
                    }
                    // 根据 level 存起来各自的 coordConfig
                    coorDataDict[item.coordConfig.level] = coorConfig
                }
                // 设置连线 coords
                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 }) => {
        // 更新 nodeDataList 中对应节点的位置
        const nodeDataListNew = nodeDataList.map(n => {
            if (n.id === draggingNode.data.id) {
                n.value = dataCoord
            }
            return n
        })
        setnodeDataList(nodeDataListNew)
    }
    // myChart 初始后调用的第一个方法
    // 展示接口返回的数据 nodeDataList
    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

你可能感兴趣的:(#,react,前端,#,js,echarts,javascript,前端)