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

网友投稿 368 2023-02-27


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

returhttp://n (

);

}

}

Chart.propTypes = {

dispatch: PropTypes.func.isRequired,

};

function mapStateToProps(state) {

return {

};

}

const WrappedChart = Form.create({})(Chart);

export default connect(mapStateToProps)(WrappedChart);

二、拆解代码

1.组件

整个图都将在div里绘制。

2.构造节点和连线

let nodes = []; // 节点

for (let i = 0; i < nodeData.length; i++) {

nodes.push({

id: (nodeData[i] && nodeData[i].id) || '',

name:http:// (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()设置力,可以设置这几种力:

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

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

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

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内。

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

selectAll:选择所有对应的元素

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'YcVwNkKs, 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'); // 填充颜色

viewport:可视区域

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) => { // 文字内容

http://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://blockbuYcVwNkKsilder.org/search/


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:接口api文档系统(接口 api)
下一篇:Vuex 进阶之模块化组织详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~