详解如何在react中搭建d3力导向图


Posted in Javascript onJanuary 12, 2018

D3js力导向图搭建

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

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

版本:4.X

详解如何在react中搭建d3力导向图

安装和导入

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 (
     <Row style={{ minWidth: 900 }}>
      <div className="outerDiv">
       <div className="theChart" id="theChart" ref="theChart">
  
       </div>
      </div>
     </Row>
    );
   }
  }

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

二、拆解代码

1.组件

<div className="theChart" id="theChart" ref="theChart">
</div>

整个图都将在div里绘制。

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.绘制连线

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/

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript下string.format函数补充
Aug 24 Javascript
html+javascript实现可拖动可提交的弹出层对话框效果
Aug 05 Javascript
JQuery中form验证出错信息的查看方法
Oct 08 Javascript
tangram框架响应式加载图片方法
Nov 21 Javascript
手机平板等移动端适配跳转URL的js代码
Jan 25 Javascript
浅谈Jquery为元素绑定事件
Apr 27 Javascript
jQuery插件HighCharts绘制简单2D折线图效果示例【附demo源码】
Mar 21 jQuery
深入理解Vue父子组件生命周期执行顺序及钩子函数
Aug 12 Javascript
react 国际化的实现代码示例
Sep 14 Javascript
JavaScript 点击触发复制功能实例详解
Nov 02 Javascript
vue多页面项目中路由使用history模式的方法
Sep 23 Javascript
微信小程序实现页面左右滑动
Nov 16 Javascript
关于axios不能使用Vue.use()浅析
Jan 12 #Javascript
Vuex 进阶之模块化组织详解
Jan 12 #Javascript
动态Axios的配置步骤详解
Jan 12 #Javascript
JS兼容所有浏览器的DOMContentLoaded事件
Jan 12 #Javascript
使用JS获取SessionStorage的值
Jan 12 #Javascript
node.js+express+mySQL+ejs+bootstrop实现网站登录注册功能
Jan 12 #Javascript
web前端vue filter 过滤器
Jan 12 #Javascript
You might like
探讨:如何使用PHP实现计算两个日期间隔的年、月、周、日数
2013/06/13 PHP
php+flash+jQuery多图片上传源码分享
2020/07/27 PHP
精解window.setTimeout()&amp;window.setInterval()使用方式与参数传递问题!
2007/11/23 Javascript
javascript 导出数据到Excel(处理table中的元素)
2009/12/18 Javascript
基于jQuery的js分页代码
2010/06/10 Javascript
JavaScript中的面向对象介绍
2012/06/30 Javascript
用jQuery获取IE9下拉框默认值问题探讨
2013/07/22 Javascript
FireBug 调试JS入门教程 如何调试JS
2013/12/23 Javascript
关闭ie窗口清除Session的解决方法
2014/01/10 Javascript
js检测离开或刷新页面时表单数据是否更改的方法
2016/08/02 Javascript
JavaScript中const、var和let区别浅析
2016/10/11 Javascript
vue.js评论发布信息可插入QQ表情功能
2017/08/08 Javascript
Vue中computed与methods的区别详解
2018/03/24 Javascript
Vue 项目代理设置的优化
2018/04/17 Javascript
AngularJS标签页tab选项卡切换功能经典实例详解
2018/05/16 Javascript
promise和co搭配生成器函数方式解决js代码异步流程的比较
2018/05/25 Javascript
详解封装基础的angular4的request请求方法
2018/06/05 Javascript
JS实现换肤功能的方法实例详解
2019/01/30 Javascript
vuex实现购物车功能
2020/06/28 Javascript
[43:41]OG vs Newbee 2019国际邀请赛淘汰赛 胜者组 BO3 第一场 8.21.mp4
2020/07/19 DOTA
Python httplib模块使用实例
2015/04/11 Python
在Python中用has_key()方法查找键是否存在的教程
2015/05/21 Python
简介Python设计模式中的代理模式与模板方法模式编程
2016/02/02 Python
基于Django框架利用Ajax实现点赞功能实例代码
2018/08/19 Python
pycharm使用matplotlib.pyplot不显示图形的解决方法
2018/10/28 Python
解决Python中list里的中文输出到html模板里的问题
2018/12/17 Python
selenium+python截图不成功的解决方法
2019/01/30 Python
pycharm配置git(图文教程)
2019/08/16 Python
python实现查找所有程序的安装信息
2020/02/18 Python
python使用信号量动态更新配置文件的操作
2020/04/01 Python
基于opencv的selenium滑动验证码的实现
2020/07/24 Python
物业管理委托协议(2篇)
2014/09/23 职场文书
党纪处分决定书
2015/06/24 职场文书
vue使用v-model进行跨组件绑定的基本实现方法
2021/04/28 Vue.js
Java Optional<Foo>转换成List<Bar>的实例方法
2021/06/20 Java/Android
JavaScript获取URL参数的方法分享
2022/04/07 Javascript