0

0

如何用D3.js实现拓扑图

不言

不言

发布时间:2018-07-10 17:27:59

|

5305人浏览过

|

来源于php中文网

原创

这篇文章主要介绍了关于如何用d3.js实现拓扑图,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

最近写项目需要画出应用程序调用链的网路拓扑图,完全自己写需要花费些时间,那么首先想到的是echarts,但echarts的自定义写法写起来非常麻烦,而且它的文档都是基于配置说明的,对于自定义开发不太方便,尝试后果断放弃,改用D3.js,自己完全可控。

我们先看看效果

3420275523-5b37148dc6036_articlex[1].png

我把代码分享下,供和我一样刚接触D3的同学参考,不对的地方欢迎指正!

完整代码:

html

A1.art
A1.art

一个创新的AI艺术应用平台,旨在简化和普及艺术创作

下载
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="http://d3js.org/d3.v5.min.js">
    </script>
    <style>
        body{
            overflow: hidden;
        }
        #togo{
            width: 800px;
            height:500px;
            border:1px solid #ccc;
            user-select: none;
        }
        #togo text{
            font-size:10px;/*和js里保持一致*/
            fill:#1A2C3F;
            text-anchor: middle;
        }
        #togo .node-other{

            text-anchor: start;
        }
        #togo .health1{
            stroke:#92E1A2;
        }
        #togo .health2{
            stroke:orange;
        }
        #togo .health3{
            stroke:red;
        }
        #togo #cloud,#togo #database{
            fill:#ccc;
        }
        #togo .link{
            stroke:#E4E8ED;
        }
        #togo .node-title{
            font-size: 14px;
        }
        #togo .node-code circle{
           fill:#3F86F5;
        }
        #togo .node-code text{
            fill:#fff;
        }
        #togo .node-bg{
            fill:#fff;
        }
        #togo .arrow{
            fill:#E4E8ED;
        }
    </style>
    <script src="data.js"></script>
</head>
<body>
 <svg id="togo" width="800" height="500">

 </svg>
 <script src="togo.js"></script>
 <script>

 </script>

 <script>
    let t=new Togo('#togo',__options);
    t.render();
 </script>


</body>
</html>

JS:

const fontSize = 10;
const symbolSize = 40;
const padding = 10;

/*
* 调用 new Togo(svg,option).render();
* */
class Togo {
  /**/
  constructor(svg, option) {
    this.data = option.data;
    this.edges = option.edges;
    this.svg = d3.select(svg);

  }

  //主渲染方法
  render() {
    this.scale = 1;
    this.width = this.svg.attr('width');
    this.height = this.svg.attr('height');
    this.container = this.svg.append('g')
    .attr('transform', 'scale(' + this.scale + ')');


    this.initPosition();
    this.initDefineSymbol();
    this.initLink();
    this.initNode();
    this.initZoom();

  }

  //初始化节点位置
  initPosition() {
    let origin = [this.width / 2, this.height / 2];
    let points = this.getVertices(origin, Math.min(this.width, this.height) * 0.3, this.data.length);
    this.data.forEach((item, i) => {
      item.x = points[i].x;
      item.y = points[i].y;
    })
  }

  //根据多边形获取定位点
  getVertices(origin, r, n) {
    if (typeof n !== 'number') return;
    var ox = origin[0];
    var oy = origin[1];
    var angle = 360 / n;
    var i = 0;
    var points = [];
    var tempAngle = 0;
    while (i < n) {
      tempAngle = (i * angle * Math.PI) / 180;
      points.push({
        x: ox + r * Math.sin(tempAngle),
        y: oy + r * Math.cos(tempAngle),
      });
      i++;
    }
    return points;
  }

  //两点的中心点
  getCenter(x1, y1, x2, y2) {
    return [(x1 + x2) / 2, (y1 + y2) / 2]
  }

  //两点的距离
  getDistance(x1, y1, x2, y2) {
    return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
  }

  //两点角度
  getAngle(x1, y1, x2, y2) {
    var x = Math.abs(x1 - x2);
    var y = Math.abs(y1 - y2);
    var z = Math.sqrt(x * x + y * y);
    return Math.round((Math.asin(y / z) / Math.PI * 180));
  }


  //初始化缩放器
  initZoom() {
    let self = this;
    let zoom = d3.zoom()
    .scaleExtent([0.7, 3])
    .on('zoom', function () {
      self.onZoom(this)
    });
    this.svg.call(zoom)
  }

  //初始化图标
  initDefineSymbol() {
    let defs=this.container.append('svg:defs');

    //箭头
    const marker = defs
    .selectAll('marker')
    .data(this.edges)
    .enter()
    .append('svg:marker')
    .attr('id', (link, i) => 'marker-' + i)
    .attr('markerUnits', 'userSpaceOnUse')
    .attr('viewBox', '0 -5 10 10')
    .attr('refX', symbolSize / 2 + padding)
    .attr('refY', 0)
    .attr('markerWidth', 14)
    .attr('markerHeight', 14)
    .attr('orient', 'auto')
    .attr('stroke-width', 2)
    .append('svg:path')
    .attr('d', 'M2,0 L0,-3 L9,0 L0,3 M2,0 L0,-3')
    .attr('class','arrow')


    //数据库
    let database =defs.append('g')
      .attr('id','database')
    .attr('transform','scale(0.042)');

    database.append('path')
    .attr('d','M512 800c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V640c0 88.37-200.58 160-448 160z')

    database.append('path')
    .attr('d','M512 608c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V448c0 88.37-200.58 160-448 160z') ;

    database.append('path')
    .attr('d','M512 416c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V256c0 88.37-200.58 160-448 160z') ;

    database.append('path')
    .attr('d','M64 224a448 160 0 1 0 896 0 448 160 0 1 0-896 0Z');

    //云
    let cloud=defs.append('g')
    .attr('id','cloud')
    .attr('transform','scale(0.042)')
    .append('path')
    .attr('d','M709.3 285.8C668.3 202.7 583 145.4 484 145.4c-132.6 0-241 102.8-250.4 233-97.5 27.8-168.5 113-168.5 213.8 0 118.9 98.8 216.6 223.4 223.4h418.9c138.7 0 251.3-118.8 251.3-265.3 0-141.2-110.3-256.2-249.4-264.5z')



  }

  //初始化链接线
  initLink() {
    this.drawLinkLine();
    this.drawLinkText();
  }

  //初始化节点
  initNode() {
    var self = this;
    //节点容器
    this.nodes = this.container.selectAll(".node")
    .data(this.data)
    .enter()
    .append("g")
    .attr("transform", function (d) {
      return "translate(" + d.x + "," + d.y + ")";
    })
    .call(d3.drag()
      .on("drag", function (d) {
        self.onDrag(this, d)
      })
    )
    .on('click', function () {
      alert()
    })

    //节点背景默认覆盖层
    this.nodes.append('circle')
    .attr('r', symbolSize / 2 + padding)
    .attr('class', 'node-bg');

    //节点图标
    this.drawNodeSymbol();
    //节点标题
    this.drawNodeTitle();
    //节点其他说明
    this.drawNodeOther();
    this.drawNodeCode();

  }

  //画节点语言标识
  drawNodeCode() {
    this.nodeCodes = this.nodes.filter(item => item.type == 'app')
    .append('g')
    .attr('class','node-code')
    .attr('transform', 'translate(' + -symbolSize / 2 + ',' + symbolSize / 3 + ')')

    this.nodeCodes
    .append('circle')
    .attr('r', d => fontSize / 2 * d.code.length / 2 + 3)

    this.nodeCodes
    .append('text')
    .attr('dy', fontSize / 2)
    .text(item => item.code);

  }

  //画节点图标
  drawNodeSymbol() {
    //绘制节点
    this.nodes.filter(item=>item.type=='app')
    .append("circle")
    .attr("r", symbolSize / 2)
    .attr("fill", '#fff')
    .attr('class', function (d) {
      return 'health'+d.health;
    })
    .attr('stroke-width', '5px')


    this.nodes.filter(item=>item.type=='database')
    .append('use')
    .attr('xlink:href','#database')
    .attr('x',function () {
      return -this.getBBox().width/2
    })
    .attr('y',function () {
      return -this.getBBox().height/2
    })

    this.nodes.filter(item=>item.type=='cloud')
    .append('use')
    .attr('xlink:href','#cloud')
    .attr('x',function () {
      return -this.getBBox().width/2
    })
    .attr('y',function () {
      return -this.getBBox().height/2
    })
  }

  //画节点右侧信息
  drawNodeOther() {
    //如果是应用的时候
    this.nodeOthers = this.nodes.filter(item => item.type == 'app')
    .append("text")
    .attr("x", symbolSize / 2 + padding)
    .attr("y", -5)
    .attr('class','node-other')

    this.nodeOthers.append('tspan')
    .text(d => d.time + 'ms');

    this.nodeOthers.append('tspan')
    .text(d => d.rpm + 'rpm')
    .attr('x', symbolSize / 2 + padding)
    .attr('dy', '1em');

    this.nodeOthers.append('tspan')
    .text(d => d.epm + 'epm')
    .attr('x', symbolSize / 2 + padding)
    .attr('dy', '1em')
  }

  //画节点标题
  drawNodeTitle() {
    //节点标题
    this.nodes.append("text")
    .attr('class','node-title')
    .text(function (d) {
      return d.name;
    })
    .attr("dy", symbolSize)

    this.nodes.filter(item => item.type == 'app').append("text")
    .text(function (d) {
      return d.active + '/' + d.total;
    })
    .attr('dy', fontSize / 2)
    .attr('class','node-call')

  }

  //画节点链接线
  drawLinkLine() {
    let data = this.data;
    if (this.lineGroup) {
      this.lineGroup.selectAll('.link')
      .attr(
        'd', link => genLinkPath(link),
      )
    } else {
      this.lineGroup = this.container.append('g')


      this.lineGroup.selectAll('.link')
      .data(this.edges)
      .enter()
      .append('path')
      .attr('class', 'link')
      .attr(
        'marker-end', (link, i) => 'url(#' + 'marker-' + i + ')'
      ).attr(
        'd', link => genLinkPath(link),
      ).attr(
        'id', (link, i) => 'link-' + i
      )
      .on('click', () => { alert() })
    }

    function genLinkPath(d) {
      let sx = data[d.source].x;
      let tx = data[d.target].x;
      let sy = data[d.source].y;
      let ty = data[d.target].y;
      return 'M' + sx + ',' + sy + ' L' + tx + ',' + ty;
    }
  }


  drawLinkText() {
    let data = this.data;
    let self = this;
    if (this.lineTextGroup) {
      this.lineTexts
      .attr('transform', getTransform)

    } else {
      this.lineTextGroup = this.container.append('g')

      this.lineTexts = this.lineTextGroup
      .selectAll('.linetext')
      .data(this.edges)
      .enter()
      .append('text')
      .attr('dy', -2)
      .attr('transform', getTransform)
      .on('click', () => { alert() })

      this.lineTexts
      .append('tspan')
      .text((d, i) => this.data[d.source].lineTime + 'ms,' + this.data[d.source].lineRpm + 'rpm');

      this.lineTexts
      .append('tspan')
      .text((d, i) => this.data[d.source].lineProtocol)
      .attr('dy', '1em')
      .attr('dx', function () {
        return -this.getBBox().width / 2
      })
    }

    function getTransform(link) {
      let s = data[link.source];
      let t = data[link.target];
      let p = self.getCenter(s.x, s.y, t.x, t.y);
      let angle = self.getAngle(s.x, s.y, t.x, t.y);
      if (s.x > t.x && s.y < t.y || s.x < t.x && s.y > t.y) {
        angle = -angle
      }
      return 'translate(' + p[0] + ',' + p[1] + ') rotate(' + angle + ')'
    }
  }


  update(d) {
    this.drawLinkLine();
    this.drawLinkText();
  }

  //拖拽方法
  onDrag(ele, d) {
    d.x = d3.event.x;
    d.y = d3.event.y;
    d3.select(ele)
    .attr('transform', "translate(" + d3.event.x + "," + d3.event.y + ")")
    this.update(d);
  }

  //缩放方法
  onZoom(ele) {
    var transform = d3.zoomTransform(ele);
    this.scale = transform.k;
    this.container.attr('transform', "translate(" + transform.x + "," + transform.y + ")scale(" + transform.k + ")")
  }

}

数据:

let __options={
  data:[{
    type:'app',
    name: 'monitor-web-server',
    time: 30,
    rpm: 40,
    epm: 50,
    active: 3,
    total: 5,
    code: 'java',
    health: 1,
    lineProtocol: 'http',
    lineTime: 12,
    lineRpm: 34,
  }, {
    type:'database',
    name: 'Mysql',
    time: 30,
    rpm: 40,
    epm: 50,
    active: 3,
    total: 5,
    code: 'java',
    health: 2,
    lineProtocol: 'http',
    lineTime: 12,
    lineRpm: 34,

  },
    {
      type:'app',
      name: 'Redis',
      time: 30,
      rpm: 40,
      epm: 50,
      active: 3,
      total: 5,
      code: 'java',
      health: 3,
      lineProtocol: 'http',
      lineTime: 12,
      lineRpm: 34,

    }, {
      type:'cloud',
      name: 'ES',
      time: 30,
      rpm: 40,
      epm: 50,
      active: 3,
      total: 5,
      code: 'java',
      health: 1,
      lineProtocol: 'http',
      lineTime: 12,
      lineRpm: 34,
      value: 100
    }
  ],
  edges: [
     {
      source: 0,
      target: 3,
    }, {
      source: 1,
      target: 2,
    }
    , {
      source: 1,
      target: 3,
    },
    {
      source: 0,
      target: 1,
    },
    {
      source: 0,
      target: 2,
    }
    // {
    //   source: 3,
    //   target: 2,
    // },
  ]
}

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

js将任意元素移动到指定位置

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

16

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

23

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

75

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

95

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

218

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

420

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

168

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

222

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

33

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

AngularJS教程
AngularJS教程

共24课时 | 4.1万人学习

CSS教程
CSS教程

共754课时 | 42万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号