Vue+D3.js實現(xiàn)監(jiān)控拓撲圖

效果圖


image.png
image.png

安裝D3.js
npm install d3 --save-dev

編寫topo.js

import * as d3 from 'd3'
import { uniqueId } from '@/lib/util'
const fontSize = 10;
const symbolSize = 40;
const padding = 10;
const colors = {
  primary: '#409EFF',
  warning: '#E6A23C',
  danger: '#F56C6C'
};
const lineColor = '#ccc';
const boxBg = '#f5f5f5';
const boxBorder = '#ccc';
/*
* 調(diào)用 new Topo(svg,option).render();
* */
export class Topo {
  currentNode = null
  /**/
  constructor(svg, option) {
    this.data = option.data;
    this.edges = option.edges;
    this.boxs = option.boxs;
    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.initBox();
    this.initLink();
    this.initNode();
    this.initZoom();
  }

  // 初始化節(jié)點位置
  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) => {
      if (!item.x || !item.y) {
        item.x = points[i].x;
        item.y = points[i].y;
      }
    })
  }

  // 根據(jù)多邊形獲取定位點
  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');

    // 箭頭
    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');

    // 數(shù)據(jù)庫
    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');

    const nginx = defs.append('g')
      .attr('id', 'nginx')
      .attr('transform', 'scale(0.042)')
    nginx.append('path')
      .attr('d', 'M512 0L68.48 256v512L512 1024l443.52-256V256z m256 707.84c0 30.08-27.552 55.04-65.248 55.04-26.912 0-57.632-10.88-76.832-34.56l-256-304.672v284.16c0 30.752-24.32 55.04-54.368 55.04H312.32c-30.752 0-55.04-25.6-55.04-55.04V316.16c0-30.08 26.88-55.04 64-55.04 27.552 0 58.88 10.88 78.08 34.56l254.72 304.672V316.16c0-30.752 25.6-55.04 55.04-55.04h3.2c30.72 0 55.04 25.6 55.04 55.04v391.68z')

    const gslb = defs.append('g')
      .attr('id', 'gslb')
      .attr('transform', 'scale(0.042)')
    gslb.append('path')
      .attr('d', 'M776.043 548.63c-24.768 0.106-48.128 6.378-69.142 16.469l-89.45-133.867c33.365-29.739 54.656-72.683 54.4-120.704-0.427-89.237-73.195-161.621-162.496-161.195-89.28 0.427-161.43 73.494-161.024 162.624 0.213 48.064 21.973 90.688 55.381 120.256l-88.043 134.678c-21.205-9.963-44.437-15.936-69.333-15.83-89.152 0.363-161.43 73.366-161.003 162.624 0.299 89.152 73.195 161.451 162.496 160.96 81.451-0.32 148.054-61.269 158.87-139.84l210.73-1.002c11.435 78.506 78.592 138.858 160.107 138.517 89.237-0.427 161.43-73.301 161.13-162.56-0.469-89.237-73.471-161.536-162.623-161.13z m-158.87 139.84l-210.73 0.96a161.13 161.13 0 0 0-52.971-97.6L441.6 457.11c21.141 9.962 44.437 16.042 69.27 15.829a159.957 159.957 0 0 0 69.162-16.384L669.525 590.4a160.32 160.32 0 0 0-52.352 98.07z')

    const ldap = defs.append('g')
      .attr('id', 'ldap')
      .attr('transform', 'scale(0.042)')
    ldap.append('path')
      .attr('d', 'M128.4096 615.512064h-1.47456c-13.272064 0-24.085504 10.816512-24.085504 24.088576v214.135808c0 13.272064 10.81344 24.082432 24.085504 24.082432h131.23584c13.272064 0 24.08448-10.810368 24.08448-24.082432 0-13.267968-10.812416-24.085504-24.08448-24.085504H152.493056V639.601664c0-13.272064-10.73152-24.0896-24.083456-24.0896z m377.895936 131.155968c0-41.86112-10.40384-73.811968-30.72-95.847424-21.464064-23.507968-52.511744-35.308544-93.965312-35.308544h-69.14048c-13.266944 0-24.08448 10.816512-24.08448 24.088576v214.135808c0 13.272064 10.816512 24.083456 24.08448 24.083456h69.14048c41.45152 0 72.585216-11.795456 93.965312-35.303424 20.400128-22.368256 30.72-54.313984 30.72-95.848448z m-66.435072 63.081472c-14.256128 14.00832-40.633344 20.232192-62.830592 20.232192h-38.749184v-167.936h38.749184c28.259328 0 49.803264 6.549504 62.830592 19.905536 12.696576 13.026304 19.089408 29.080576 19.089408 58.487808-0.001024 36.378624-7.706624 58.165248-19.089408 69.310464zM574.54592 631.2448l-79.628288 214.135808c-5.896192 15.725568 5.818368 32.52224 22.611968 32.52224h1.964032c10.24 0 19.336192-6.470656 22.69184-16.054272l17.531904-49.728512h89.62048l17.531904 49.728512c3.439616 9.663488 12.531712 16.054272 22.69184 16.054272h1.143808c16.795648 0 28.428288-16.794624 22.611968-32.52224L633.690112 631.2448c-3.520512-9.42592-12.531712-15.732736-22.607872-15.732736h-14.009344c-9.995264 0-19.006464 6.227968-22.526976 15.732736z m0.079872 138.851328l28.919808-82.410496h1.063936l28.672 82.410496h-58.655744z m254.528512-154.584064h-81.677312c-13.272064 0-24.08448 10.816512-24.08448 24.088576v214.135808c0 13.272064 10.812416 24.082432 24.08448 24.082432h1.805312c13.267968 0 24.08448-10.810368 24.08448-24.082432v-76.598272H828.416c62.180352 0 93.30688-27.195392 93.30688-81.1776-0.001024-53.661696-31.128576-80.448512-92.568576-80.448512z m37.51936 108.0576c-8.603648 6.631424-22.120448 10.319872-40.716288 10.319872H773.36576v-74.220544h52.59264c18.188288 0 31.783936 3.2768 40.38656 10.322944 8.599552 6.637568 13.188096 14.665728 13.188096 26.049536 0.001024 13.678592-4.258816 20.564992-12.859392 27.528192z')
    ldap.append('path')
      .attr('d', 'M312.776704 524.178432h-92.16V192.216064c0-47.993856 39.046144-87.04 87.04-87.04h414.797824c47.993856 0 87.04 39.046144 87.04 87.04v331.961344h-92.16V197.336064H312.776704v326.842368z')
    ldap.append('path')
      .attr('d', 'M622.244864 335.669248H400.73216c-16.965632 0-30.72-13.754368-30.72-30.72s13.754368-30.72 30.72-30.72h221.512704c16.965632 0 30.72 13.754368 30.72 30.72s-13.754368 30.72-30.72 30.72z')
    ldap.append('path')
      .attr('d', 'M622.244864 435.24608H400.73216c-16.965632 0-30.72-13.754368-30.72-30.72s13.754368-30.72 30.72-30.72h221.512704c16.965632 0 30.72 13.754368 30.72 30.72s-13.754368 30.72-30.72 30.72z')

    const OpenResty = defs.append('g')
      .attr('id', 'OpenResty')
      .attr('transform', 'scale(0.042)')
    OpenResty.append('path')
      .attr('d', 'M407.3 960.3l-321-110v-642l321 82z')
    OpenResty.append('path')
      .attr('d', 'M407.3 960.3l450-240v-600l-450 170z')
    OpenResty.append('path')
      .attr('d', 'M86.3 208.3l321 82 450-170-344.4-56.6z')
    OpenResty.append('path')
      .attr('d', 'M360.1 348.9v80l-239-60v-80l239 60z m-239 48.3l239 60v-10l-239-60v10z m0 23l239 60v-10l-239-60v10z m0 23l239 60v-10l-239-60v10z')
    OpenResty.append('path')
      .attr('d', 'M135.5 308.4v34l210.2 53v-34zM135.5 363.4l210.2 53v-13.3l-210.2-53z')
    OpenResty.append('path')
      .attr('d', 'M407.3 894.1v-5.2l-174.4-55.3-1.3 4.8zM231.6 809.3L407.3 864v-5.3l-174.4-54.3zM231.6 780.1l175.7 53.7v-5.2l-174.4-53.4zM407.3 707.9l-174.4-49.3-1.3 4.8 175.7 49.7zM231.6 692.6l175.7 50.7v-5.2l-174.4-50.4zM231.6 750.9l175.7 52.7v-5.2l-174.4-52.3zM231.6 721.8l175.7 51.7v-5.3l-174.4-51.3z')
    OpenResty.append('path')
      .attr('d', 'M339.3 540.8c-9.4-3.4-17 1.6-17 11.2s7.6 20.1 17 23.2 17-1.9 17-11.2-7.6-19.8-17-23.2zM339.3 603.6c-3.9-1.2-7 1-7 4.9s3.1 8.1 7 9.3 7-1 7-4.9c0-3.9-3.2-8-7-9.3z')
    OpenResty.append('path')
      .attr('d', 'M737.7 753.9m-200 0a200 200 0 1 0 400 0 200 200 0 1 0-400 0Z')
    OpenResty.append('path')
      .attr('d', 'M799.5 563.7c53.5 36.8 87.3 90.6 87.3 150.6 0 110.9-115.4 200.8-257.8 200.8-3.4 0-6.7-0.1-10-0.2 33.2 24.6 74.3 39.1 118.8 39.1 110.5 0 200-89.5 200-200-0.1-89-58.1-164.3-138.3-190.3z')
    OpenResty.append('path')
      .attr('d', 'M804.3 760s-7.5-4.5-12.5-6-2.5-9-2.5-9 3 4.5 11.5 6 22.5 3.5 26.5 8.5 4.5 8 6.5 10 3.5 7 3.5 7-8.3-1.3-10-5c-1.7-3.7-3.5-9.5-5-8s-0.5 7-6 7-7-5-11-6-1-4.5-1-4.5z m-22.5-104l14-2.5 7-7.5-2-7 3-11-4-6s0-11.5-4.5-13.5-3-14.5-3-14.5l0.5-2.5-5 4.5 3 13 4.5 13 0.5 22.5-10.5 6.5-8 10.5 4.5-5.5z m10.5-25.5l-3-4-3.5 0.5-1 6.5 3.5 0.5 4-3.5zM831.8 803s-0.5-9-4.5-9-5.5-10-5.5-10l-8-8.5s0 12.5 0.5 14.5-5.5 5-7 4-7-9.5-7-9.5 2.2-4.8-1-4.5c-0.3 0-0.5 0.1-0.7 0.1-0.8-0.1-2.1-0.3-4.3-0.6l-10.4-1.3-1.1 4.3s-6 5-9.5 6.5-7.5 16-12 16-11 0.5-11 0.5-8 13.5-5.5 18.5 3.5 10 3 16 0.5 5 9 3.5 15.5-13 25-11 12.5 11.5 12.5 11.5l8-7s-1 5-2.5 5.5 1.5 1 3.5 4.5 12.9 8.4 27-5 9-25 9-25l-7.5-14z m-219.5-12c1-9.5-4.5-7-4-20.5s19-45 17.5-45-10 6.5-13 6-16-30-16-34.5-3.5-21.5-3.5-21.5 4 11.5 7 12 4 13 6.5 19 10.5 16 15.5 15.5 20.5-28.5 20.5-28.5l-8.5-5-9 5.5s-2.5-6-5.5-9.5-2.5-12-0.5-12 4.3 5.8 14.5 12.5 19.5 0 21.5 1.5 10 8 10.5 12 10.5 31.5 12 33.5 7.5-7 10-18.5 14.5-17 18.5-20 3.5 3.5 6 9.5 9 9.5 12 12 0.5 10.5 5.5 10.5 9-8.5 9-8.5l-3.5-9.5c-3.5-9.5 1.5-10.5 6-12.5s12.5-9.5 18-18-4.5-16.5-7.5-24 3.5-7 7-7.5 7 19.5 7 19.5l6.5-3.5s-1.5-6.5-4.5-12.5 4-6 12.5-15 3.6-13.3 4-24.5-10.5-10-10.5-10-2.5 4 0.5-10 23.5-6 28.5-6.5c3.9-0.4 8.8-10.1 10.7-14.2-23.4-9.6-48.9-14.9-75.8-14.9-106.8 0-194.1 83.7-199.7 189.2 2.4 1.3 5.6 2.7 9.3 3.4 8 1.5 4.5 14 5 20.5s2 13.5 5 20-1 10.5-2.5 15 2 11.5 5 11.5-5.5 15.5 4 31.5 23.5-1.5 30-24.5 17.5-20 18.5-29.5z m129-43.5l4 8 8.5 4s3-12 8-18-3-11.5-6.5-12.5l-14 18.5z m83.2-137c0.3 1 0.8 1.5 0.8 1.5s-0.3-0.6-0.8-1.5z m-60.1 161.8c-2.6-0.8-6.8-2-13.2-3.8-16-4.5-12-3-17.5-6.5s-0.5-5 0.5-9.5-5-19-5-19l-6-5-1.5 9-6.5 2 8 19s13.5 11.5 22 15.5c5.1 2.4 13.4 0.3 19.2-1.7zM619.8 826s8-14.5 9.5-21.5-1-18-1-18-10.5 8-8.5 9-3 5.5-4 13.5 4 17 4 17z m201.3-253.9c-4.4 5.4-10.3 13.1-10.3 15.9 0 4.5 7 4.5 7.5 8 0.4 2.7 4.5 11 6.2 14.5-0.4-1.3-0.6-3.4 0.8-6 2.5-4.5 3-15 0.5-19.5-1.2-2.1 0.3-6 2.2-9.6l-6.9-3.3zM769 770.5c1.4-0.6 2.2-1 2.2-1s-1 0.4-2.2 1z m-1.7 2.5c-1.5-0.6 0.2-1.7 1.8-2.5-1.2 0.5-2.8 1.1-4.6 1.8 4 1.2 4.3 1.3 2.8 0.7z')

    // 云
    const cloud = defs.append('g')
      .attr('id', 'cloud')
      .attr('transform', 'scale(0.042)')
    cloud.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')

    const dns = defs.append('g')
      .attr('id', 'dns')
      .attr('transform', 'scale(0.042)')
    dns.append('path')
      .attr('d', 'M449.774933 765.013333h47.650134v148.855467c56.6784-12.305067 107.6992-67.8656 141.312-148.846933h51.2c-17.442133 47.086933-40.32 88.064-67.362134 120.3968a374.801067 374.801067 0 0 0 150.818134-120.405334h57.617066C756.693333 884.650667 624.418133 964.266667 473.6 964.266667c-150.8096 0-283.093333-79.616-357.410133-199.253334h57.617066a374.801067 374.801067 0 0 0 150.818134 120.405334c-27.042133-32.341333-49.92-73.301333-67.370667-120.405334h51.2c33.621333 80.981333 84.642133 136.5504 141.320533 148.855467V765.021867z m0-422.4V169.873067c-62.097067 13.482667-117.410133 78.890667-150.468266 172.757333h-50.261334c18.176-57.173333 44.0576-106.606933 75.579734-144.298667-69.12 30.225067-127.342933 80.853333-167.125334 144.298667H102.4C173.320533 209.800533 312.951467 119.466667 473.6 119.466667S773.879467 209.800533 844.8 342.613333h-55.099733c-39.7824-63.4368-98.005333-114.065067-167.125334-144.298666 31.522133 37.700267 57.3952 87.125333 75.588267 144.298666h-50.261333c-33.066667-93.866667-88.379733-159.266133-150.4768-172.7488V342.613333h-47.650134zM68.266667 398.336h107.861333c47.931733 0 83.882667 13.243733 108.680533 39.739733 23.552 24.840533 35.541333 60.859733 35.541334 108.0576 0 46.779733-11.989333 82.807467-35.541334 108.0576-24.797867 26.496-60.7488 39.748267-108.680533 39.748267H68.266667V398.327467z m48.349866 41.3952v212.804267h50.414934c36.778667 0 63.6416-8.695467 80.580266-25.668267 16.5376-17.390933 24.797867-44.296533 24.797867-80.733867 0-37.265067-8.260267-64.5888-24.797867-81.152-16.938667-16.9728-43.8016-25.250133-80.580266-25.250133h-50.414934z m249.1904-41.403733h48.349867l145.877333 213.2224h1.646934V398.327467h48.768v295.611733h-47.112534L415.803733 477.824h-1.646933V693.930667h-48.349867V398.327467zM772.437333 392.533333c34.7136 0 61.986133 7.04 81.408 21.5296 20.667733 15.317333 32.648533 39.338667 35.541334 71.620267h-47.931734c-4.138667-18.210133-11.989333-31.4624-22.7328-39.330133-10.743467-8.277333-27.272533-12.0064-48.759466-12.0064-18.602667 0-32.648533 2.4832-42.564267 7.867733-12.398933 6.212267-18.184533 16.554667-18.184533 30.634667 0 12.424533 6.613333 22.775467 20.6592 30.225066 6.203733 3.310933 23.1424 9.5232 50.414933 18.218667 40.0896 12.416 65.706667 22.357333 77.277867 28.9792C883.2 565.589333 896 586.709333 896 614.033067c0 26.496-10.325333 47.616-30.993067 62.933333C844.347733 691.8656 815.0016 699.733333 777.386667 699.733333c-36.360533 0-64.878933-7.04-85.128534-21.111466-24.789333-17.390933-38.4256-44.714667-40.4992-82.389334h47.940267c3.3024 22.357333 11.1616 38.5024 23.970133 48.0256 11.5712 8.277333 29.3376 12.834133 53.717334 12.834134 21.495467 0 38.852267-3.729067 51.242666-10.769067 12.398933-7.4496 19.012267-16.9728 19.012267-29.397333 0-15.726933-9.506133-28.151467-27.690667-37.256534-5.7856-2.901333-24.789333-9.5232-57.437866-19.456-36.369067-11.5968-59.093333-19.882667-67.771734-24.840533-22.7328-13.6704-33.885867-33.536-33.885866-59.6224s10.743467-46.779733 33.058133-61.696C714.581333 399.581867 740.616533 392.533333 772.437333 392.533333z')

    const docker = defs.append('g')
      .attr('id', 'docker')
      .attr('transform', 'scale(0.042)')
    docker.append('path')
      .attr('d', 'M699.88718 472.6h-132.2v-118.8h132.2v118.8z m0-408.6h-132.2v121.4h132.2V64z m156.4 289.6H724.08718v118.8h132.2v-118.8z m-312.6-144.2h-132.2v120.2h132.2v-120.2z m156.2 0h-132.2v120.2h132.2v-120.2z m553.6 200c-28.8-19.4-95.2-26.4-146.2-16.8-6.6-48-33.4-89.8-82.2-127.4l-28-18.6-18.6 28c-36.8 55.6-46.8 147.2-7.4 207.6-17.4 9.4-51.6 22.2-96.8 21.4H4.88718c-17.4 101.6 11.6 233.6 88 324.2 74.2 87.8 185.4 132.4 330.8 132.4 314.8 0 547.8-145 656.8-408.4 42.8 0.8 135.2 0.2 182.6-90.4 3-5 13.2-26.4 17-34.2l-26.6-17.8z m-1022.2-55.8h-132v118.8h132.2v-118.8z m156.2 0h-132.2v118.8h132.2v-118.8z m156.2 0h-132.2v118.8h132.2v-118.8z m-156.2-144.2h-132.2v120.2h132.2v-120.2z')

    const gateway = defs.append('g')
      .attr('id', 'gateway')
      .attr('transform', 'scale(0.042)')
    gateway.append('path')
      .attr('d', 'M264.815304 86.77287v850.498782h573.217392V86.77287H264.815304z m286.630957 761.010087a48.217043 48.217043 0 1 1-0.044522-96.389566 48.217043 48.217043 0 0 1 0.044522 96.389566z m0-187.392a27.826087 27.826087 0 1 1 0 55.652173 27.826087 27.826087 0 0 1 0-55.652173z m-27.826087-64.912696a27.826087 27.826087 0 1 1 55.652174 0 27.826087 27.826087 0 0 1-55.652174 0z m185.032348-138.195478H394.195478V411.336348h314.457044v45.946435z m0-82.098087H394.195478V329.282783h314.457044v45.901913z m0-79.293218H394.195478V167.891478h314.457044v128z')
    gateway.append('path')
      .attr('d', 'M551.446261 799.521391m-18.565565 0a18.565565 18.565565 0 1 0 37.13113 0 18.565565 18.565565 0 1 0-37.13113 0Z')
    gateway.append('path')
      .attr('d', 'M437.337043 212.368696h228.173914v42.651826H437.337043z')

    const _switch = defs.append('g')
      .attr('id', 'switch')
      .attr('transform', 'scale(0.042)')
    _switch.append('path')
      .attr('d', 'M264.32 731.84h79.04a20.16 20.16 0 0 0 20.16-20.16v-18.88a20.16 20.16 0 0 0-20.16-20.16H264.32a20.16 20.16 0 0 0-20.16 20.16v18.88a20.16 20.16 0 0 0 20.16 20.16z m0 118.72h79.04a20.16 20.16 0 0 0 20.16-20.16v-18.88a20.16 20.16 0 0 0-20.16-20.16H264.32a20.16 20.16 0 0 0-20.16 20.16V832a20.16 20.16 0 0 0 20.16 20.16z m187.52-118.72a29.44 29.44 0 0 0 29.44-29.44 29.44 29.44 0 0 0-29.44-29.44 29.44 29.44 0 0 0-29.44 29.44 29.44 29.44 0 0 0 29.44 29.44z m0 118.72a29.44 29.44 0 0 0 29.44-29.44 29.44 29.44 0 0 0-29.44-29.44 29.44 29.44 0 0 0-29.44 29.44 29.44 29.44 0 0 0 29.44 29.44z m119.36-118.72a29.44 29.44 0 0 0 29.44-29.44 29.44 29.44 0 0 0-29.44-29.44 29.44 29.44 0 0 0-29.44 29.44 29.44 29.44 0 0 0 29.44 29.44z m0 118.72a29.44 29.44 0 0 0 29.44-29.44 29.44 29.44 0 0 0-29.44-29.44 29.44 29.44 0 0 0-29.44 29.44 29.44 29.44 0 0 0 29.44 29.44z m118.4-118.72a29.44 29.44 0 0 0 29.44-29.44 29.44 29.44 0 0 0-29.44-29.44 29.44 29.44 0 0 0-29.44 29.44 29.44 29.44 0 0 0 29.44 29.44z m0 118.72a29.44 29.44 0 0 0 29.44-29.44 29.44 29.44 0 0 0-29.44-29.44 29.44 29.44 0 0 0-29.44 29.44 29.44 29.44 0 0 0 29.44 29.44z m119.36-118.72a29.44 29.44 0 0 0 29.44-29.44 29.44 29.44 0 0 0-29.44-29.44 29.44 29.44 0 0 0-29.44 29.44 29.44 29.44 0 0 0 29.44 29.44z m0 118.72a29.44 29.44 0 0 0 29.44-29.44 29.44 29.44 0 0 0-29.44-29.44 29.44 29.44 0 0 0-29.44 29.44 29.44 29.44 0 0 0 29.44 29.44z m0 0')
    _switch.append('path')
      .attr('d', 'M960 610.88L898.24 157.44a29.76 29.76 0 0 0-29.44-25.6H217.92a29.76 29.76 0 0 0-29.44 25.6l-64 453.44v322.56A34.24 34.24 0 0 0 160 967.68h768a34.24 34.24 0 0 0 32-34.24V611.52zM512 192l-3.84 64h289.6v64h-294.4l-4.16 64-187.2-96z m-205.12 229.44h293.44l2.56-69.44L800 449.6l-204.8 104.32 2.56-64H299.52z m593.6 486.4H184.32v-296.96h715.84z m0 0')

    const tidb = defs.append('g')
      .attr('id', 'tidb')
      .attr('transform', 'scale(0.042)')
    tidb.append('path')
      .attr('d', 'M667.692 690.099h123.031v27.324H667.692z')
    tidb.append('path')
      .attr('d', 'M715.546 704.638h27.324v128.949h-27.324zM787.253 777.259h27.324v56.584h-27.324zM787.253 737.953h27.324v23.415h-27.324zM562.681 669.124a894.203 894.203 0 0 1-53.814 1.608c-147.778 0-274.579-35.833-329.148-86.976v86.976c0 63.231 147.363 114.486 329.148 114.486 21.935 0 28.7-1.796 49.434-3.219l-0.02 28.502c-20.791 1.504-27.538 3.338-49.414 3.338-185.513 0-337.998-56.471-355.962-128.797h-1.808V241.428c0-79.029 160.187-143.108 357.77-143.108s357.751 64.078 357.751 143.108v354.986l-28.622 0.487V440.649c-54.55 51.142-181.352 86.995-329.129 86.995s-274.579-35.852-329.148-86.995v86.995c0 63.231 147.363 114.486 329.148 114.486a942.32 942.32 0 0 0 54.927-1.587l-1.113 28.581z m-53.814-542.183c-181.785 0-329.148 51.255-329.148 114.486s147.363 114.486 329.148 114.486 329.129-51.255 329.129-114.486-147.345-114.486-329.129-114.486z m329.129 170.6c-54.55 51.142-181.352 86.995-329.129 86.995s-274.579-35.852-329.148-86.995v86.995c0 63.231 147.363 114.486 329.148 114.486s329.129-51.255 329.129-114.486v-86.995zM208.34 253.516v-16.721c51.425 44.91 166.645 76.186 300.527 76.186s249.102-31.277 300.527-76.186v16.721c-58.599 44.006-171.221 73.776-300.527 73.776s-241.928-29.77-300.527-73.776z')
    tidb.append('path')
      .attr('d', 'M745.683 928.162l-149.077-86.538 0.402-172.383 149.479-85.844 149.04 86.538-0.402 172.383-149.442 85.844zM623.93 825.916l121.826 70.685 122.118-70.137 0.329-140.821-121.789-70.685-122.155 70.137-0.329 140.821z')

    const user = defs.append('g')
      .attr('id', 'user')
      .attr('transform', 'scale(0.042)')
    user.append('path')
      .attr('d', 'M868.790857 726.747429a344.649143 344.649143 0 0 0-98.742857-69.046858 228.059429 228.059429 0 0 0 84.845714-177.737142 229.961143 229.961143 0 0 0-232.301714-228.571429 228.717714 228.717714 0 0 0-140.288 406.308571c-36.278857 16.822857-69.632 39.862857-98.742857 69.046858a340.626286 340.626286 0 0 0-100.132572 233.033142 9.142857 9.142857 0 0 0 9.142858 9.362286h64A9.069714 9.069714 0 0 0 365.714286 960.365714a258.340571 258.340571 0 0 1 76.214857-175.469714 258.998857 258.998857 0 0 1 184.32-76.288c69.632 0 135.094857 27.062857 184.32 76.288a259.364571 259.364571 0 0 1 76.288 175.469714c0.146286 4.900571 4.242286 8.777143 9.142857 8.777143h64c5.12 0 9.289143-4.242286 9.142857-9.362286a339.894857 339.894857 0 0 0-100.352-233.033142zM626.249143 626.322286a145.042286 145.042286 0 0 1-103.424-42.861715 146.505143 146.505143 0 0 1 101.961143-249.709714 147.163429 147.163429 0 0 1 104.009143 41.764572c28.379429 27.794286 43.885714 64.950857 43.885714 104.448 0 39.131429-15.213714 75.776-42.861714 103.497142a145.554286 145.554286 0 0 1-103.497143 42.788572zM340.041143 510.098286a293.010286 293.010286 0 0 1 3.364571-83.382857 9.216 9.216 0 0 0-5.12-10.020572 145.773714 145.773714 0 0 1-86.381714-137.728 145.554286 145.554286 0 0 1 41.398857-97.792 145.554286 145.554286 0 0 1 106.569143-44.251428 146.139429 146.139429 0 0 1 121.563429 67.291428 9.142857 9.142857 0 0 0 10.605714 3.657143c20.114286-7.021714 41.398857-11.922286 63.195428-14.262857a9.142857 9.142857 0 0 0 7.241143-13.165714A228.937143 228.937143 0 0 0 401.554286 54.857143C274.870857 52.882286 169.252571 156.818286 169.252571 283.282286c0 71.826286 33.060571 135.826286 84.845715 177.737143-36.352 16.822857-69.851429 40.009143-98.889143 69.046857A339.894857 339.894857 0 0 0 54.857143 763.172571a9.142857 9.142857 0 0 0 9.142857 9.435429h64.146286a9.069714 9.069714 0 0 0 9.142857-8.777143 258.340571 258.340571 0 0 1 76.214857-175.542857 257.974857 257.974857 0 0 1 119.661714-68.169143 9.142857 9.142857 0 0 0 6.802286-9.947428z')

    const innerUser = defs.append('g')
      .attr('id', 'innerUser')
      .attr('transform', 'scale(0.042)')
    innerUser.append('path')
      .attr('d', 'M587.310545 549.050182a266.053818 266.053818 0 0 0 111.057455-216.157091c0-148.200727-122.600727-268.846545-273.221818-268.846546S152.576 184.133818 152.576 332.334545c0 88.436364 44.032 167.098182 111.057455 216.064C109.102545 611.141818 0 761.297455 0 935.377455c0 23.272727 19.176727 42.077091 42.821818 42.07709a42.449455 42.449455 0 0 0 42.821818-42.07709c0-184.692364 152.482909-334.848 340.154182-334.848s340.247273 150.155636 340.247273 334.848c0 23.272727 19.083636 42.077091 42.821818 42.07709a42.449455 42.449455 0 0 0 42.635636-42.07709c-0.558545-174.08-109.754182-323.584-264.192-386.327273zM237.474909 332.334545c0-101.841455 84.247273-184.692364 187.671273-184.692363 103.330909 0 187.671273 82.850909 187.671273 184.692363 0 101.748364-84.247273 184.692364-187.671273 184.692364s-187.671273-82.850909-187.671273-184.692364z m642.792727 148.852364a238.778182 238.778182 0 0 0 98.304-219.880727C966.469818 147.642182 871.982545 57.157818 755.898182 47.755636c-43.380364-3.723636-69.632 1.210182-104.075637 16.942546-6.330182 3.072-29.975273 20.666182-13.963636 45.893818 9.495273 12.474182 20.386909 26.996364 51.013818 16.290909 26.065455-8.843636 40.96-7.540364 73.448728-2.513454 72.704 11.357091 130.885818 69.818182 140.38109 141.963636 12.753455 96.721455-60.043636 179.665455-154.530909 186.554182-13.312 3.723636-20.293818 18.897455-22.341818 31.464727-1.861818 13.777455 3.258182 42.077091 12.846546 43.938909 165.981091 1.303273 301.335273 134.423273 301.893818 297.797818 0 19.456 14.056727 36.398545 33.233454 38.912A37.981091 37.981091 0 0 0 1117.090909 827.950545c-0.651636-156.392727-98.304-290.257455-236.823273-346.763636z')

    const redis = defs.append('g')
      .attr('id', 'redis')
      .attr('transform', 'scale(0.042)')
    redis.append('path')
      .attr('d', 'M632.661333 774.4c-65.194667 33.962667-101.12 33.621333-152.746666 8.96-51.2-24.576-479.573333-203.52-479.573334-203.52v129.706667c0 11.264 15.701333 23.04 44.714667 36.949333 58.538667 28.074667 383.658667 159.146667 434.773333 183.893333 51.2 24.576 87.466667 24.746667 152.746667-8.96 65.109333-34.048 370.346667-159.317333 429.312-190.293333 30.037333-15.616 43.264-27.904 43.264-38.826667v-128h-0.085333c-0.170667-0.170667-407.466667 175.957333-472.405334 210.090667z')
    redis.append('path')
      .attr('d', 'M632.661333 592.213333c-65.194667 33.962667-101.12 33.536-152.746666 8.96-51.2-24.746667-479.573333-203.776-479.573334-203.776v129.706667c0 11.178667 15.701333 22.954667 44.714667 36.864 58.538667 28.16 383.658667 159.317333 434.773333 184.064 51.2 24.576 87.466667 24.746667 152.746667-8.96 65.109333-34.048 370.346667-159.317333 429.312-190.293333 30.037333-15.616 43.264-27.904 43.264-38.826667v-128h-0.085333c-0.170667 0-407.466667 176.128-472.405334 210.176z')
    redis.append('path')
      .attr('d', 'M1105.322667 211.114667c0.512-11.178667-14.421333-21.162667-43.776-31.914667C1004.032 158.037333 700.501333 37.290667 642.304 16.042667c-58.282667-21.333333-81.92-20.309333-150.186667 4.010666S100.693333 171.349333 43.093333 193.877333c-29.013333 11.264-43.093333 21.930667-42.24 33.28V226.986667v127.658666S428.885333 535.04 480.426667 559.530667c51.2 24.661333 87.381333 24.746667 152.661333-8.96 65.024-34.218667 472.661333-213.333333 472.661333-213.333334l-0.426666-126.122666z m-143.018667 3.413333L794.453333 280.746667l-151.296-59.818667 167.594667-66.304 151.552 59.904z m-310.016 62.890667l-77.568 113.493333-178.517333-74.069333 256-39.424z m-134.485333-172.373334l-24.746667-45.738666 77.312 30.122666 72.618667-23.893333-19.712 47.104 74.24 27.904-95.829334 9.813333-21.504 51.797334-34.56-57.685334-110.506666-9.728 82.773333-29.781333z m-190.634667 64.170667c75.605333 0 136.874667 23.637333 136.874667 53.077333 0 29.184-61.269333 53.077333-136.874667 53.077334-75.605333 0-136.96-23.637333-136.96-53.077334 0.170667-29.013333 61.44-53.077333 136.96-53.077333z')
  }

  initBox() {
    this.drawBox();
    this.drawBoxText();
  }
  // 初始化鏈接線
  initLink() {
    this.drawLinkLine();
    this.drawLinkText();
  }

  // 初始化節(jié)點
  initNode() {
    var self = this;
    // 節(jié)點容器
    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 (d) {
        self.currentNode = d
        self.showDialog = true
      })

    // 節(jié)點背景默認覆蓋層
    this.nodes.append('circle')
      .attr('r', symbolSize / 2 + padding)
      .attr('class', 'node-bg')
      .style('opacity', 0.0); // 設(shè)置透明背景

    // 節(jié)點圖標
    this.drawNodeSymbol();
    // 節(jié)點標題
    this.drawNodeTitle();
    // 節(jié)點其他說明
    // this.drawNodeOther();
    // this.drawNodeCode();
  }

  // 畫節(jié)點語言標識
  // 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);
  // }

  // 畫節(jié)點圖標
  drawNodeSymbol() {
    this.nodeUse && this.nodes.selectAll('use').remove();
    this.nodeUse = this.nodes.append('use');
    this.nodeUse
      .attr('xlink:href', function (d) {
        return '#' + d.type
      })
      .attr('fill', function (d) {
        return colors[d.color] || colors[0]
      })
      .attr('x', function () {
        return -20
      })
      .attr('y', function () {
        return -20
      })
    let run = () => {
      this.nodeUse.filter(node => node.color === 'danger')
        .transition()
        .duration(300)
        .style('fill', boxBg)
        .transition()
        .duration(300)
        .style('fill', function (d) {
          return colors[d.color] || colors[0]
        })
        .on('end', run)
    }
    run()
  }

  // 畫節(jié)點標題
  drawNodeTitle() {
    this.nodeTitle && this.nodes.selectAll('text').remove();
    this.nodeTitle = this.nodes.append('text');
    this.nodeTitle
      .attr('class', 'node-title')
      .text(function (d) {
        return d.name;
      })
      .attr('dy', 35)
  }

  // 畫節(jié)點右側(cè)信息
  drawNodeOther() {
    this.nodes.filter(node => node.other !== undefined)
      .append('circle')
      .attr('cx', 35)
      .attr('cy', -3)
      .attr('r', 5)
      .attr('fill', colors.primary);
    this.nodes.filter(node => node.other !== undefined)
      .append('text')
      .attr('dx', 45)
      .attr('dy', 0)
      .text(d => d.other.num)

    this.nodes.filter(node => node.other !== undefined)
      .append('circle')
      .attr('cx', 35)
      .attr('cy', 7)
      .attr('r', 5)
      .attr('fill', colors.danger);
    this.nodes.filter(node => node.other !== undefined)
      .append('text')
      .attr('dx', 45)
      .attr('dy', 10)
      .text(d => d.other.error)
  }

  // 畫節(jié)點鏈接線
  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')
        .style('storke-width', '0.2') // 邊框?qū)挾?        .attr('stroke', lineColor)
        .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 source = data.find(f => f.id === d.source)
      let target = data.find(f => f.id === d.target)
      if (source && target) {
        let sx = source.x; // data[d.source].x;
        let tx = target.x; // data[d.target].x;
        let sy = source.y; // data[d.source].y;
        let ty = target.y; // 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('class', 'linetext')
        .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.find(f => f.id === link.source); // data[link.source];
      let t = data.find(f => f.id === link.target); // data[link.target];
      if (s && t) {
        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 + ')'
      }
    }
  }

  drawBox() {
    if (this.lineBoxGroup) {
      this.lineBoxGroup.selectAll('.box')
    } else {
      this.lineBoxGroup = this.container.append('g')

      this.lineBoxGroup.selectAll('.box')
        .data(this.boxs)
        .enter()
        .append('rect')
        .attr('class', 'box')
        .attr('x', box => box.x)
        .attr('y', box => box.y)
        .attr('width', box => box.width)
        .attr('height', box => box.height)
        .attr('fill', boxBg)
        .style('storke-width', '0.2') // 邊框?qū)挾?            .style('stroke', boxBorder)
        .attr(
          'id', (box, i) => 'box-' + i
        )
        .on('click', () => { alert() })
    }
  }

  drawBoxText () {
    this.boxTextGroup = this.container.append('g')
    this.boxTexts = this.boxTextGroup
      .selectAll('g')
      .data(this.boxs.filter(t => t.text !== undefined))
      .enter()
      .append('text')
      .attr('class', 'boxText')
      .attr('x', function(d, i) { // 每個矩形的起始x坐標
        return d.textPosition === 'right' ? (d.x + d.width - 30) : (d.x + 30)
      })
      .attr('y', function(d, i) {
        return d.y + 20
      }).text(function(d) { // 添加文字描述
        return d['text'];
      })
      .style('font-size', fontSize)// 設(shè)置文字大小
  }

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

  // 重畫
  redraw(option) {
    this.data = option.data;
    this.edges = option.edges;
    this.boxs = option.boxs;
    let update = this.nodes.data(this.data)
    update.attr('transform', function (d) {
      return 'translate(' + d.x + ',' + d.y + ')';
    })
    // 節(jié)點圖標
    this.drawNodeSymbol();
    // 節(jié)點標題
    this.drawNodeTitle();
    this.update()
  }

  downloadImg(name = 'topo', type = 'png') { // 將當前canvas轉(zhuǎn)換為png圖片
    var serializer = new XMLSerializer();
    var source = serializer.serializeToString(this.svg.node());
     
    source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
    var url = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);
    var image = new Image();
    image.src = url;
    let canvas = document.createElement('canvas');
    canvas.width = 1600;
    canvas.height = 800;
    var context = canvas.getContext('2d');
    context.fillStyle = '#fff'; // #fff設(shè)置保存后的PNG 是白色的  
    context.fillRect(0, 0, 10000, 10000);
    image.onload = function() {
      context.drawImage(image, 0, 0); // 圖片要完全loaded后才能draw,否則火狐下的報錯'Component is not available'
      
      var a = document.createElement('a');
      document.body.appendChild(a);
      a.download = `${name + new Date().toLocaleString()}.${type}`
      a.href = canvas.toDataURL(`image/${type}`)
      var evt = new MouseEvent('click', { view: window, bubbles: true, cancelable: true });
      a.dispatchEvent(evt);
      document.body.removeChild(a);
    }
  }
}

export class Node {
  constructor (id, type, name, status, x, y) {
    this.id = id || uniqueId()
    this.name = name
    this.type = type
    this.color = status
    this.x = x
    this.y = y
  }
}

index.vue

 <template>
  <div class="main-container">
    <el-row class="main-top">
      <el-col :span="12">
        <div class="top-left">
          <el-select
            v-model="refresh"
            size="small"
            placeholder=""
            :style="isZh ? 'width: 100px': ''"
            @change="changeRefresh"
          >
            <el-option
              v-for="item in refreshs"
              :key="item.value"
              :label="$t(item.i18nKey)"
              :value="item.value">
            </el-option>
          </el-select>
        </div>
      </el-col>
      <el-col :span="12">
        <div class="top-right">
          <template v-for="item of Object.keys(state)">
            <span :key="item + '_dot'" class="dot" :class="state[item]"></span>
            <span :key="item + '_text'" class="text" :class="state[item]">
              {{ $t(`topo.${state[item]}`) }}
            </span>
          </template>
          <el-button type="primary" size="small" @click="exportPNG">
            {{ $t('btn.png') }}
          </el-button>
        </div>
      </el-col>
    </el-row>
    <div class="svg-container">
      <svg
        :id="docId"
        class="topo"
        width="1600"
        height="800"
      />
    </div>
  </div>
</template>
<script>
import locale from '@/i18n/locale.mixin'
import { Topo, Node } from './topo'
// import detail from './detail'
export default {
  components: {
    // detail
  },
  mixins: [locale],
  data () {
    return {
      topo: {},
      topoData: {
        data: [],
        edges: [],
        boxs: []
      },
      docId: 'topo',
      state: {
        'NORMAL': 'primary',
        'WARN': 'warning',
        'BREAK': 'danger'
      },
      refresh: 60,
      refreshs: [
        { value: 0, i18nKey: 'topo.refreshNone' },
        { value: 10, i18nKey: 'topo.refresh10' },
        { value: 30, i18nKey: 'topo.refresh30' },
        { value: 60, i18nKey: 'topo.refresh60' },
        { value: 120, i18nKey: 'topo.refresh120' }
      ],
      timer: ''
    }
  },
  created () {
    
  },
  async mounted () {
    await this.initData()
    this.topo = new Topo('#' + this.docId, this.topoData)
    this.topo.render();
    // this.timer = setInterval(this.update, this.refresh * 1000);
  },
  beforeDestroy() {
    clearInterval(this.timer);
  },
  methods: {
    initData (refresh = false) {
      const x = 50;
      const y = 50;
      // 節(jié)點數(shù)據(jù)
      let data = require('./topoNode.json');
      refresh && this.topoData.data.splice(0, this.topoData.data.length);
      data.forEach(item => {
        let node = new Node(
          item.id,
          item.description ? item.description : 'innerUser',
          item.name,
          item.state ? item.state : 'primary',
          x * item.positionX,
          y * item.positionY
        )
        this.topoData.data.push(node)
      })
      // 連線數(shù)據(jù)
      refresh && this.topoData.edges.splice(0, this.topoData.edges.length);
      this.topoData.edges = require('./topoEdge.json');
    },
    changeRefresh(v) {
      if (v > 0) {
        this.timer && clearInterval(this.timer);
        this.timer = setInterval(this.update, v * 1000);
        this.update()
      } else {
        clearInterval(this.timer);
      }
    },
    async update() {
      // TODO
      await this.initData(true)
      this[this.docId].redraw(this.topoData)
    },
    exportPNG() {
      this[this.docId].downloadImg()
    }
  }
}
</script>

<style>
  .svg-container {
    border:1px solid #ccc;
    height: calc(100vh - 180px); /*垂直 500 < 550*/
    overflow:auto;
  }
  .topo{
    width: 100%;
    min-height: 500px;
    height: 800px;
    overflow:auto;
    user-select: none;
  }
  .topo text{
    font-size:10px;/*和js里保持一致*/
    fill:#1A2C3F;
    text-anchor: middle;
  }
  .topo .node-other{
    text-anchor: start;
  }
  .topo .node-title{
    font-size: 14px;
  }
  .topo .node-code circle{
    fill:#3F86F5;
  }
  .topo .node-code text{
    fill:#fff;
  }
  .topo .arrow{
    fill:#E4E8ED;
  }
  .main-top .title-box {
    line-height: 16px;
  }
  .main-top .top-left, .main-top .top-right {
    line-height: 32px;
  }
  .main-top .top-right {
    text-align: right;
  }
  .main-top .top-right .dot {
    top: 0;
    margin-left: 20px;
  }
  .main-top .dot.primary {
    background-color: #409EFF;
  }
  .main-top .primary {
    color: #409EFF;
  }
  .main-top .dot.warning {
    background-color: #E6A23C;
  }
  .main-top .warning {
    color: #E6A23C;
  }
  .main-top .dot.danger {
    background-color: #F56C6C;
  }
  .main-top .danger {
    color: #F56C6C;
  }
  .main-top .top-right .text {
    margin-left: 5px;
    font-weight: 700;
  }
  .dot {
    position: relative;
    top: -1px;
    display: inline-block;
    width: 10px;
    height: 10px;
    margin-right: 2px;
    border-radius: 8px;
    background-color: #1890ff;
  }
</style>

topoNode.json

[
  {
    "id": 1,
    "name": "用戶",
    "description": "user",
    "positionX": 2,
    "positionY": 2
  },
  {
    "id": 2,
    "name": "云",
    "description": "cloud",
    "positionX": 2,
    "positionY": 4
  },
  {
    "id": 3,
    "name": "DNS",
    "description": "dns",
    "positionX": 3,
    "positionY": 4
  },
  {
    "id": 4,
    "name": "四層交換機",
    "description": "switch",
    "state": "danger",
    "positionX": 8,
    "positionY": 4
  },
  {
    "id": 5,
    "name": "交換機",
    "description": "switch",
    "positionX": 2,
    "positionY": 6
  },
  {
    "id": 6,
    "name": "交換機",
    "description": "switch",
    "positionX": 4,
    "positionY": 6
  },
  {
    "id": 7,
    "name": "交換機",
    "description": "switch",
    "positionX": 6,
    "positionY": 6
  },
  {
    "id": 8,
    "name": "交換機",
    "description": "switch",
    "positionX": 8,
    "positionY": 6
  },
  {
    "id": 9,
    "name": "交換機",
    "description": "switch",
    "positionX": 10,
    "positionY": 6
  },
  {
    "id": 10,
    "name": "交換機",
    "description": "switch",
    "positionX": 12,
    "positionY": 6
  },
  {
    "id": 11,
    "name": "工作站",
    "description": "OpenResty",
    "positionX": 2,
    "positionY": 8
  },
  {
    "id": 12,
    "name": "工作站",
    "description": "OpenResty",
    "positionX": 4,
    "positionY": 8
  },
  {
    "id": 13,
    "name": "工作站",
    "description": "OpenResty",
    "positionX": 6,
    "positionY": 8
  },
  {
    "id": 14,
    "name": "工作站",
    "description": "OpenResty",
    "positionX": 8,
    "positionY": 8
  },
  {
    "id": 15,
    "name": "工作站",
    "description": "OpenResty",
    "positionX": 10,
    "positionY": 8
  },
  {
    "id": 16,
    "name": "工作站",
    "description": "OpenResty",
    "positionX": 12,
    "positionY": 8
  },
  {
    "id": 17,
    "name": "服務(wù)器",
    "description": "gateway",
    "positionX": 11,
    "positionY": 4
  },
  {
    "id": 18,
    "name": "",
    "description": "gateway",
    "positionX": 11.5,
    "positionY": 4
  }
]

topoEdge.json

[
  {
    "source": 1,
    "target": 2
  },
  {
    "source": 2,
    "target": 4
  },
  {
    "source": 4,
    "target": 5
  },
  {
    "source": 4,
    "target": 5
  },
  {
    "source": 4,
    "target": 6
  },
  {
    "source": 4,
    "target": 7
  },
  {
    "source": 4,
    "target": 8
  },
  {
    "source": 4,
    "target": 9
  },
  {
    "source": 4,
    "target": 10
  },
  {
    "source": 5,
    "target": 11
  },
  {
    "source": 6,
    "target": 12
  },
  {
    "source": 7,
    "target": 13
  },
  {
    "source": 8,
    "target": 14
  },
  {
    "source": 9,
    "target": 15
  },
  {
    "source": 10,
    "target": 16
  },
  {
    "source": 4,
    "target": 17
  }
]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末犀勒,一起剝皮案震驚了整個濱河市辛萍,隨后出現(xiàn)的幾起案子窗宇,更是在濱河造成了極大的恐慌疗绣,老刑警劉巖烙懦,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踪区,死亡現(xiàn)場離奇詭異昆烁,居然都是意外死亡,警方通過查閱死者的電腦和手機缎岗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門静尼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人传泊,你說我怎么就攤上這事鼠渺。” “怎么了眷细?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵拦盹,是天一觀的道長。 經(jīng)常有香客問我溪椎,道長掌敬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任池磁,我火速辦了婚禮奔害,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘地熄。我一直安慰自己华临,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布端考。 她就那樣靜靜地躺著雅潭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪却特。 梳的紋絲不亂的頭發(fā)上扶供,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音裂明,去河邊找鬼椿浓。 笑死,一個胖子當著我的面吹牛闽晦,可吹牛的內(nèi)容都是我干的扳碍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼仙蛉,長吁一口氣:“原來是場噩夢啊……” “哼笋敞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荠瘪,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤夯巷,失蹤者是張志新(化名)和其女友劉穎赛惩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趁餐,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡坊秸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了澎怒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡阶牍,死狀恐怖喷面,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情走孽,我是刑警寧澤惧辈,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站磕瓷,受9級特大地震影響盒齿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜困食,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一边翁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硕盹,春花似錦符匾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至垛贤,卻和暖如春焰坪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背聘惦。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工某饰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人善绎。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓露乏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涂邀。 傳聞我的和親對象是個殘疾皇子瘟仿,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容