您好,欢迎访问一九零五行业门户网

如何用D3.js实现拓扑图

这篇文章主要介绍了关于如何用d3.js实现拓扑图,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下
最近写项目需要画出应用程序调用链的网路拓扑图,完全自己写需要花费些时间,那么首先想到的是echarts,但echarts的自定义写法写起来非常麻烦,而且它的文档都是基于配置说明的,对于自定义开发不太方便,尝试后果断放弃,改用d3.js,自己完全可控。
我们先看看效果
我把代码分享下,供和我一样刚接触d3的同学参考,不对的地方欢迎指正!
完整代码:
html:
<!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,    // },  ]}
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注!
相关推荐:
js将任意元素移动到指定位置
关于vue antv g2-3.x组件化的介绍
以上就是如何用d3.js实现拓扑图的详细内容。
其它类似信息

推荐信息