详解如何在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 (
     <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/

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

(0)

相关推荐

  • 详解如何在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'; imp

  • 详解如何在pyqt中通过OpenCV实现对窗口的透视变换

    窗口的透视变换效果    当我们点击Win10的UWP应用中的小部件时,会发现小部件会朝着鼠标点击位置凹陷下去,而且不同的点击位置对应着不同的凹陷情况,看起来就好像小部件在屏幕上不只有x轴和y轴,甚至还有一个z轴.要做到这一点,其实只要对窗口进行透视变换即可.下面是对Qt的窗口和按钮进行透视变换的效果: 具体代码    1.下面先定义一个类,它的作用是将传入的 QPixmap 转换为numpy 数组,然后用 opencv 的 warpPerspective 对数组进行透视变换,最后再将 nump

  • 详解如何在Javascript中使用Object.freeze()

    Object.freeze() Object.freeze() 方法可以冻结一个对象.一个被冻结的对象再也不能被修改:冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性.可配置性.可写性,以及不能修改已有属性的值.此外,冻结一个对象后该对象的原型也不能被修改.freeze() 返回和传入的参数相同的对象 用法 const objectExample = { prop1: 20, prop2: "羊先生" }; // 默认情况下,我们可以根据需

  • 详解如何在C#中使用投影(Projection)

    投影(Projection) 是一种可以将查询结果进行 塑性 的一种操作,你可以使用 投影 将一个 object 转成仅包含你需要属性的新对象,这篇文章中,我们就一起看看如何使用 投影 功能. C# 中的投影 LINQ 集成查询中有两个支持投影的扩展方法,分别为: Select 和 SelectMany 操作,可以用它们投影单个或者多个属性,或者投影查询的结果集到一个新的匿名类型中,还可以在投影的过程中执行: 再计算,过滤,或者其他一些必要的操作. Select 投影 为了演示目的,我先构造一个

  • 详解如何在Flutter中集成华为认证服务

    最近发现华为AGC认证服务支持Flutter框架了,期待这个平台的支持已经很久了,所以迫不及待接入了,关联了自己的邮箱等账号. 集成步骤 安装flutter环境 a) 下载Flutter sdk包,地址:https://flutter.dev/docs/get-started/install/windows 将压缩包解压到任意文件夹,例如D:\Flutter b) 将flutter命令文件添加到环境变量中,此处我添加的Path为D:\Flutter\flutter_windows_1.22.2-

  • 详解Ref在React中的交叉用法

    目录 一.首先说明下什么是Ref 二.ref在hooks中的用法 1.ref在hooks中HTMLDom的用法 2.ref在hooks中与函数式组件的用法 3.ref在hooks中与类组件一同使用 4.ref在hooks中与class.react-redux一同使用 一.首先说明下什么是Ref Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧.对于大多数应用中的组件来说,这通常不是必需的.但其对某些组件,尤其是可重用的组件库是很有用的 Ref官网说明:点击这里 二.ref在ho

  • 详解如何在Java中调用Python程序

    Java中调用Python程序 1.新建一个Maven工程,导入如下依赖 <dependency> <groupId>org.python</groupId> <artifactId>jython-standalone</artifactId> <version>2.7.0</version> </dependency> 2.在java中直接执行python代码片段 import org.python.util

  • 详解如何在Java中实现堆排序算法

    目录 算法描述 实现代码 测试代码 算法描述 堆排序算法的描述如下: 将待排序的数组调整为最大堆,此时未排序的长度 N 为数组的长度,调整的过程就是倒序将数组的前 N/2 个元素下沉的过程,每次下沉都会将较大的元素带到上面,最终将数组变为最大堆: 弹出最大堆的堆顶元素并将其移动到数组的最后面,将原本最后面的元素放到堆顶,然后将未排序的长度 N - 1,调整数组的前 N 个元素为最大堆: 重复步骤 2 直到未排序的长度为 0. 实现代码 package com.zhiyiyo.collection

  • 详解如何在Flutter中获取设备标识符

    目录 使用 platform_device_id 应用预览 代码 使用 device_info_plus 应用预览 代码 结论 本文将引导您完成 2 个示例,演示如何在 Flutter 中获取设备标识符 使用 platform_device_id 如果您只需要运行应用程序的设备的 id,最简单快捷的解决方案是使用platform_device_id包.它适用于 Android (AndroidId).iOS (IdentifierForVendor).Windows (BIOS UUID).ma

  • 详解如何在SpringBoot中自定义参数解析器

    目录 前言 1.自定义参数解析器 2.PrincipalMethodArgumentResolver 3.RequestParamMapMethodArgumentResolver 4.小结 前言 在一个 Web 请求中,参数我们无非就是放在地址栏或者请求体中,个别请求可能放在请求头中. 放在地址栏中,我们可以通过如下方式获取参数: String javaboy = request.getParameter("name "); 放在请求体中,如果是 key/value 形式,我们可以通

随机推荐