D3.js力导向图+react的demo(bug修复的完整版)

原文章示例地址:
https://blog.csdn.net/weixin_33858485/article/details/91456350

效果展示
在这里插入图片描述
网上找了一圈大部分都是同一个demo,还有两个bug,一个是node节点的名称只显示了一个,另一个是连线的名称少显示了一个。

解决方案:

  1. node节点名称显示完整:
    红框部分修改,原因是前面创建连线名称时用的也是text,后面创建节点名称时取text会取到连线名称的text
    在这里插入图片描述
  2. 连线名称显示完整:
    在这里插入图片描述
    如图修改

以下是原完整代码

import React, { Component } from 'react'
import * as d3 from 'd3';

const WIDTH = 1900;
const HEIGHT = 580;
const R = 30;

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {

    }
  }
  componentDidMount() {
    this.print();
  }

  print = () => {
    //初始化数据
    const nodeData = [
      { id: 1, name: '感冒' },
      { id: 2, name: '疟疾' },
      { id: 3, name: '感冒' },
      { id: 4, name: '咽痛寒热' },
      { id: 5, name: '阴虚火旺,风邪外乘' },
      { id: 6, name: '黑膏汤' },
      { id: 7, name: '育阴之中,佐以解表' },
    ];
    let nodes = [];
    for (let i = 0; i < nodeData.length; i++) {
      nodes.push({
        id: (nodeData[i] && nodeData[i].id) || '', // 节点id
        name: (nodeData[i] && nodeData[i].name) || '', // 节点名称
      });
    }
    const relData = [
      { id: 1, source: 1, target: 2, tag: '影响2' },
      { id: 2, source: 1, target: 3, tag: '影响3' },
      { id: 3, source: 1, target: 4, tag: '影响4' },
      { id: 4, source: 1, target: 5, tag: '影响5' },
      { id: 5, source: 1, target: 6, tag: '影响6' },
      { id: 6, source: 1, target: 7, tag: '影响7' },
    ];
    let edges = [];
    for (let i = 0; i < relData.length; i++) {
      edges.push({
        id: (relData[i] && (relData[i].id)) || '', // 连线id
        source: relData[i].source, // 开始节点
        target: relData[i].target, // 结束节点
        tag: (relData[i].tag) || '', // 连线名称
      });
    }
    this.setState({
      nodes: nodes,
      edges: edges
    })
    this.forceChart(nodes, edges)
  }

  forceChart = (nodes, edges) => {
    this.refs['theChart'].innerHTML = '';

    //定义力模型
    const simulation = d3.forceSimulation(nodes) // 指定被引用的nodes数组
      .force('link', d3.forceLink(edges).id(d => d.id).distance(150))
      .force('collision', d3.forceCollide(1).strength(0.1))
      .force('center', d3.forceCenter(WIDTH / 2, HEIGHT / 2))
      .force('charge', d3.forceManyBody().strength(-1000).distanceMax(800));

    //缩放
    function onZoomStart(d) {
      // console.log('start zoom');
    }
    function zooming(d) {
      // 缩放和拖拽整个g
      // console.log('zoom ing', d3.event.transform, d3.zoomTransform(this));
      g.attr('transform', d3.event.transform); // 获取g的缩放系数和平移的坐标值。
    }
    function onZoomEnd() {
      // console.log('zoom end');
    }
    const zoom = d3.zoom()
      // .translateExtent([[0, 0], [WIDTH, HEIGHT]]) // 设置或获取平移区间, 默认为[[-∞, -∞], [+∞, +∞]]
      .scaleExtent([1 / 10, 10]) // 设置最大缩放比例
      .on('start', onZoomStart)
      .on('zoom', zooming)
      .on('end', onZoomEnd);

    //绘制svg
    const svg = d3.select('#theChart').append('svg') // 在id为‘theChart’的标签内创建svg
      .style('width', WIDTH)
      .style('height', HEIGHT * 0.9)
      .on('click', () => {
        console.log('click', d3.event.target.tagName);
      })
      .call(zoom); // 缩放
    const g = svg.append('g'); // 则svg中创建g

    //绘制连线
    const edgesLine = svg.select('g')
      .selectAll('line')
      .data(edges) // 绑定数据
      .enter() // 为数据添加对应数量的占位符
      .append('path') // 在占位符上面生成折线(用path画)
      .attr('d', (d) => { return d && 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; }) //遍历所有数据。d表示当前遍历到的数据,返回绘制的贝塞尔曲线
      .attr('id', (d, i) => { return 'edgepath' + i; }) // 设置id,用于连线文字
      .attr('marker-end', 'url(#arrow)') // 根据箭头标记的id号标记箭头
      .style('stroke', '#000') // 颜色
      .style('stroke-width', 1); // 粗细

    //连线名称
    const edgesText = svg.select('g').selectAll('.edgelabel')
      .data(edges)
      .enter()
      .append('text') // 为每一条连线创建文字区域
      .attr('class', 'edgelabel')
      .attr('dx', 80)
      .attr('dy', 0);
    edgesText.append('textPath')
      .attr('xlink:href', (d, i) => { return '#edgepath' + i; }) // 文字布置在对应id的连线上
      .style('pointer-events', 'none') // 禁止鼠标事件
      .text((d) => { return d && d.tag; }); // 设置文字内容

    //绘制连线上的箭头
    const defs = g.append('defs'); // defs定义可重复使用的元素
    const arrowheads = defs.append('marker') // 创建箭头
      .attr('id', 'arrow')
      // .attr('markerUnits', 'strokeWidth') // 设置为strokeWidth箭头会随着线的粗细进行缩放
      .attr('markerUnits', 'userSpaceOnUse') // 设置为userSpaceOnUse箭头不受连接元素的影响
      .attr('class', 'arrowhead')
      .attr('markerWidth', 20) // viewport
      .attr('markerHeight', 20) // viewport
      .attr('viewBox', '0 0 20 20') // viewBox
      .attr('refX', 9.3 + R) // 偏离圆心距离
      .attr('refY', 5) // 偏离圆心距离
      .attr('orient', 'auto'); // 绘制方向,可设定为:auto(自动确认方向)和 角度值
    arrowheads.append('path')
      .attr('d', 'M0,0 L0,10 L10,5 z') // d: 路径描述,贝塞尔曲线
      .attr('fill', '#000'); // 填充颜色

    //拖拽
    function onDragStart(d) {
      // console.log('start');
      // console.log(d3.event.active);
      if (!d3.event.active) {
        simulation.alphaTarget(1) // 设置衰减系数,对节点位置移动过程的模拟,数值越高移动越快,数值范围[0,1]
          .restart();  // 拖拽节点后,重新启动模拟
      }
      d.fx = d.x;    // d.x是当前位置,d.fx是静止时位置
      d.fy = d.y;
    }
    function dragging(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }
    function onDragEnd(d) {
      if (!d3.event.active) simulation.alphaTarget(0);
      d.fx = null;       // 解除dragged中固定的坐标
      d.fy = null;
    }
    const drag = d3.drag()
      .on('start', onDragStart)
      .on('drag', dragging) // 拖拽过程
      .on('end', onDragEnd);

    //绘制节点
    const nodesCircle = svg.select('g')
      .selectAll('circle')
      .data(nodes)
      .enter()
      .append('circle') // 创建圆
      .attr('r', 30) // 半径
      .style('fill', '#9FF') // 填充颜色
      .style('stroke', '#0CF') // 边框颜色
      .style('stroke-width', 2) // 边框粗细
      .on('click', (node) => { // 点击事件
        console.log('click');
      })
      .call(drag); // 拖拽单个节点带动整个图

    //节点名称
    const nodesTexts = svg.select('g')
      .selectAll('.jdtext')
      .data(nodes)
      .enter()
      .append('text')
      .attr('class', '.jdtext')//bug修复
      .attr('dy', '.3em') // 偏移量
      .attr('text-anchor', 'middle') // 节点名称放在圆圈中间位置
      .style('fill', 'black') // 颜色
      .style('pointer-events', 'none') // 禁止鼠标事件
      .text((d) => { // 文字内容
        return d && d.name; // 遍历nodes每一项,获取对应的name
      });

    //鼠标移到节点上有气泡提示
    nodesCircle.append('title')
      .text((node) => { // .text设置气泡提示内容
        return node.name; // 气泡提示为node的名称
      });

    //监听图元素的位置变化
    simulation.on('tick', () => {
      // 更新节点坐标
      nodesCircle.attr('transform', (d) => {
        return d && 'translate(' + d.x + ',' + d.y + ')';
      });
      // 更新节点文字坐标
      nodesTexts.attr('transform', (d) => {
        return 'translate(' + (d.x) + ',' + d.y + ')';
      });
      // 更新连线位置
      edgesLine.attr('d', (d) => {
        const path = 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
        return path;
      });
      // 更新连线文字位置
      edgesText.attr('transform', (d, i) => {
        return 'rotate(0)';
      });
    });
  }

  render() {
    return (
      <div>
        <div className="outerDiv">
          <div className="theChart" id="theChart" ref="theChart">

          </div>
        </div>
      </div>
    )
  }
}


其他注意:
去掉了antd框架(没什么用)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值