vue使用svg画拓扑图(关系图) 拖拽 缩放

概述

项目刚开始用的echarts画的拓扑图,echarts有个关系图可以直接画出来,但是上个前端在拖拽功能上留了bug,我眼睛都快看瞎了,都没找出来哪里出问题,还找了各种文章借鉴学习都没搞定

后来跑到gitHub上面找了个大神写的拓扑,根据他的代码改好了,不过他用的svg画的图,所以我这份记录也是svg

注意

  1. 数据从后端获取,模拟数据已经提供,修改getData()中代码就行
  2. 缩放功能用到了d3,先安装npm install d3 --save-devmain.js中引入 import * as d3 from 'd3'; Vue.prototype.$d3 = d3
  3. 图片自己随便弄,注意图片名字和后缀名就行

代码

HTML

<template>
    <div class="content">
        <!--拓扑存放位置-->
        <svg
                class="topo"
                id="svg"
                ondragover="return false"
                oncontextmenu="return true"
                @mousewheel="zooming"
        >
            <!-- 已连接的线 -->
            <line
                    v-for="(item) in lines"
                    :key="item.x"
                    :x1="item.x1" :y1="item.y1"
                    :x2="item.x2" :y2="item.y2"
                    style="stroke:rgb(214,214,218);stroke-width:2"/>

            <g
                    v-for="(item, index) in topoNodes"
                    :key="item.id"
                    @mousedown.left.stop.prevent="moveAndLink(index, $event)"
            >
                <image :xlink:href="item.symbol" width="50px" height="50px" :x="item.x" :y="item.y"></image>
                <text :x="item.x + 25" :y="item.y + 66" style="text-anchor: middle; user-select: none;">
                    {{item.name}}
                </text>
            </g>
        </svg>
    </div>
</template>

JavaScript

<script>
    export default {
        name: 'SvgDemo',
        props: {},
        data() {
            return {
                res: {
                    code: 200,
                    data: [{
                        name: "default",
                        devices: [
                            {
                                id: "3",
                                name: "Router",
                                ip: "169.254.200.2",
                                type: "router",
                                x: 400,
                                y: 50
                            },
                            {
                                id: "1",
                                name: "Linux",
                                ip: "192.168.67.101",
                                type: "server",
                                x: 52,
                                y: 500
                            },
                            {
                                id: "5",
                                name: "Winserver",
                                ip: "192.168.67.200",
                                type: "server",
                                x: 500,
                                y: 500
                            },
                            {
                                id: "4",
                                name: "SW",
                                ip: "192.168.67.201",
                                type: "switch",
                                x: 200,
                                y: 200
                            }
                        ],
                        relation: [
                            {source: "3", target: "4", network: "Net-CSRiface_1"},//连线——————source:起点,target(目标):终点
                            {source: "1", target: "4", network: "Net-SWiface_16"},
                            {source: "5", target: "4", network: "Net-R4iface_0"}
                        ]
                    }]
                },
                topoNodes: [], // topo图中的节点
                topoLinks: [], // topo图中的连线
                isMove: true,// 操作模式,默认为移动。可切换为连接模式
                positions: [],//更改的位置
                token: null
            }
        },

        computed: {
            // 动态计算节点间的连线
            lines() {
                let hash = {}
                const OFFSET = 20
                this.topoNodes.forEach((item, index) => {
                    hash[item.id] = index
                })
                /*
                    hash:{
                        1: 1
                        3: 0
                        4: 3
                        5: 2
                     },

                     source:3 1 5,
                     target:4 4 4
                 */
                return this.topoLinks.map(item => {
                    const startNode = this.topoNodes[hash[Number(item.source)]]
                    const endNode = this.topoNodes[hash[Number(item.target)]]
                    return {
                        x1: startNode.x + OFFSET,
                        y1: startNode.y + OFFSET,
                        x2: endNode.x + OFFSET,
                        y2: endNode.y + OFFSET,
                    }
                })
            }
        },
        created() {
            this.token = sessionStorage.getItem("token");
            this.getData();
        },
        methods: {
            getData() {
                //使用模拟数据
                /*this.topoNodes = this.res.data[0].devices;
                this.topoLinks=this.res.data[0].relation;
                for (let item of this.topoNodes) {
                    item.symbol = require(`@/assets/images/${item.type}.svg`);
                }
                for (let item of this.topoLinks) {
                    item.source = Number(item.source);
                    item.target=Number(item.target);
                }
                console.log(this.topoNodes)*/

                //使用接口返回数据
                this.$axios({
                    url: window.config.Login_URL + "/mirror/spaces/topology", //topology
                    method: "GET",
                    headers: {
                        MyToken: this.token,
                    },
                    data: {
                        status: this.value,
                    },
                })
                    .then((res) => {
                        if (res.data.code === 200) {
                            this.topoNodes = res.data.data[0].devices;
                            this.topoLinks = res.data.data[0].relation;
                            for (let item of this.topoNodes) {
                                item.symbol = require(`@/assets/images/${item.type}.svg`);
                            }
                            for (let item of this.topoLinks) {
                                item.source = Number(item.source);
                                item.target = Number(item.target);
                            }
                            //console.log('初始数据',this.topoNodes)
                        }
                    })
                    .catch(() => {
                        this.$message.error("获取失败");
                    });
            },

            //移动事件
            moveAndLink(index, e) {
                // 判断当前模式
                if (this.isMove) {
                    // 移动模式
                    const layerX = e.layerX - this.topoNodes[index].x;
                    const layerY = e.layerY - this.topoNodes[index].y;

                    //实时获取更新后的坐标
                    document.onmousemove = (e) => {
                        this.topoNodes[index].x = e.layerX - layerX;
                        this.topoNodes[index].y = e.layerY - layerY;
                    }
                    //将新坐标存进数据库
                    //如果使用模拟数据,删除该方法
                    document.onmouseup = () => {
                        this.positions = [];
                        for (let j = 0; j < this.topoNodes.length; j++) {
                            this.positions.push({
                                'ip': this.topoNodes[j].ip,
                                'x': this.topoNodes[j].x,
                                'y': this.topoNodes[j].y
                            })
                        }
                        this.$axios({
                            url: window.config.Login_URL + "/mirror/spaces/topology",
                            method: "POST",
                            headers: {
                                MyToken: this.token,
                            },
                            data: {
                                positions: this.positions,
                            },
                        })
                            .then(() => {
                            })
                            .catch(() => {
                            });
                        document.onmousemove = null
                        document.onmouseup = null
                    }

                } else {
                    document.onmousemove = null // 重置鼠标移动事件
                    this.isMove = true // 重置为移动模式
                }
            },

            // 放大缩小
            zooming(){
                var svg = this.$d3.select('#svg');
                var zoom = this.$d3.zoom().scaleExtent([0.4, 5]).on("zoom", function (e) {
                    svg.selectAll('g').attr('transform',e.transform);
                    svg.selectAll('line').attr('transform',e.transform)
                });
                svg.call(zoom)
            }
        }
    }
</script>

CSS

<style scoped>
    .content {
        width: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .topo {
        width: 1070px;
        height: 600px;
    }
</style>
基于Vue拓扑图绘制是指使用Vue.js框架来创建和展示拓扑图拓扑图通常用于表示网络、设备或其他复杂系统的结构关系。通过Vue.js,可以实现动态、可交互的拓扑图展示。 以下是一些实现基于Vue拓扑图绘制的基本步骤: 1. **选择合适的库**:有许多开源库可以帮助你在Vue中绘制拓扑图,例如D3.js、GoJS和vis-network等。这些库提供了丰富的图形绘制和交互功能。 2. **安装和配置**:通过npm或yarn安装所选的库。例如,使用D3.js: ```bash npm install d3 ``` 3. **创建Vue组件**:在Vue组件中引入并使用选定的库。例如,使用D3.js创建一个简单的拓扑图: ```vue <template> <div ref="chart"></div> </template> <script> import * as d3 from &#39;d3&#39;; export default { name: &#39;TopologyChart&#39;, mounted() { this.createChart(); }, methods: { createChart() { const data = { nodes: [ { id: &#39;1&#39;, name: &#39;Node 1&#39; }, { id: &#39;2&#39;, name: &#39;Node 2&#39; }, { id: &#39;3&#39;, name: &#39;Node 3&#39; } ], links: [ { source: &#39;1&#39;, target: &#39;2&#39; }, { source: &#39;2&#39;, target: &#39;3&#39; }, { source: &#39;3&#39;, target: &#39;1&#39; } ] }; const svg = d3.select(this.$refs.chart) .append(&#39;svg&#39;) .attr(&#39;width&#39;, 500) .attr(&#39;height&#39;, 500); const link = svg.selectAll(&#39;line&#39;) .data(data.links) .enter() .append(&#39;line&#39;) .attr(&#39;stroke&#39;, &#39;black&#39;); const node = svg.selectAll(&#39;circle&#39;) .data(data.nodes) .enter() .append(&#39;circle&#39;) .attr(&#39;r&#39;, 20) .attr(&#39;fill&#39;, &#39;lightblue&#39;); node.append(&#39;title&#39;) .text(d => d.name); const simulation = d3.forceSimulation(data.nodes) .force(&#39;link&#39;, d3.forceLink(data.links).id(d => d.id).distance(100)) .force(&#39;charge&#39;, d3.forceManyBody()) .force(&#39;center&#39;, d3.forceCenter(250, 250)); simulation.on(&#39;tick&#39;, () => { link .attr(&#39;x1&#39;, d => d.source.x) .attr(&#39;y1&#39;, d => d.source.y) .attr(&#39;x2&#39;, d => d.target.x) .attr(&#39;y2&#39;, d => d.target.y); node .attr(&#39;cx&#39;, d => d.x) .attr(&#39;cy&#39;, d => d.y); }); } } }; </script> <style scoped> svg { border: 1px solid #ccc; } </style> ``` 4. **交互功能**:根据需要添加交互功能,例如节点拖拽缩放、点击事件等。可以通过D3.js或其他库提供的API来实现这些功能。 5. **优化和扩展**:根据实际需求,优化拓扑图的性能和功能,例如数据动态更新、动效果等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值