D3.js实现知识图谱(删除,隐藏,展开)

D3.js实现知识图谱(隐藏/删除/展开

前段时间接到做知识图谱的需求,大部分功能需要和neo4j上面的示例一致,再加一些个性化的东西,找到中文文档,但是版本比较老,有很多东西都弃用了,没有太多参考价值,我用的是5.0版本,找了很多资料才大体完成0.0
下面是代码,给刚接触的同学一点参考,有问题的地方还请大家不吝赐教
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    .link-active {
      stroke-opacity: 1;
      stroke-width: 3;
    }
  </style>
  <script src="http://d3js.org/d3.v5.min.js"></script>
</head>

<body>
  <div style="border:1px solid #000;position: relative;">
    <svg width="960" height="500"></svg>
    <div
      style="width:100px;height:100px;border:1px solid red;position:absolute;top:2px;right:2px;background-color: azure;">
      <span id="xxx"></span>
    </div>
  </div>
</body>
<script type="text/javascript">

  let marge = { top: 60, bottom: 60, left: 60, right: 60 }
  let svg = d3.select('svg')
  let width = svg.attr('width')
  let height = svg.attr('height')
  svg.call(
    d3.zoom().on('zoom', function () {
      g.attr('transform', d3.event.transform)
    })
  )
    .on('dblclick.zoom', null)
  let g = svg.append('g')
    .attr('transform', 'translate(' + marge.top + ',' + marge.left + ')')
    .attr('class', 'container')
  // 准备数据
  // 节点集
  let nodes = [
    { id: 12, name: '湖南邵阳' },
    { id: 2, name: '山东泰安' },
    { id: 3, name: '广东阳江不知道怎么回' },
    { id: 4, name: '山西太原' },
    { id: 5, name: '亮' },
    { id: 6, name: '丽' },
    { id: 7, name: '雪' },
    { id: 8, name: '小明' },
    { id: 9, name: '组长' }
  ]
  // 边集
  let tempEdges = [
    { id: 1, source: 12, target: 5, relation: '籍贯', value: 1.3 },
    { id: 2, source: 5, target: 6, relation: '舍友', value: 1 },
    { id: 3, source: 5, target: 7, relation: '舍友', value: 1 },
    { id: 4, source: 5, target: 8, relation: '舍友', value: 1 },
    { id: 5, source: 2, target: 7, relation: '籍贯', value: 2 },
    { id: 6, source: 3, target: 6, relation: '籍贯', value: 0.9 },
    { id: 7, source: 4, target: 8, relation: '籍贯', value: 1 },
    { id: 8, source: 6, target: 7, relation: '同学', value: 1.6 },
    { id: 9, source: 7, target: 8, relation: '朋友', value: 0.7 },
    { id: 10, source: 7, target: 9, relation: '职责', value: 2 },
    { id: 11, source: 9, target: 7, relation: '人物', value: 2 },
    { id: 12, source: 9, target: 7, relation: '哈哈哈', value: 2 }
  ]
  nodes.forEach(item => {

  })
  // 生成 nodes map
  let nodesMap = genNodesMap(nodes);
  console.log('3333',nodesMap)
  nodesData = d3.values(nodesMap)
  let linkMap = genLinkMap(tempEdges)
  // 构建 links(source 属性必须从 0 开始)
  edges = genLinks(tempEdges);
  console.log('123123',edges,nodesData)
  // 设置一个颜色比例尺
  let colorScale = d3.scaleOrdinal()
    .domain(d3.range(nodesData.length))
    .range(d3.schemeCategory10)
  // 新建一个力导向图
  let forceSimulation = d3.forceSimulation()
    .force('link', d3.forceLink())
    .force('charge', d3.forceManyBody())
    .force('center', d3.forceCenter())
  // 生成节点数据
  forceSimulation.nodes(nodesData)
  // 生成边数据
  forceSimulation.force('link')
    .links(edges)
    .distance(function (d) { // 每一边的长度
      return d.value * 200
    })
  // 设置图形中心位置
  forceSimulation.force('center')
    .x(width / 2)
    .y(height / 2)
  // 箭头
  var marker = g.append('g').attr('class', 'showLine').append('marker')
    .attr('id', 'resolved')
    // .attr("markerUnits","strokeWidth")// 设置为strokeWidth箭头会随着线的粗细发生变化
    .attr('markerUnits', 'userSpaceOnUse')
    .attr('viewBox', '0 -5 10 10')// 坐标系的区域
    .attr('refX', 44)// 箭头坐标
    .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', '#000000')// 箭头颜色
  // 绘制边
  let links = g.append('g').selectAll('path')
    .data(edges)
    .enter()
    .append('path')
    .attr('d', link => genLinkPath(link)) // 遍历所有数据。d表示当前遍历到的数据,返回绘制的贝塞尔曲线
    .attr('id', (d, i) => { return 'edgepath' + d.id }) // 设置id,用于连线文字
    .style('stroke', '#000') // 颜色
    .style('stroke-width', 2) // 粗细
    .attr('class', 'lines')
    .attr('marker-end', 'url(#resolved)') // 根据箭头标记的id号标记箭头
  // 边上的文字
  let linksText = g.append('g')
    .selectAll('text')
    .data(edges)
    .enter()
    .append('text')
    .attr('class', 'linksText')
    .text(function (d) {
      return d.relations
    })
    .style('font-size', 14)
    .attr('fill-opacity', 0)
  // 创建分组
  let gs = g.append('g')
    .selectAll('.circleText')
    .data(nodesData)
    .enter()
    .append('g')
    .attr('class', 'singleNode')
    .attr('id', function (d) {
      return 'singleNode' + d.id
    })
    .style('cursor', 'pointer')
    .attr('transform', function (d) {
      let cirX = d.x
      let cirY = d.y
      return 'translate(' + cirX + ',' + cirY + ')'
    })
  // 鼠标交互
  gs.on('mouseover', function (d, i) {
    // 显示连接线上的文字
    toggleLineText(d, true)
    toggleLine(links, d, true)
    toggleNode(gs, d, true)
  })
    .on('mouseout', function (d, i) {
      // 隐去连接线上的文字
      toggleLineText(d, false)
      toggleLine(links, d, false)
      toggleNode(gs, d, false)
    })
    .on('click', function (d, i) {
      linksText.style('fill-opacity', function (edge) {
        if (edge.source === d) {
          return 1
        }
      })
      toggleCircle(d3.select(this), d)
    }, true)
    .call(d3.drag()
      .on('start', started)
      .on('drag', dragged)
      .on('end', ended)
    )
  svg.on('click', function () {
    nodes.forEach(d => d.clickFlag = false)
    var event = d3.event
    var target = event.srcElement,   //  获取事件发生源
      data = d3.select(target).datum(); //  获取事件发生源的数据
    removeSingle()
    if (!data) {
      document.getElementById('xxx').innerText = ''
    }
  }, true)
  forceSimulation.on('tick', ticked)
  function toggleLineText(currNode, isHover) {
    if (isHover) {
      linksText.style('fill-opacity', function (edge) {
        if (edge.source === currNode) {
          return 1
        }
      })
    } else {
      linksText.style('fill-opacity', function (edge) {
        if (edge.source === currNode || edge.target === currNode) {
          return 0
        }
      })
    }
  }
  function toggleLine(linkLine, currNode, isHover) {
    if (isHover) {
      // 加重连线样式
      links
        .style('opacity', 0.1)
        .filter(link => isLinkLine(currNode, link))
        .style('opacity', 1)
        .classed('link-active', true)
    } else {
      links
        .style('opacity', 1)
        .classed('link-active', false)
    }
  }
  function showMyList() {
    var e = { id: 10, name: "河北" };
    var h = { id: 11, name: "河南" };
    var f = { id: 13, source: 9, target: 10, relation: '222', value: 2 };
    nodes.push(e);
    nodes.push(h);
    tempEdges.push(f);
    nodesMap = genNodesMap(nodes);
    nodesData = d3.values(nodesMap)
    linkMap = genLinkMap(tempEdges)
    edges = genLinks(tempEdges)
    updateData()
  }
  function updateData() {
    links = links
      .data(edges, function (d) {
      })
      .join("path")
      .attr('id', (d, i) => { return 'edgepath' + d.id }) // 设置id,用于连线文字
      .style('stroke', '#000') // 颜色
      .style('stroke-width', 2) // 粗细
      .attr('class', 'lines')
      .attr('marker-end', 'url(#resolved)') // 根据箭头标记的id号标记箭头
      .merge(links);
    linksText = linksText
      .data(edges)
      .join('text')
      .attr('class', 'linksText')
      .text(function (d) {
        return d.relations
      })
      .style('font-size', 14)
      .attr('fill-opacity', 0)
    gs = gs
      .data(nodesData, function (d) {
      })
      .join("g")
      .attr('class', 'singleNode')
      .attr('id', function (d) {
        return 'singleNode' + d.id
      })
      .style('cursor', 'pointer')
      .merge(gs)
      .call(d3.drag()
        .on("start", started)
        .on("drag", dragged)
        .on("end", ended));
    gs.append('circle')
      .attr('fill', function (d) {
        return 'orange'
      })
      .attr('r', 35)
      .attr('stroke', 'grey')
      .attr('stroke-width', 3)
    gs.on('mouseover', function (d, i) {
      // 显示连接线上的文字
      toggleLineText(d, true)
      toggleLine(links, d, true)
      toggleNode(gs, d, true)
    })
      .on('mouseout', function (d, i) {
        // 隐去连接线上的文字
        toggleLineText(d, false)
        toggleLine(links, d, false)
        toggleNode(gs, d, false)
      })
      .on('click', function (d, i) {
        linksText.style('fill-opacity', function (edge) {
          if (edge.source === d) {
            return 1
          }
        })
        toggleCircle(d3.select(this), d)
      }, true)
    gs.append('text')
      .attr('y', -20)
      .attr('dy', 10)
      .attr('text-anchor', 'middle')
      .style('font-size', 12)
      .attr('x', function ({ name }) {
        return textBreaking(d3.select(this), name)
      })
    gs.append('title')
      .text((node) => {
        return node.name
      })
    forceSimulation.nodes(nodesData);
    forceSimulation.force("link").links(edges);
    forceSimulation.alpha(0.8).restart();
  }
  function getLineTextAngle(d, bbox) {
    if (d.target.x < d.source.x) {
      const {
        x,
        y,
        width,
        height
      } = bbox;
      const rx = x + width / 2;
      const ry = y + height / 2;
      return 'rotate(180 ' + rx + ' ' + ry + ')';
    } else {
      return 'rotate(0)';
    }
  }
  function toggleNode(nodeCircle, currNode, isHover) {
    if (isHover) {
      // 提升节点层级 
      // nodeCircle.sort((a, b) => a.id === currNode.id ? 1 : -1);
      // this.parentNode.appendChild(this);
      nodeCircle
        .style('opacity', .1)
        .filter(node => isLinkNode(currNode, node))
        .style('opacity', 1);

    } else {
      nodeCircle.style('opacity', 1);
    }

  }
  function removeSingle() {
    d3.select('.singleCircle').remove()
  }
  function toggleCircle(current, d) {
    var currentD = d
    if (d.clickFlag) {
      removeSingle()
      document.getElementById('xxx').innerText = ''
    }
    d.clickFlag = true
    document.getElementById('xxx').innerText = d.name
    var data = [{
      population: 30,
      value: 'X',
      type: 'delete'
    }, {
      population: 30,
      value: '收起',
      type: 'showOn'
    }, {
      population: 30,
      value: '展开',
      type: 'showOff'
    }]
    var sum = d3.sum(data.map(function (d) {
      return d.population
    }))
    for (i in data) {
      data[i].Percentage = (data[i].population / sum * 100).toFixed(0) + "%";
    }
    var width = 300,
      height = 300,
      margin = { "left": 30, "top": 30, "right": 30, "bottom": 30 },
      svg_width = width + margin.left + margin.right,
      svg_height = height + margin.top + margin.bottom,
      font_size = 15;
    var g = current
      .append("g")
      .attr('class', 'singleCircle')
      .attr("width", width)
      .attr("height", height)
    var Pie = g.append("g")

    var arc_generator = d3.arc()
      .innerRadius(width / 6.5)
      .outerRadius(width / 4)
    var angle_data = d3.pie()
      .value(function (d) {
        return d.population;
      })
    var pieData = angle_data(data)
    var pieAngle = pieData.map(function (p) {
      return (p.startAngle + p.endAngle) / 2 / Math.PI * 180;
    });

    // var color=d3.schemeCategory10;

    //生成内部圆环
    Pie.selectAll("path")
      .data(angle_data(data))
      .enter()
      .append("path")
      .attr("d", arc_generator)
      .style("fill", function (d, i) {
        return 'grey';
      })
      .style('stroke', 'black')
      .attr("class", "path")
      .attr('type', function (d) {
        return d.data.type
      })
      .on('click', function (d) {
        if (d.data.type === 'delete') {
          deleteNode(currentD)
        } else if (d.data.type === 'showOn') {
          deleteNextNodes(currentD)
        } else {
          // showMyList()
        }
        d3.event.stopPropagation()
      })
    var arc_label = d3.arc()
      .innerRadius(width / 4)
      .outerRadius(width / 2)

    Pie.selectAll(".arc_label")
      .data(angle_data(data))
      .enter()
      .append("path")
      .attr("d", arc_label)
      .attr("class", "arc_label")
      .style("fill", "none")
    const labelFontSize = 12;
    const labelValRadius = (170 * 0.35 - labelFontSize * 0.35); // 计算正确半径 文字位置
    const labelValRadius1 = (170 * 0.35 + labelFontSize * 0.35);


    const labelsVals = current.select('.singleCircle').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')
      .style('font-size', labelFontSize)
      .style('fill', 'black')
      .style('font-weight', "bold")
      .style('text-anchor', 'middle')
      .append('textPath')
      .attr('href', function (d, i) {
        const p = pieData[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];
        let 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') {
          deleteNode(currentD)
        } else if (d.type === 'showOn') {
          deleteNextNodes(currentD)
        } else {
          // showMyList()
        }
        d3.event.stopPropagation()
      }, true)
  }
  //删除当前节点下一级没有其他关系的节点
  function deleteNextNodes(curr) {
    document.getElementById('xxx').innerText = '';
    // var removeIndex = nodesData.findIndex(node=>node.id == curr.id)
    // nodesData.splice(removeIndex,1)
    // nodes.splice(removeIndex,1)
    d3.select(this).remove();
    let relationNode = [],
      relationList = [],
      hasRelationList = []
    var clickNode = curr.id;//点击节点的名字
    d3.selectAll(".lines").each(function (e) {
      if (e.source.id == curr.id || e.target.id == curr.id) {
        hasRelationList.push(e)
      } else {
        relationList.push(e)//出去跟删除节点有关系的其他关系
      }
      //需要删除的节点相关的节点
      if (e.source.id == curr.id) {
        relationNode.push(e.target)
      }
      //需要删除的节点相关的节点
      if (e.target.id == curr.id) {
        relationNode.push(e.source)
      }
    })
    var tempNodeList = JSON.parse(JSON.stringify(relationNode))
    tempNodeList = uniqObjInArray(tempNodeList)
    //区分下级节点是否是孤节点
    tempNodeList.forEach(function (item, index) {
      var hasLine = relationList.findIndex(jtem => jtem.target.id == item.id || jtem.source.id == item.id)
      if (hasLine >= 0) {
        item.notSingle = true
      }
    })
    tempNodeList.forEach(function (item, index) {
      if (!item.notSingle) {
        d3.select('#singleNode' + item.id).remove()
      }
    })
    var 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('#edgepath' + item.id).remove()
        }
      })
    })
    d3.selectAll(".singleNode").each(function (d, i) {
      var temp = d.id;
      //删除当前需要隐藏的节点
      if (temp == clickNode) {
        removeSingle()
      }
    });
    d3.selectAll(".linksText").each(function (e) {
      if (e.source === curr || e.target === curr) {
        d3.select(this).remove();
      }
    })
    gs.style('opacity', 1);
    links.style('opacity', 1)
      .classed('link-active', false);
  }
  //删除当前及下一级没有其他关系的节点
  function deleteNode(curr) {
    document.getElementById('xxx').innerText = '';
    var removeIndex = nodesData.findIndex(node => node.id == curr.id)
    nodesData.splice(removeIndex, 1)
    nodes.splice(removeIndex, 1)
    d3.select(this).remove();
    let relationNode = [],
      relationList = []
    var clickNode = curr.id;//点击节点的名字
    d3.selectAll(".lines").each(function (e) {
      if (e.source.id == curr.id || e.target.id == curr.id) {
        d3.select(this).remove();
      } else {
        relationList.push(e)//出去跟删除节点有关系的其他关系
      }
      //需要删除的节点相关的节点
      if (e.source.id == curr.id) {
        relationNode.push(e.target)
      }
      //需要删除的节点相关的节点
      if (e.target.id == curr.id) {
        relationNode.push(e.source)
      }
    })
    var tempNodeList = JSON.parse(JSON.stringify(relationNode))
    tempNodeList = uniqObjInArray(tempNodeList)
    //区分下级节点是否是孤节点
    tempNodeList.forEach(function (item, index) {
      var hasLine = relationList.findIndex(jtem => jtem.target.id == item.id || jtem.source.id == item.id)
      if (hasLine >= 0) {
        item.notSingle = true
      }
    })
    tempNodeList.forEach(function (item, index) {
      if (!item.notSingle) {
        d3.select('#singleNode' + item.id).remove()
      }
    })
    d3.selectAll(".singleNode").each(function (d, i) {
      var temp = d.id;
      //删除当前需要隐藏的节点
      if (temp == clickNode) {
        removeSingle()
        d3.select(this).remove();
      }
    });
    d3.selectAll(".linksText").each(function (e) {
      if (e.source === curr || e.target === curr) {
        d3.select(this).remove();
      }
    })
    gs.style('opacity', 1);
    links.style('opacity', 1)
      .classed('link-active', false);
  }
  // 关联节点去重重组
  function uniqObjInArray(objarray) {
    let len = objarray.length;
    let tempJson = {

    };
    let res = [];
    for (let i = 0; i < len; i++) {
      //取出每一个对象
      tempJson[JSON.stringify(objarray[i])] = true;
    }
    let keyItems = Object.keys(tempJson);
    for (let j = 0; j < keyItems.length; j++) {
      res.push(JSON.parse(keyItems[j]));
    }
    return res;
  }
  function isLinkLine(node, link) {
    return link.source.id === node.id
  }
  function isLinkNode(currNode, node) {
    if (currNode.id === node.id) {
      return true;
    }
    return linkMap[currNode.id + '-' + node.id] || linkMap[node.id + '-' + currNode.id];
  }
  function largerNode(nodes, currNode, isHover) {
    if (isHover) {
      gs
        .style('stroke-width', 1)
        .filter(node => isNode(currNode, node))
        .style('stroke-width', 10)
    } else {
      gs
        .style('stroke-width', 1)
    }
  }
  function isNode(node, cNode) {
    return true
  }
  // 绘制节点
  gs.append('circle')
    .attr('r', 35)
    .attr('id', function (d) {
      return 'circle' + d.id
    })
    .attr('fill', function (d, i) {
      return 'orange'
    })
    .attr('stroke', 'grey')
    .attr('stroke-width', 3)
  // 文字
  var nodeText = gs.append('text')
    // .attr('x', -10)
    .attr('y', -20)
    .attr('dy', 10)
    .attr('text-anchor', 'middle')
    .style('font-size', 12)
    .attr('x', function ({ name }) {
      return textBreaking(d3.select(this), name)
    })
  gs.append('title')
    .text((node) => {
      return node.name
    })
  function genLinkMap(relations) {
    const hash = {};
    relations.map(function ({
      source,
      target,
      relation
    }) {
      const key = source + '-' + target;
      if (hash[key]) {
        hash[key] += 1;
        hash[key + '-relation'] += '、' + relation;
      } else {
        hash[key] = 1;
        hash[key + '-relation'] = relation;
      }
    });
    return hash;
  }
  function genLinks(relations) {
    const indexHash = {};
    return relations.map(function ({
      id,
      source,
      target,
      relation,
      value
    }, i) {
      const linkKey = source + '-' + target;
      const count = linkMap[linkKey];
      if (indexHash[linkKey]) {
        indexHash[linkKey] -= 1;
      } else {
        indexHash[linkKey] = count - 1;
      }
      return {
        id,
        source: nodesMap[source],
        target: nodesMap[target],
        relation,
        value,
        relations: linkMap[linkKey + '-relation'],
        count: linkMap[linkKey],
        index: indexHash[linkKey]
      }
    })
  }
  // 生成关系连线路径
  function genLinkPath(link) {
    const count = link.count;
    const index = link.index;
    let sx = link.source.x;
    let tx = link.target.x;
    let sy = link.source.y;
    let ty = link.target.y;
    return 'M' + sx + ',' + sy + ' L' + tx + ',' + ty;
  }
  function genNodesMap(nodes) {
    const hash = {};
    nodes.map(function ({
      id,
      name
    }) {
      hash[id] = {
        id,
        name
      };
    });
    return hash;
  }
  // 处理节点文字换行
  function textBreaking(d3text, text) {
    const len = text.length
    if (len <= 3) {
      d3text.append('tspan')
        .attr('x', 0)
        .attr('y', -3)
        .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
      let botY = 34
      if (len <= 9) {
        topY += 10
        midY += 10
      } else {
        botText = text.substring(7, 9) + '...'
      }
      d3text.text('')
      d3text.append('tspan')
        .attr('x', 0)
        .attr('y', topY)
        .text(function () {
          return topText
        })
      d3text.append('tspan')
        .attr('x', 0)
        .attr('y', midY)
        .text(function () {
          return midText
        })
      d3text.append('tspan')
        .attr('x', 0)
        .attr('y', botY - 7)
        .text(function () {
          return botText
        })
    }
  }
  // ticked
  function ticked() {
    // 连线路径
    links
      .attr('d', link => genLinkPath(link))
    // 连线文字位置
    linksText
      .attr('x', function (d) { return (d.source.x + d.target.x) / 2 })
      .attr('y', function (d) { return (d.source.y + d.target.y) / 2 })
    // 节点位置
    gs
      .attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')' })
  }
  // drag
  function started(d) {
    if (!d3.event.active) {
      forceSimulation.alphaTarget(0.8).restart() // 设置衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0, 1]
    }
    d.fx = d.x
    d.fy = d.y
  }
  function dragged(d) {
    d.fx = d3.event.x
    d.fy = d3.event.y
  }
  function ended(d) {
    if (!d3.event.active) {
      forceSimulation.alphaTarget(0)
    }
    d.fx = null
    d.fy = null
  }
</script>

</html>

参考的文章比较多就不一一例举了,在bilibili上看到一位UP主针对d3实现知识图谱做了专栏,给我很大帮助,谢谢大佬啦(ip:北极熊Leo)

  • 8
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值