在Vue2下使用D3.js可视化知识图谱

下载依赖

注意要下载5.16.0版本,亲测4和6版本不能用

// 下载d3.js
npm install d3@5.16.0
// 在项目中导入d3.js
import * as d3 from "d3";

效果展示

在这里插入图片描述

代码如下

json文件

// inheritor.json
{
  "code": 0,
  "message": "",
  "data": {
    "relations": [{
      "id": "2730",
      "name": "编号",
      "semantic_type": "编号",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 0,
      "relation_node": {
        "id": "2c29f5249cf211ebbc710242c0a8c409",
        "name": "01-0072",
        "semantic_type": "编号",
        "labels": ["Concept", "编号"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }, {
      "id": "2677",
      "name": "性别",
      "semantic_type": "性别",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 1,
      "relation_node": {
        "id": "8f2d01129c1011eb892ad31672d12132",
        "name": "男",
        "semantic_type": "性别",
        "labels": ["Concept", "性别"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }, {
      "id": "2649",
      "name": "出生年月",
      "semantic_type": "出生年月",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 1,
      "relation_node": {
        "id": "8e8922369c1011eb892ad31672d12132",
        "name": "1927/8/1",
        "semantic_type": "出生年月",
        "labels": ["Concept", "出生年月"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }, {
      "id": "2672",
      "name": "项目名录",
      "semantic_type": "项目名录",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 1,
      "relation_node": {
        "id": "8f0c21689c1011eb892ad31672d12132",
        "name": "剪纸(乐清细纹刻纸)",
        "semantic_type": "项目名录",
        "labels": ["Concept", "项目名录"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }, {
      "id": "2662",
      "name": "级别",
      "semantic_type": "级别",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 1,
      "relation_node": {
        "id": "8eec11d49c1011eb892ad31672d12132",
        "name": "国家级",
        "semantic_type": "级别",
        "labels": ["Concept", "级别"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }, {
      "id": "2660",
      "name": "门类",
      "semantic_type": "门类",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 1,
      "relation_node": {
        "id": "8ed52bcc9c1011eb892ad31672d12132",
        "name": "传统美术",
        "semantic_type": "门类",
        "labels": ["Concept", "门类"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }, {
      "id": "2636",
      "name": "批次",
      "semantic_type": "批次",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 1,
      "relation_node": {
        "id": "8ebdb2d09c1011eb892ad31672d12132",
        "name": "一",
        "semantic_type": "批次",
        "labels": ["Concept", "批次"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }, {
      "id": "2621",
      "name": "出生地",
      "semantic_type": "出生地",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 1,
      "relation_node": {
        "id": "84417f449c1011eb892ad31672d12132",
        "name": "乐清市",
        "semantic_type": "出生地",
        "labels": ["Concept", "出生地"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }, {
      "id": "2553",
      "name": "公布时间",
      "semantic_type": "公布时间",
      "properties": {
        "scenes": "allinmd",
        "status": 1
      },
      "direction": 1,
      "relation_node": {
        "id": "8ef701fc9c1011eb892ad31672d12132",
        "name": "2007/6/9",
        "semantic_type": "公布时间",
        "labels": ["Concept", "公布时间"],
        "properties": {
          "scenes": "allinmd",
          "status": 1
        }
      }
    }]
  }
}

vue代码

// backendinheritorzstp.vue
<template>
  <div
      class="d3-container">
    <div
        class="types">
      <span
          v-for="(value, key, index) of typeCategories"
          :key="index"
          :style="{backgroundColor: typeColor[key]}"
          @mouseover="handleTypeMouseover(value, key)"
          @mouseout="handleTypeMouseout()">
        {{ key }}({{ value.length }})
      </span>
    </div>
    <div
        class="info">
      {{ info }}
    </div>
    <div
        class="btns">
      <span
          id="zoomIn">
        +
      </span>
      <span
          id="zoomOut">
        -
      </span>
      <span
          id="reset">
        reset
      </span>
    </div>
    <svg />
  </div>
</template>

<script>
import * as d3 from 'd3'
import mock from '@/static/inheritor.json'
export default {
  data() {
    return {
      info: '',
      typeCategories: {},
      typeColor: {}, // 类型配色
      type: ['编号', '性别', '出生年月', '项目名录', '级别', '门类', '批次', '出生地', '公布时间', '姓名'], // 分类
      nodes: [], // 节点集
      links: [], // 关系集
      visibleFlag: false
    }
  },
  watch: {
    nodes: {
      handler(val) {
        // 监听节点变化,设置类型标签
        const obj = {}
        val.forEach(e => {
          if (Object.keys(obj).indexOf('' + e.semantic_type) === -1) {
            obj[e.semantic_type] = []
          }
          obj[e.semantic_type].push(e)
        })
        this.typeCategories = obj
      },
      deep: true
    }
  },
  mounted() {
    this.setTypeColor()
    this.initD3()
  },
  methods: {
    handleTypeMouseout() {
      d3.selectAll('.single-node').style('opacity', 1)
      d3.selectAll('.single-line').style('opacity', 1)
    },
    handleTypeMouseover(data) {
      d3.selectAll('.single-node').style('opacity', 0.1)
      d3.selectAll('.single-line').style('opacity', 0.1)
      for (let i = 0; i < data.length; i++) {
        const nodeID = '#single-node' + data[i].id
        d3.selectAll(nodeID).style('opacity', 1)
      }
    },
    setTypeColor() {
      const obj = {}
      this.type.forEach(e => {
        if (Object.keys(obj).indexOf('' + e) === -1) {
          obj[e] = ''
        }
        obj[e] = this.randomColor()
      })
      this.typeColor = obj
    },
    randomColor() {
      const colors = ['#F4AB87', '#EEC88D', '#76CADF', '#97DA9D', '#88DCD8', '#FB7F89', '#F0E403', '#F576BE', '#ACADFF', '#7EC3FB', '#D0DB02', '#C07B11', '#00ACC2', '#2AAD41', '#A59D00', '#EB4747', '#CD0EBD', '#DE3997']
      return colors[Math.floor(Math.random() * colors.length)]
    },
    initD3() {
      const _this = this
      // 数据示例
      // nodes = [
      //   { id: 'a', name: 'a' },
      //   { id: 'b', name: 'b' },
      //   { id: 'c', name: 'c' }
      // ]
      // links = [
      //   { id: 'ab', source: 'a', target: 'b' },
      //   { id: 'bc', source: 'b', target: 'c' }
      // ]

      // 容器
      const svg = d3.select('svg')
          .attr('viewBox', [-window.innerWidth / 2, -window.innerHeight / 2, window.innerWidth, window.innerHeight])

      // 缩放
      const zoom = d3.zoom()
          .on('zoom', function () {
            svg.attr('transform', d3.zoomTransform(svg.node()))
            // const tran = d3.zoomTransform(svg.node())
            // const _k = tran.k
            // console.log(tran)
            // console.log(Math.floor(_k * 100) / 100)
          })
      svg.call(zoom)

      d3.select('#reset')
          .on('click', function () {
            svg.call(zoom.transform, d3.zoomIdentity)
          })
      d3.select('#zoomIn')
          .on('click', function () {
            zoom.scaleBy(svg, 1.1)
          })
      d3.select('#zoomOut')
          .on('click', function () {
            zoom.scaleBy(svg, 0.9) // 执行该方法后 会触发 zoom 事件 0.9 缩小
          })

      // 新建一个力导向图
      const simulation = d3.forceSimulation()
          .force('charge', d3.forceManyBody().strength(-1000))
          .force('link', d3.forceLink().id(d => d.id).distance(200))
          .force('x', d3.forceX())
          .force('y', d3.forceY())
          .on('tick', ticked)

      // 关系路径
      let link = svg.append('g')
          .attr('class', 'link-container')
          .attr('stroke', '#000')
          .attr('stroke-width', 1)
          .selectAll('line')

      // 关系文字
      let linkText = svg.append('g')
          .attr('class', 'link-text-container')
          .attr('stroke', '#000')
          .attr('stroke-width', 1.5)
          .selectAll('text')

      // 节点
      let node = svg.append('g')
          .attr('class', 'node-container')
          .selectAll('circle')

      function ticked() {
        node
            .attr('transform', function (d) {
              return 'translate(' + d.x + ',' + d.y + ')'
            })

        link.attr('x1', d => d.source.x)
            .attr('y1', d => d.source.y)
            .attr('x2', d => d.target.x)
            .attr('y2', d => d.target.y)

        linkText
            .attr('x', d => (d.source.x + d.target.x) / 2)
            .attr('y', d => (d.source.y + d.target.y) / 2)
      }

      // 更新
      const updateObj = Object.assign(svg.node(), {
        update({ nodes, links }) {
          // 做一个浅复制,以防止突变,回收旧节点以保持位置和速度
          const old = new Map(node.data().map(d => [d.id, d]))
          nodes = nodes.map(d => Object.assign(old.get(d.id) || {}, d))
          links = links.map(d => Object.assign({}, d))

          // 节点
          node = node
              .data(nodes, d => d.id)
              .join(
                  enter =>
                      enter.append('g')
                          .attr('class', 'single-node')
                          .attr('id', (d) => {
                            return 'single-node' + d.id
                          })
              )
              .call(d3.drag()
                  .on('start', dragstarted)
                  .on('drag', dragged)
                  .on('end', dragended))
          d3.selectAll('.single-node')
              .append('circle')
              .attr('r', 30)
              .attr('fill', nodeColor)
              .style('cursor', 'pointer')
          // 节点文字
          d3.selectAll('.single-node')
              .append('text')
              .attr('y', 0)
              .attr('dy', 5)
              .attr('text-anchor', 'middle')
              .style('cursor', 'pointer')
              .attr('x', function (d) {
                return textBreaking(d3.select(this), d, false)
              })

          // 绘制箭头
          svg.append('g')
              .attr('class', 'arrow-marker')
              .append('marker')
              .attr('id', 'arrow-marker')
              .attr('markerUnits', 'strokeWidth') // 设置为 strokeWidth 箭头会随着线的粗细发生变化
              .attr('markerUnits', 'userSpaceOnUse')
              .attr('viewBox', '0 -5 10 10') // 坐标系的区域
              .attr('refX', 40) // 箭头坐标
              .attr('refY', 0)
              .attr('markerWidth', 10) // 标识的大小
              .attr('markerHeight', 10)
              .attr('orient', 'auto') // 绘制方向,可设定为:auto(自动确认方向)和 角度值
              .attr('stroke-width', 2) // 箭头宽度
              .append('path')
              .attr('d', 'M0,-5L10,0L0,5') // 箭头的路径
              .attr('fill', '#000') // 箭头颜色

          // 关系路径
          link = link
              .data(links, d => [d.source, d.target])
              .join(
                  enter =>
                      enter.append('line')
                          .attr('class', 'single-line')
                          .attr('id', (d) => {
                            return 'single-line' + d.id
                          })
                          .attr('marker-end', 'url(#arrow-marker)') // 根据箭头标记的 id 号标记箭头
              )

          // 路径文字
          linkText = linkText
              .data(links, d => [d.source, d.target])
              .join(
                  enter =>
                      enter.append('text')
                          .attr('class', 'link-text')
                          .attr('id', (d) => {
                            return 'link-text' + d.id
                          })
                          .text((d) => {
                            return d.semantic_type
                          })
                          .attr('stroke', '#000')
                          .attr('stroke-width', '1')
                          .attr('fill', 'none')
                          .style('cursor', 'pointer')
              )

          simulation.nodes(nodes)
          simulation.force('link').links(links)
          simulation.alpha(1).restart()

          node
              .on('click', function (d) {
                _this.visibleFlag = !_this.visibleFlag
                toggleMenu(d3.select(this), d, _this.visibleFlag)
              })
              .on('mouseover', function (d) {
                // 鼠标移入节点,高亮当前节点及与当前节点有关系的路径和节点
                d3.selectAll('.single-node').style('opacity', 0.2)
                d3.selectAll('.single-line').style('opacity', 0.2)
                d3.selectAll('.link-text').style('opacity', 0.2)
                d3.select('#single-node' + d.id).style('opacity', 1)
                const relationLinks = []
                _this.links.forEach((item) => {
                  if (item.source === d.id || item.target === d.id) {
                    relationLinks.push(item)
                  }
                })
                relationLinks.forEach((item) => {
                  d3.select('#single-line' + item.id).style('opacity', 1)
                  d3.select('#link-text' + item.id).style('opacity', 1)
                  d3.select('#single-node' + item.source).style('opacity', 1)
                  d3.select('#single-node' + item.target).style('opacity', 1)
                })
                _this.info = JSON.stringify(d)
              })
              .on('mouseout', function () {
                d3.selectAll('.single-node').style('opacity', 1)
                d3.selectAll('.single-line').style('opacity', 1)
                d3.selectAll('.link-text').style('opacity', 1)
                _this.info = ''
              })

          link
              .on('mouseover', function (d) {
                d3.selectAll('.single-node').style('opacity', 0.2)
                d3.selectAll('.single-line').style('opacity', 0.2)
                d3.selectAll('.link-text').style('opacity', 0.2)
                d3.select('#single-line' + d.id).style('opacity', 1)
                d3.select('#link-text' + d.id).style('opacity', 1)
                d3.select('#single-node' + d.source.id).style('opacity', 1)
                d3.select('#single-node' + d.target.id).style('opacity', 1)
                _this.info = JSON.stringify(d)
              })
              .on('mouseout', function () {
                d3.selectAll('.single-node').style('opacity', 1)
                d3.selectAll('.single-line').style('opacity', 1)
                d3.selectAll('.link-text').style('opacity', 1)
                _this.info = ''
              })
        }
      })

      /**
       * @name: 设置节点颜色
       * @param {*} node
       */
      function nodeColor(node) {
        const type = node.semantic_type
        if (_this.typeColor[type]) {
          return _this.typeColor[type]
        } else {
          return '#ddd'
        }
      }

      /**
       * @name: 新增节点和关系
       * @param {*} node
       */
      function addNodesAndLinks(node) {
        // 模拟接口返回节点和关系数据
        _this.$nextTick(() => {
          const res = mock
          const resData = res.data.relations
          const edgeResult = []
          for (let i = 0; i < resData.length; i++) {
            edgeResult[i] = {
              id: resData[i].id,
              source: resData[i].direction === 0 ? node.id : resData[i].relation_node.id,
              target: resData[i].direction === 0 ? resData[i].relation_node.id : node.id,
              relation: resData[i].name,
              name: resData[i].name,
              properties: resData[i].properties,
              semantic_type: resData[i].semantic_type
            }
          }
          const nodeReault = resData.map(_ => _.relation_node)
          nodeReault.forEach((item) => {
            _this.nodes.push(item)
          })
          _this.nodes.push({
            'id': 'qwertyuiop',
            'name': '测试无关系节点',
            'semantic_type': 'Symptom',
            'labels': ['Concept', 'Symptom'],
            'properties': {
              'scenes': 'allinmd',
              'status': 1,
              'lastModified': 1618293198,
              'releaseDate': 1618293198
            }
          })
          edgeResult.forEach((item) => {
            _this.links.push(item)
          })
          updateObj.update({
            nodes: _this.nodes,
            links: _this.links
          })
        })
      }

      /**
       * @name: 关联节点去重重组
       * @param {*} objarray
       */
      function uniqObjInArray(objarray) {
        const len = objarray.length
        const tempJson = {}
        const res = []
        for (let i = 0; i < len; i++) {
          // 取出每一个对象
          tempJson[JSON.stringify(objarray[i])] = true
        }
        const keyItems = Object.keys(tempJson)
        for (let j = 0; j < keyItems.length; j++) {
          res.push(JSON.parse(keyItems[j]))
        }
        return res
      }

      /**
       * @name: 收起,删除当前节点下一级没有其他关系的节点
       * @param {*} node
       */
      function deleteNextNodes(node) {
        const relationNode = []
        const relationList = []
        const hasRelationList = []
        d3.selectAll('.single-line').each(function (e) {
          if (e.source.id === node.id) {
            hasRelationList.push(e)
          } else {
            relationList.push(e) // 删除节点有关系的其他关系
          }
          // 需要删除的节点相关的节点
          if (e.source.id === node.id) {
            relationNode.push(e.target)
          }
          if (e.target.id === node.id) {
            relationNode.push(e.source)
          }
        })
        let tempNodeList = JSON.parse(JSON.stringify(relationNode))
        tempNodeList = uniqObjInArray(tempNodeList)
        // 区分下级节点是否是孤节点,如果还有其他关系,则不能删除
        tempNodeList.forEach(function (item) {
          const hasLine = relationList.findIndex(jtem => jtem.target.id === item.id || jtem.source.id === item.id)
          if (hasLine >= 0) {
            item.notSingle = true
          }
        })
        tempNodeList.forEach(function (item) {
          if (!item.notSingle) {
            d3.select('#single-node' + item.id).remove()
          }
        })
        const otherTempNode = []
        tempNodeList = tempNodeList.map(item => {
          if (!item.notSingle) {
            otherTempNode.push(item)
          }
        })
        hasRelationList.forEach(item => {
          otherTempNode.forEach(jtem => {
            if (jtem.id === item.source.id || jtem.id === item.target.id) {
              d3.select('#single-line' + item.id).remove()
              d3.select('#link-text' + item.id).remove()
            }
          })
        })
        d3.select('.menu-circle').remove()
      }

      /**
       * @name: 隐藏,删除当前及下一级没有其他关系的节点
       * @param {*} node
       */
      function deleteNodeAndLinks(node) {
        const removeIndex = _this.nodes.findIndex(data => data.id === node.id)
        _this.nodes.splice(removeIndex, 1)
        const relationNode = []
        const relationList = []
        const clickNode = node.id
        d3.selectAll('.single-line').each(function (e) {
          if (e.source.id === node.id || e.target.id === node.id) {
            d3.select(this).remove()
          } else {
            relationList.push(e)
          }
          // 需要删除的节点相关的节点
          if (e.source.id === node.id) {
            relationNode.push(e.target)
          }
        })
        let tempNodeList = JSON.parse(JSON.stringify(relationNode))
        tempNodeList = uniqObjInArray(tempNodeList)
        // 区分下级节点是否是孤节点
        tempNodeList.forEach(function (item) {
          const hasLine = relationList.findIndex(jtem => jtem.target.id === item.id || jtem.source.id === item.id)
          if (hasLine >= 0) {
            item.notSingle = true
          }
        })
        tempNodeList.forEach(function (item) {
          if (!item.notSingle) {
            d3.select('#single-node' + item.id).remove()
          }
        })
        d3.selectAll('.single-node').each(function (d) {
          const temp = d.id
          // 删除当前需要隐藏的节点
          if (temp === clickNode) {
            d3.select('.menu-circle').remove()
            d3.select(this).remove()
          }
        })
        d3.selectAll('.link-text').each(function (e) {
          if (e.source === node || e.target === node) {
            d3.select(this).remove()
          }
        })
      }

      /**
       * @name: 生成操作菜单
       * @param {*} current 当前元素
       * @param {*} d 当前元素对应的数据
       * @param {*} flag 显隐
       */
      function toggleMenu(current, d, flag) {
        const currentD = d
        const data = [{
          population: 30,
          value: '隐藏',
          type: 'delete'
        }, {
          population: 30,
          value: '收起',
          type: 'showOn'
        }, {
          population: 30,
          value: '展开',
          type: 'showOff'
        }]
        // 创建一个环生成器
        const arc = d3.arc()
            .innerRadius(80) // 内半径
            .outerRadius(35) // 外半径
        const pie = d3.pie()
            .value(function (d) {
              return d.population
            })
            .sort(null)
        const pieData = pie(data)
        const pieAngle = pieData.map(function (p) {
          return (p.startAngle + p.endAngle) / 2 / Math.PI * 180
        })
        // 菜单容器
        const g = current
            .append('g')
            .attr('class', 'menu-circle')
            .attr('width', 100)
            .attr('height', 100)
        const Pie = g.append('g')
        Pie.selectAll('path')
            .data(pie(data))
            .enter()
            .append('path')
            .attr('d', arc)
            .attr('fill', '#d3d7dc')
            .style('stroke', '#fff')
            .style('cursor', 'pointer')
            .on('click', function (d) {
              if (d.data.type === 'delete') {
                deleteNodeAndLinks(currentD)
              } else if (d.data.type === 'showOn') {
                deleteNextNodes(currentD)
              } else {
                addNodesAndLinks(currentD)
              }
              d3.event.stopPropagation()
            })
            .on('mouseover', function () {
              d3.select(this)
                  .style('fill', '#d3d7dc')
                  .transition()
                  .style('fill', '#aaaeb4')
            })
            .on('mouseout', function () {
              d3.select(this)
                  .style('fill', '#aaaeb4')
                  .transition()
                  .style('fill', '#d3d7dc')
            })

        // 安妮文字
        const labelFontSize = 12
        const labelValRadius = (170 * 0.35 - labelFontSize * 0.35)
        const labelValRadius1 = (170 * 0.35 + labelFontSize * 0.35)
        const labelsVals = current
            .select('.menu-circle')
            .append('g')
            .classed('labelsvals', true)
        // 定义两条路径以使标签的方向正确
        labelsVals.append('def')
            .append('path')
            .attr('id', 'label-path-1')
            .attr('d', `m0 ${-labelValRadius} a${labelValRadius} ${labelValRadius} 0 1,1 -0.01 0`)
        labelsVals.append('def')
            .append('path')
            .attr('id', 'label-path-2')
            .attr('d', `m0 ${-labelValRadius1} a${labelValRadius1} ${labelValRadius1} 0 1,0 0.01 0`)
        labelsVals.selectAll('text')
            .data(data)
            .enter()
            .append('text')
            .attr('dy', function (d) {
              if (d.type === 'showOn') {
                return -5
              } else {
                return 5
              }
            })
            .style('font-size', labelFontSize)
            .style('fill', 'black')
            .style('font-weight', 'bold')
            .style('text-anchor', 'middle')
            .append('textPath')
            .style('cursor', 'pointer')
            .attr('href', function (d, i) {
              const angle = pieAngle[i]
              if (angle > 90 && angle <= 270) { // 根据角度选择路径
                return '#label-path-2'
              } else {
                return '#label-path-1'
              }
            })
            .attr('startOffset', function (d, i) {
              const p = pieData[i]
              const angle = pieAngle[i]
              const percent = (p.startAngle + p.endAngle) / 2 / 2 / Math.PI * 100
              if (angle > 90 && angle <= 270) { // 分别计算每条路径的正确百分比
                return 100 - percent + '%'
              }
              return percent + '%'
            })
            .text(function (d) {
              return d.value
            })
            .on('click', function (d) {
              if (d.type === 'delete') {
                deleteNodeAndLinks(currentD)
              } else if (d.type === 'showOn') {
                deleteNextNodes(currentD)
              } else {
                addNodesAndLinks(currentD)
              }
              d3.event.stopPropagation()
            }, true)
        if (flag === false) {
          d3.selectAll('.menu-circle').remove()
        }
      }

      /**
       * @name: 节点文字换行
       * @param {*} dom
       * @param {*} data
       * @param {*} breaking 是否换行
       */
      function textBreaking(dom, data, breaking) {
        const text = data.name
        if (breaking) {
          const len = text.length
          if (len <= 3) {
            dom.append('tspan')
                .attr('x', 0)
                .attr('y', 0)
                .text(text)
          } else {
            const topText = text.substring(0, 3)
            const midText = text.substring(3, 7)
            let botText = text.substring(7, len)
            let topY = -22
            let midY = 8
            const botY = 34
            if (len <= 9) {
              topY += 10
              midY += 10
            } else {
              botText = text.substring(7, 9) + '...'
            }
            dom.text('')
            dom.append('tspan')
                .attr('x', 0)
                .attr('y', topY)
                .text(function () {
                  return topText
                })
            dom.append('tspan')
                .attr('x', 0)
                .attr('y', midY)
                .text(function () {
                  return midText
                })
            dom.append('tspan')
                .attr('x', 0)
                .attr('y', botY - 7)
                .text(function () {
                  return botText
                })
          }
        } else {
          dom.append('tspan')
              .attr('x', 0)
              .attr('y', 0)
              .style('font-size', 12)
              .style('stroke', '#333')
              .text(data.name)
        }
      }

      /**
       * @name: 拖动
       * @param {*} event
       */
      function dragstarted(event) {
        if (!d3.event.active) {
          simulation.alphaTarget(0.8).restart() // 设置衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0, 1]
        }
        event.fx = event.x
        event.fy = event.y
      }
      function dragged(event) {
        event.fx = d3.event.x
        event.fy = d3.event.y
      }
      function dragended(event) {
        if (!d3.event.active) {
          simulation.alphaTarget(0)
        }
        event.fx = null
        event.fy = null
      }

      // updateObj.update({
      //   nodes: _this.nodes,
      //   links: _this.links
      // })

      // 模拟接口返回节点信息
      _this.$nextTick(() => {
        // 初次返回单个节点
        const res = {
          'code': 0,
          'message': '',
          'data': {
            'id': '83c8aeb69c1011eb892ad31672d12132',
            'name': '林邦栋',
            'semantic_type': '姓名',
            'labels': ['Concept', '姓名'],
            'properties': {
              'scenes': 'allinmd',
              'status': 1,
              'lastModified': 1618293198,
              'releaseDate': 1618293198
            }
          }
        }
        const data = res.data
        _this.nodes.push(data)
        updateObj.update({
          nodes: _this.nodes,
          links: _this.links
        })
      })
    }
  }
}
</script>

<style>
.d3-container {
  position: relative;
}
.d3-container .info {
  background: #fff;
  position: absolute;
  left: 50px;
  bottom: 50px;
  z-index: 9;
}
.d3-container .btns {
  background: #fff;
  position: absolute;
  right: 50px;
  bottom: 200px;
  z-index: 99;
}
.d3-container .btns span {
  cursor: pointer;
}
.d3-container .types {
  position: absolute;
  left: 50px;
  top: 50px;
  z-index: 9;
}
.d3-container .types span {
  display: inline-block;
  background: #a5abb6;
  border-radius: 4px;
  margin-right: 10px;
  padding: 5px 6px;
  cursor: pointer;
  color: #2e0f00;
  font-size: 12px;
}
</style>



  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值