0

0

使用react如何搭建d3力导向图(详细教程)

亚连

亚连

发布时间:2018-06-12 12:04:50

|

4514人浏览过

|

来源于php中文网

原创

本篇文章主要介绍了如何在react中搭建d3力导向图,现在分享给大家,也给大家做个参考。

D3js力导向图搭建

d3js是一个可以基于数据来操作文档的JavaScript库。可以使用HTML,CSS,SVG以及Canvas来展示数据。力导向图能够用来表示节点间多对多的关系。

实现效果:连线有箭头,点击节点能改变该节点颜色和所连接的线的粗细,缩放、拖拽。

版本:4.X

安装和导入

npm安装:npm install d3

前端导入:import * as d3 from 'd3';

一、完整代码

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import * as d3 from 'd3';
import { Row, Form } from 'antd';

import { chartReq} from './actionCreator';
import './Chart.less';

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

let simulation;

class Chart extends Component {
 constructor(props, context) {
  super(props, context);
  this.print = this.print.bind(this);
  this.forceChart = this.forceChart.bind(this);
  this.state = {

  };
 }

 componentWillMount() {
  this.props.dispatch(push('/Chart'));
 }

 componentDidMount() {
  this.print();
 }

 print() {
  let callback = (res) => { // callback获取后台返回的数据,并存入state
   let nodeData = res.data.nodes;
   let relationData = res.data.rels;
   this.setState({
    nodeData: res.data.nodes,
    relationData: res.data.rels,
   });
   let nodes = [];
   for (let i = 0; i < nodeData.length; i++) {
    nodes.push({
     id: (nodeData[i] && nodeData[i].id) || '',
     name: (nodeData[i] && nodeData[i].name) || '',
     type: (nodeData[i] && nodeData[i].type) || '',
     definition: (nodeData[i] && nodeData[i].definition) || '',
    });
   }
   let edges = [];
   for (let i = 0; i < relationData.length; i++) {
    edges.push({
     id: (relationData[i] && (relationData[i].id)) || '',
     source: (relationData[i] && relationData[i].start.id) || '',
     target: (relationData[i] && relationData[i].end.id) || '',
     tag: (relationData[i] && relationData[i].name) || '',
    });
   }
   this.forceChart(nodes, edges); // d3力导向图内容
  };
  this.props.dispatch(chartReq({ param: param }, callback));
 }

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

  // 函数内其余代码请看拆解代码
  }

   render() {
  
    return (
     
      

); } } Chart.propTypes = { dispatch: PropTypes.func.isRequired, }; function mapStateToProps(state) { return { }; } const WrappedChart = Form.create({})(Chart); export default connect(mapStateToProps)(WrappedChart);

二、拆解代码

1.组件

整个图都将在p里绘制。

2.构造节点和连线

let nodes = []; // 节点
for (let i = 0; i < nodeData.length; i++) {
  nodes.push({
    id: (nodeData[i] && nodeData[i].id) || '',
    name: (nodeData[i] && nodeData[i].name) || '', // 节点名称
  });
}
let edges = []; // 连线
for (let i = 0; i < relationData.length; i++) {
  edges.push({
    id: (relationData[i] && (relationData[i].id)) || '',
    source: (relationData[i] && relationData[i].start.id) || '', // 开始节点
    target: (relationData[i] && relationData[i].end.id) || '', // 结束节点
    tag: (relationData[i] && relationData[i].name) || '', // 连线名称
  });
}

具体怎么构造依据你们的项目数据。

3.定义力模型

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));

通过simulation.force()设置力,可以设置这几种力:

  1. Centering:中心力,设置图中心点位置。

  2. Collision:节点碰撞作用力,.strength参数范围为[0,1]。

  3. Links:连线的作用力;.distance设置连线两端节点的距离。

  4. Many-Body:.strength的参数为正时,模拟重力,为负时,模拟电荷力;.distanceMax的参数设置最大距离。

Positioning:给定向某个方向的力。

通过simulation.on监听力图元素位置变化。

4.绘制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

创建svg,在svg里创建g,将节点连线等内容放在g内。

  1. select:选择第一个对应的元素

  2. selectAll:选择所有对应的元素

  3. append:创建元素

5.绘制连线

万知
万知

万知: 你的个人AI工作站

下载
const edgesLine = svg.select('g')
  .selectAll('line')
  .data(edges) // 绑定数据
  .enter() // 添加数据到选择集edgepath
  .append('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 i && 'edgepath' + i; }) // 设置id,用于连线文字
  .attr('marker-end', 'url(#arrow)') // 根据箭头标记的id号标记箭头
  .style('stroke', '#000') // 颜色
  .style('stroke-width', 1); // 粗细

连线用贝塞尔曲线绘制:(M  起点X  起点y  L  终点x  终点y)

6.绘制连线上的箭头

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'); // 填充颜色
  1. viewport:可视区域

  2. viewBox:实际大小,会自动缩放填充viewport

7.绘制节点

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); // 拖拽单个节点带动整个图

创建圆作为节点。

.call()调用拖拽函数。

8.节点名称

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

因为文字在节点上层,如果没有设置禁止鼠标事件,点击文字将无法响应点击节点的效果,也无法拖拽节点。

9.连线名称

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 i && '#edgepath' + i; }) // 文字布置在对应id的连线上
  .style('pointer-events', 'none')
  .text((d) => { return d && d.tag; });

10.鼠标移到节点上有气泡提示

nodesCircle.append('title')
  .text((node) => { // .text设置气泡提示内容
    return node.definition;
  });

11.监听图元素的位置变化

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

12.拖拽

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);

13.缩放

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);

三、其它效果

1.单击节点时让连接线加粗

nodesCircle.on('click, (node) => {
  edges_line.style("stroke-width",function(line){
    if(line.source.name==node.name || line.target.name==node.name){
      return 4;
    }else{
      return 0.5;
    }
  });
})

2.被点击的节点变色

nodesCircle.on('click, (node) => {
  nodesCircle.style('fill', (nodeOfSelected) => { // nodeOfSelected:所有节点, node: 选中的节点
  if (nodeOfSelected.id === node.id) { // 被点击的节点变色
    console.log('node')
      return '#36F';
    } else {
      return '#9FF';
    }
  });
})

四、在react中使用注意事项

componentDidMount() {
  this.print();
}
print() {
  let callback = (res) => { // callback获取后台返回的数据,并存入state
    let nodeData = res.data.nodes;
    let relationData = res.data.rels;
    this.setState({
    nodeData: res.data.nodes,
    relationData: res.data.rels,
    });
    let nodes = [];
    for (let i = 0; i < nodeData.length; i++) {
      nodes.push({
        id: (nodeData[i] && nodeData[i].id) || '',
        name: (nodeData[i] && nodeData[i].name) || '',
        type: (nodeData[i] && nodeData[i].type) || '',
        definition: (nodeData[i] && nodeData[i].definition) || '',
      });
    }
    let edges = [];
    for (let i = 0; i < relationData.length; i++) {
      edges.push({
        id: (relationData[i] && (relationData[i].id)) || '',
        source: (relationData[i] && relationData[i].start.id) || '',
        target: (relationData[i] && relationData[i].end.id) || '',
        tag: (relationData[i] && relationData[i].name) || '',
      });
    }
    this.forceChart(nodes, edges); // d3力导向图内容
  };
  this.props.dispatch(getDataFromNeo4J({
    neo4jrun: 'match p=(()-[r]-()) return p limit 300',
  }, callback));
}

在哪里构造图 因为图是动态的,如果渲染多次(render执行多次,渲染多次),不会覆盖前面渲染的图,反而会造成渲染多次,出现多个图的现象。把构造图的函数print()放到componentDidMount()内执行,则只会渲染一次。
对节点和连线数据进行增删改操作后,需要再次调用print()函数,重新构造图。

从哪里获取数据 数据不从redux获取,发送请求后callback直接获取。

五、干货:d3项目查找网址

D3js所有项目检索.http://blockbuilder.org/search/

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在vue中如何才能实现前进刷新后退不刷新效果

在Vue2.5中通过Table 和 Pagination 组件如何实现分页功能

在Laravel中如何整合Bootstrap 4?

在jquery中如何获取select标签中的option值

使用js如何给select动态添加option(详细教程)

使用vue.js如何实现无缝滚动效果

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
laravel组件介绍
laravel组件介绍

laravel 提供了丰富的组件,包括身份验证、模板引擎、缓存、命令行工具、数据库交互、对象关系映射器、事件处理、文件操作、电子邮件发送、队列管理和数据验证。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

320

2024.04.09

laravel中间件介绍
laravel中间件介绍

laravel 中间件分为五种类型:全局、路由、组、终止和自定。想了解更多laravel中间件的相关内容,可以阅读本专题下面的文章。

278

2024.04.09

laravel使用的设计模式有哪些
laravel使用的设计模式有哪些

laravel使用的设计模式有:1、单例模式;2、工厂方法模式;3、建造者模式;4、适配器模式;5、装饰器模式;6、策略模式;7、观察者模式。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

373

2024.04.09

thinkphp和laravel哪个简单
thinkphp和laravel哪个简单

对于初学者来说,laravel 的入门门槛较低,更易上手,原因包括:1. 更简单的安装和配置;2. 丰富的文档和社区支持;3. 简洁易懂的语法和 api;4. 平缓的学习曲线。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

374

2024.04.10

laravel入门教程
laravel入门教程

本专题整合了laravel入门教程,想了解更多详细内容,请阅读专题下面的文章。

86

2025.08.05

laravel实战教程
laravel实战教程

本专题整合了laravel实战教程,阅读专题下面的文章了解更多详细内容。

65

2025.08.05

laravel面试题
laravel面试题

本专题整合了laravel面试题相关内容,阅读专题下面的文章了解更多详细内容。

68

2025.08.05

jquery插件有哪些
jquery插件有哪些

jquery插件有jQuery UI、jQuery Validate、jQuery DataTables、jQuery Slick、jQuery LazyLoad、jQuery Countdown、jQuery Lightbox、jQuery FullCalendar、jQuery Chosen和jQuery EasyUI等。本专题为大家提供jquery插件相关的文章、下载、课程内容,供大家免费下载体验。

151

2023.09.12

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

9

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Echarts实战项目—疫情数据展示
Echarts实战项目—疫情数据展示

共13课时 | 1.3万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号