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

D3js力导向图搭建

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

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

版本:4.X

安装和导入

npm安装:npm install d3

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

一、完整代码

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"&gt;
   <div className="theChart" id="theChart" ref="theChart"&gt;

   </div>
  </div>
 </Row>
);

}
}

Chart.propTypes = {
dispatch: PropTypes.func.isrequired,};

function mapStatetoProps(state) {
return {

};
}

const WrappedChart = Form.create({})(Chart);
export default connect(mapStatetoProps)(WrappedChart);

二、拆解代码

1.组件

rush:xhtml;">

整个图都将在div里绘制。

2.构造节点和连线

rush:js;"> let nodes = []; // 节点 for (let i = 0; i < nodeData.length; i++) { nodes.push({ id: (nodeData[i] && nodeData[i].id) || '',// 节点名称 }); } 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.定义力模型

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

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

{ 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.绘制连线上的箭头

rush:js;"> 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.绘制节点

{ // 点击事件 console.log('click'); }) .call(drag); // 拖拽单个节点带动整个图

创建圆作为节点。

.call()调用拖拽函数

8.节点名称

{ // 文字内容 return d && d.name; // 遍历nodes每一项,获取对应的name });

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

9.连线名称

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

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

{ // .text设置气泡提示内容 return node.deFinition; });

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

{ // 更新节点坐标 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',i) => { return 'rotate(0)'; }); });

12.拖拽

rush:js;"> 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.缩放

rush:js;"> 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.单击节点时让连接线加粗

{ 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.style('fill',(nodeOfSelected) => { // nodeOfSelected:所有节点,node: 选中的节点 if (nodeOfSelected.id === node.id) { // 被点击的节点变色 console.log('node') return '#36F'; } else { return '#9FF'; } }); })

四、在react中使用注意事项

{ // callback获取后台返回的数据,并存入state let nodeData = res.data.nodes; let relationData = res.data.rels; this.setState({ nodeData: res.data.nodes,}); let nodes = []; for (let i = 0; i < nodeData.length; i++) { nodes.push({ id: (nodeData[i] && nodeData[i].id) || '',}); } let edges = []; for (let i = 0; i < relationData.length; i++) { edges.push({ id: (relationData[i] && (relationData[i].id)) || '',target: (relationData[i] && relationData[i].end.id) || '',}); } 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/

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

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...