Maison  >  Article  >  interface Web  >  Comment implémenter un diagramme de topologie avec D3.js

Comment implémenter un diagramme de topologie avec D3.js

不言
不言original
2018-07-10 17:27:594552parcourir

Cet article présente principalement comment utiliser D3.js pour implémenter des diagrammes de topologie. Il a une certaine valeur de référence. Maintenant, je le partage avec tout le monde. Les amis dans le besoin peuvent s'y référer

Les projets d'écriture récents nécessitent des applications de dessin. .Il faut un certain temps pour écrire vous-même le diagramme de topologie du réseau de la chaîne d'appel du programme. La première chose qui me vient à l'esprit est les echarts, mais la méthode d'écriture personnalisée des echarts est très difficile à écrire et ses documents sont tous basés sur la configuration. instructions.Pour le développement personnalisé Ce n'était pas très pratique, alors j'ai abandonné après l'avoir essayé et je suis passé à D3.js, qui est entièrement sous mon contrôle.

Jetons d'abord un coup d'oeil à l'effet

Comment implémenter un diagramme de topologie avec D3.js

Je partagerai le code pour référence avec les étudiants qui sont nouveaux en D3 comme moi. Si vous trouvez quelque chose qui ne va pas, bienvenue Correction !

Code complet :

html :

nbsp;html>


    <meta>
    <title>Title</title>
    <script>
    </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></script>


 <svg>

 </svg>
 <script></script>
 <script>

 </script>

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



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  '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) {
        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 + ")")
  }

}

Données :

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,
    // },
  ]
}

C'est tout Nous espérons que l'ensemble du contenu de cet article sera utile à l'étude de chacun. Pour plus de contenu connexe, veuillez prêter attention au site Web PHP chinois !

Recommandations associées :

js déplace n'importe quel élément vers une position spécifiée

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn