d3-graphviz实现拓扑图

d3-graphviz实现拓扑图

源码地址:https://gitee.com/simod/d3-graphviz

具体代码如下:

<template>
//防止节点数据太多页面出现空白,所以添加loading
  <div  class="main-charts" v-loading="loadList" element-loading-text="拼命加载中">
  
  // 承载画布的元素
    <div class="graph-container" id="graph"></div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      source: {}, //或缺节点数据
      layers: [], //节点所对应的层
      graph: {}, // d3-graphviz的对象
      ciTypesName: {},
      loadList: false,
      tabList:[]
    }
  },
 methods: {
   initGraph () {
      this.loadList = true
      let graph
      const initEvent = () => {
      //获取承载拓扑图画布的对象
        graph = d3.select('#graph')
        graph
          .on('dblclick.zoom', null) 
          .on('wheel.zoom', null)
          .on('mousewheel.zoom', null)
        // 这些属性确保拓扑图可以双击或者鼠标滚动可以放大缩小图
        
        this.graph.graphviz = graph
          .graphviz()
          .zoom(true) //允许拓扑图可以放大缩小
          .scale(2) //设置拓扑图放大缩小的比例
          .width(window.innerWidth - 92)
          // 画布的宽度
          .height(window.innerHeight - 190)
          // 设置画布的高度
          .attributer(function (d) {
            if (d.attributes.class === 'edge') {
              var keys = d.key.split('->')
              var from = keys[0].trim()
              var to = keys[1].trim()
              d.attributes.from = from
              d.attributes.to = to
            }
            if (d.tag === 'text') {
              var key = d.children[0].text
              d3.select(this).attr('text-key', key)
            }
          })
          //渲染拓扑图的函数
          this.renderGraph(this.source)
      }
      let layerResponse = {}
      _fetch({
        url: `/cmdb/cmdb/getAllLevel`,
        type: "get",
      })
      .then(response => {
        layerResponse = response.data
        if (layerResponse.status) {
          let tempLayer = layerResponse.body.data
            .map(_ => {
              return { name: _.name, layerId: _._id, ..._ }
            })
            // 将获取到的层排序
          this.layers = tempLayer.sort((a, b) => {
            return a.seqNo - b.seqNo
          })
          _fetch({
            url: `/cmdb/cmdb/getFullCitRelation`,
            type: "get",
          })
          .then(response => {
            let ciResponse = response.data
            if (ciResponse.status) {
            //获取节点后,将节点处理
              this.source = ciResponse.body.data
              this.source.forEach(_ => {
                _.ciTypes &&
                  _.ciTypes.forEach(async i => {
                    this.ciTypesName[i.ciTypeId] = i.name
                    let imgFileSource = i.imageFileId === undefined
                        ? '/controlview/assets/img/icon//icon (1).png'
                        : i.imageFileId
                    this.$set(i, 'form', {
                      ...i,
                      imgSource: imgFileSource,
                      imgUploadURL: `imgFileSource`
                    })
                    i.references &&
                      i.references.forEach(j => {
                        this.$set(j, 'form', {
                          ...j,
                          isAccessControlled: j.isAccessControlled ? 'yes' : 'no',
                          isNullable: j.isNullable ? 'yes' : 'no',
                          isSystem: j.isSystem ? 'yes' : 'no'
                        })
                      })
                  })
              })
              
              let uploadToken = document.cookie.split(';').find(i => i.indexOf('XSRF-TOKEN') !== -1)
              setHeaders({
                'X-XSRF-TOKEN': uploadToken && uploadToken.split('=')[1]
              })
              initEvent()
            }
          })
        }
      })
    },
    genDOT (data) {
      let nodes = []
      data.forEach(_ => {
        if (_.ciTypes) nodes = nodes.concat(_.ciTypes)
      })
      // 组装成d3-graphviz所需要的数据
      var dots = [
        'digraph  {',
        'bgcolor="transparent";',
        'node [fontname=Arial,shape="ellipse", fixedsize="true", width="1.1", height="1.1", color="transparent", fontsize=11];',
        'edge [fontname=Arial,minlen="1", color="#7f8fa6", fontsize=10];',
        'ranksep = 1.1; nodesep=.7; size = "11,8";rankdir=TB'
      ] 
      //设置基础属性
      let layerTag = `node [];`

      // 将所有节点的层数据组装
      let tempClusterObjForGraph = {}
      let tempClusterAryForGraph = []
      this.layers.map((_, index) => {
        if (index !== this.layers.length - 1) {
          layerTag += `"layer_${_.layerId}"->`
        } else {
          layerTag += `"layer_${_.layerId}"`
        }
        // “rank=same”属性很重要,此属性是将顶点分组
        tempClusterObjForGraph[index] = [
          `{ rank=same;  "layer_${_.layerId}"[id="layerId_${_.layerId}",class="layer",label="${_.name}",tooltip="${_.name}"];`
        ]
        // 根据层id找出每一层所对应的节点组装节点数据
        nodes.forEach((node, nodeIndex) => {
          if (node.layerId === _.layerId) {
            tempClusterObjForGraph[index].push(
              `"ci_${node.ciTypeId}"[id="${node.ciTypeId}~${node.alias}",label="${node.name}",tooltip="${node.name}",class="ci cit_${node.ciTypeId}",image="${node.form.imgSource}", labelloc="b"]`
            )
          }
          if (nodeIndex === nodes.length - 1) {
            tempClusterObjForGraph[index].push('} ')
          }
        })
        if (nodes.length === 0) {
          tempClusterObjForGraph[index].push('} ')
        }
        tempClusterAryForGraph.push(tempClusterObjForGraph[index].join(''))
      })

      dots.push(tempClusterAryForGraph.join(''))
      dots.push('{' + layerTag + '[style=invis]}')

      // 找出每个节点之间所对应的连线关系
      nodes.forEach(node => {
        node.references &&
          node.references.forEach(attr => {
            if (attr) {
              const target = nodes.find(_ => _.ciTypeId === attr.referenceId)
              if (target) {
                dots.push(this.genEdge(nodes, node, attr))
              }
            }
          })
      })
      
      dots.push('}')
      return dots.join('')

    },
    genEdge (nodes, from, to) {
      const target = nodes.find(_ => _.ciTypeId === to.referenceId)
      let labels = to.referenceName ? to.referenceName.trim() : ''
      return `"ci_${from.ciTypeId}"->"ci_${target.ciTypeId}"[taillabel="${labels}",labeldistance=3];`
    },
    // 设置节点以及连线的样式
    shadeAll () {
      d3.selectAll('g path')
        .attr('stroke', '#7f8fa6')
        .attr('stroke-opacity', '.2')
      d3.selectAll('g polygon')
        .attr('stroke', '#7f8fa6')
        .attr('stroke-opacity', '.2')
        .attr('fill', '#7f8fa6')
        .attr('fill-opacity', '.2')
      d3.selectAll('.edge text').attr('fill', '#7f8fa6')
    },
    colorNode (nodeName) {
      d3.selectAll('g[from="' + nodeName + '"] path')
        .attr('stroke', 'red')
        .attr('stroke-opacity', '1')
      d3.selectAll('g[from="' + nodeName + '"] text').attr('fill', 'red')
      d3.selectAll('g[from="' + nodeName + '"] polygon')
        .attr('stroke', 'red')
        .attr('fill', 'red')
        .attr('fill-opacity', '1')
        .attr('stroke-opacity', '1')
      d3.selectAll('g[to="' + nodeName + '"] path')
        .attr('stroke', 'green')
        .attr('stroke-opacity', '1')
      d3.selectAll('g[to="' + nodeName + '"] text').attr('fill', 'green')
      d3.selectAll('g[to="' + nodeName + '"] polygon')
        .attr('stroke', 'green')
        .attr('fill', 'green')
        .attr('fill-opacity', '1')
        .attr('stroke-opacity', '1')
    },
    renderGraph (data) {
      let nodesString = this.genDOT(data)
      this.graph.graphviz
        .transition()
        .renderDot(nodesString, () => {   
          this.loadList = false   
          this.$nextTick(() => {
            this.startApps()
          })
        })
         //渲染完成 的回调函数
        .on('end', () => {
          this.shadeAll()
        })
      
    },
    // 节点添加事件
    startApps() {
      d3.selectAll(".node").attr('cursor', 'pointer')
      var nodes = d3.selectAll(".node");
      var _that=this
      nodes.on("mouseover", function(el) {
        const e = d3.event
        e.preventDefault()
        e.stopPropagation()
        const endNode = d3.select(this)
        var nodeName = endNode.selectWithoutDataPropagation("title").text().trim()
        _that.shadeAll()
        _that.colorNode(nodeName)
      })
      nodes.on("dblclick", function(el) {
        _that.handleNodeClick(this)
      })
    },
    handleNodeClick (kl) {
      const e = d3.event
      e.preventDefault()
      e.stopPropagation()
      const endNode = d3.select(kl)
      const g = endNode._groups[0][0]
      let isLayerSelected = g.getAttribute('class').indexOf('layer') >= 0
      if (isLayerSelected) {
        return
      }
      const found = this.tabList.find(_ => _.id === g.id)
      if (!found) {
        const ci = {
          name: endNode.selectWithoutDataPropagation("text").text().trim(),
          id: g.id.split('~')[0],
          aliasName: g.id.split('~')[1]
        }
        
      }
    }


},
  mounted() {
    this.initGraph()
  }

}
</script>

后端应该返回的接口数据

返回节点所对应的层的数据

{
    data:[{
		"name": "应用架构层",
		"seqNo": 1,
		"_id": 1,
	}]
}

返回所有节点的数据格式:

{
    data:[{
      "codeId" : "1",//层ID
      "value" : "应用系统",//层名称
      "ciTypes" : [ {
        //节点所对应的层id
        "layerId" : "1",
        "seqNo" : 0,
        // 当前节点所对应的关系
        "references" : [ {
          //连线的目标ID,以及连线的关系
          "referenceId" : "16fb095d60484b08952a63661a059164",
          "referenceName" : "组成"
        } ],
        //节点名称
        "name" : "产品",
        //当前节点的id
        "ciTypeId" : "8ce47ddc86cc44fbb59e15cb5d683b48",
        // 节点的图片
        "imageFileId" : "/controlview/assets/img/linkShow//icon (8).png"
      }]
}

引入依赖:

使用时引入的框架

1,引入d3.js

2,引入vize.js

<script src="https://unpkg.com/viz.js@1.8.1/viz.js" type="javascript/worker"></script>

3,引入@hpcc-js/wasm,d3-graphviz中渲染数据需要用到,

4,最后引入d3-graphviz

最终效果图的展示
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值