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

news2025/6/8 7:27:01

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 += `<div key=${item.name} class='topoEchartsBox_tooltip_title'>${item.name}:<span class='topoEchartsBox_tooltip_title_span'>${item.value}</span></div>`
                })
                const str = `<div class='topoEchartsBox_tooltip'> ${itemInfoStr} </div>`
                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

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2403874.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

产品经理课程(九)

从需求到功能设计 &#xff08;一&#xff09;复习 产品规划&#xff1a;产品定位、阶段性计划 产品定位&#xff1a;产品画布&#xff08;9个步骤&#xff1b;最重要的是先解决什么问题&#xff09; &#xff08;Roadmap&#xff09;目标要素&#xff1a;时间、事项、里程碑…

从零开始开发纯血鸿蒙应用之网络检测

从零开始开发纯血鸿蒙应用 〇、前言一、认识 connection 模块1、获取默认网络2、获取网络能力信息3、解析网络能力信息3.1、NetCap3.2、NetBearType 二、实现网络检测功能1、申请权限2、获取默认网路的 NetCap 数组 三、总结 〇、前言 在之前的博文里&#xff0c;介绍了如何实…

向 AI Search 迈进,腾讯云 ES 自研 v-pack 向量增强插件揭秘

作者&#xff1a;来自腾讯云刘忠奇 2025 年 1 月&#xff0c;腾讯云 ES 团队上线了 Elasticsearch 8.16.1 AI 搜索增强版&#xff0c;此发布版本重点提升了向量搜索、混合搜索的能力&#xff0c;为 RAG 类的 AI Search 场景保驾护航。除了紧跟 ES 官方在向量搜索上的大幅优化动…

【win | docker开启远程配置】使用 SSH 隧道访问 Docker的前操作

在主机A pycharm如何连接远程主机B win docker? 需要win docker配置什么&#xff1f; 快捷配置-主机B win OpenSSH SSH Server https://blog.csdn.net/z164470/article/details/121683333 winR,打开命令行&#xff0c;输入net start sshd,启动SSH。 或者右击我的电脑&#…

股指期货波动一个点多少钱?

很多朋友在交易股指期货时&#xff0c;都会好奇一个问题&#xff1a;股指期货波动一个点&#xff0c;我的账户里到底是赚了还是亏了多少钱&#xff1f;要搞清楚这个问题&#xff0c;其实很简单&#xff0c;只需要了解两个关键信息&#xff1a;股指期货的“交易单位”&#xff0…

iOS、Android、鸿蒙、Web、桌面 多端开发框架Kotlin Multiplatform

Kotlin Multiplatform&#xff08;简称 KMP&#xff09;是 JetBrains 推出的开源跨平台开发框架 Kuikly 是腾讯开源的跨端开发框架&#xff0c;基于 Kotlin Multiplatform 技术构建&#xff0c;为开发者提供了技术栈更统一的跨端开发体验 KMP 不仅局限于移动端&#xff0c;它…

探索C++标准模板库(STL):String接口的底层实现(下篇)

前引&#xff1a;在C的面向对象编程中&#xff0c;对象模型是理解语言行为的核心。无论是类的成员函数如何访问数据&#xff0c;还是资源管理如何自动化&#xff0c;其底层机制均围绕两个关键概念展开&#xff1a;this指针与六大默认成员函数。它们如同对象的“隐形守护者”&am…

Flutter知识点汇总

Flutter架构解析 1. Flutter 是什么?它与其他移动开发框架有什么不同? Flutter 是 Google 开发的开源移动应用开发框架,可用于快速构建高性能、高保真的移动应用(iOS 和 Android),也支持 Web、桌面和嵌入式设备。。它与其他移动开发框架(如 React Native、Xamarin、原…

​线性注意力 vs. 传统注意力:效率与表达的博弈新解

​核心结论​&#xff1a;线性注意力用计算复杂度降维换取全局建模能力&#xff0c;通过核函数和结构优化补足表达缺陷 一、本质差异&#xff1a;两种注意力如何工作&#xff1f; ​特性​传统注意力&#xff08;Softmax Attention&#xff09;线性注意力&#xff08;Linear At…

YOLO在QT中的完整训练、验证与部署方案

以下是YOLO在QT中的完整训练、验证与部署方案&#xff1a; 训练方案 准备数据集&#xff1a; 收集数据&#xff1a;收集与目标检测任务相关的图像数据集&#xff0c;可以是公开数据集如COCO、Pascal VOC&#xff0c;也可以是自定义数据集。标注数据&#xff1a;使用标注工具如…

增量式网络爬虫通用模板

之前做过一个项目&#xff0c;他要求是只爬取新产生的或者已经更新的页面&#xff0c;避免重复爬取未变化的页面&#xff0c;从而节省资源和时间。这里我需要设计一个增量式网络爬虫的通用模板。可以继承该类并重写部分方法以实现特定的解析和数据处理逻辑。这样可以更好的节约…

【JVM】三色标记法原理

在JVM中&#xff0c;三色标记法是GC过程中对象状态的判断依据&#xff0c;回收前给对象设置上不同的三种颜色&#xff0c;三色分为白色、灰色、黑色。根据颜色的不同&#xff0c;决定对象是否要被回收。 白色表示&#xff1a; 初始状态&#xff1a;所有对象未被 GC 访问。含义…

【uniapp开发】picker组件的使用

项目uniapp&#xff0c;结合fastadmin后端开发 picker组件的官方文档说明 https://en.uniapp.dcloud.io/component/picker.html#普通选择器 先看效果&#xff1a; 1、实现设备类型的筛选&#xff1b;2、实现设备状态的筛选&#xff1b; 前端代码&#xff08;节选&#xff0…

【HarmonyOS Next之旅】DevEco Studio使用指南(三十一) -> 同步云端代码至DevEco Studio工程

目录 1 -> 同步云函数/云对象 1.1 -> 同步单个云函数/云对象 1.2 -> 批量同步云函数/云对象 2 -> 同步云数据库 2.1 -> 同步单个对象类型 2.2 -> 批量同步对象类型 3 -> 一键同步云侧代码 1 -> 同步云函数/云对象 说明 对于使用DevEco Studio…

go-zero微服务入门案例

一、go-zero微服务环境安装 1、go-zero脚手架的安装 go install github.com/zeromicro/go-zero/tools/goctllatest2、etcd的安装下载地址根据自己电脑操作系统下载对应的版本&#xff0c;具体的使用自己查阅文章 二、创建一个user-rpc服务 1、定义user.proto文件 syntax &qu…

Python控制台输出彩色字体指南

在Python开发中&#xff0c;有时我们需要在控制台输出彩色文本以提高可读性或创建更友好的用户界面。本文将介绍如何使用colorama库来实现这一功能。 为什么需要彩色输出&#xff1f; 提高可读性&#xff1a;重要信息可以用不同颜色突出显示更好的用户体验&#xff1a;错误信息…

开源之夏·西安电子科技大学站精彩回顾:OpenTiny开源技术下沉校园,点燃高校开发者技术热情

开源之夏2025编程活动正在如火如荼的进行中&#xff0c;当前也迎来了报名的倒计时阶段&#xff0c;开源之夏组织方也通过高校行系列活动进入各大高校&#xff0c;帮助高校开发者科普开源文化、开源活动、开源技术。 6月4日 开源之夏携手多位开源技术大咖、经验型选手走进西安电…

解决数据库重启问题

最近部署软件时&#xff0c;发现mysql会一直在重启&#xff0c;记录下解决办法&#xff1a; 1.删除/home/dataexa/install/docker/datas/mysql路径下的data文件夹 2.重新构建mysql docker-compose up -d --build mysql 3.停掉所有应用&#xff0c;在全部重启&#xff1a; do…

前后端交互过程中—各类文件/图片的上传、下载、显示转换

前后端交互过程中—各类文件/图片的上传、下载、显示转换 图片补充&#xff1a;new Blob()URL.createObjectURL()替代方案&#xff1a;FileReader.readAsDataURL()​​对比&#xff1a; tiff文件TIFF库TIFF转换通过url转换tiff文件为png通过文件选择的方式转换tiff文件为png 下…

数据库同步是什么意思?数据库架构有哪些?

目录 一、数据库同步是什么 &#xff08;一&#xff09;基本概念 &#xff08;二&#xff09;数据库同步的类型 &#xff08;三&#xff09;数据库同步的实现方式 二、数据库架构的类型 &#xff08;一&#xff09;单机架构 &#xff08;二&#xff09;主从复制架构 &a…