반응에서 d3 힘 방향 그래프를 작성하는 방법 공유

小云云
풀어 주다: 2018-01-15 09:23:22
원래의
2013명이 탐색했습니다.

이 글은 주로 React에서 d3 force-directed 그래프를 작성하는 방법을 소개합니다. 편집자는 꽤 좋다고 생각해서 지금 공유하고 참고용으로 제공하겠습니다. 에디터를 따라가서 살펴보도록 하겠습니다. 모두에게 도움이 되었으면 좋겠습니다.

D3js force-directed graph construction

d3js은 데이터를 기반으로 문서를 조작할 수 있는 JavaScript 라이브러리입니다. 데이터는 HTML, CSS, SVG 및 Canvas를 사용하여 표시할 수 있습니다. 힘 방향 그래프를 사용하여 노드 간의 다대다 관계를 나타낼 수 있습니다.

성취 효과: 연결선에 화살표가 있습니다. 노드를 클릭하면 노드의 색상과 연결된 선의 두께가 변경되고 확대/축소 및 드래그할 수 있습니다.

버전: 4.

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) || &#39;&#39;,
     name: (nodeData[i] && nodeData[i].name) || &#39;&#39;,
     type: (nodeData[i] && nodeData[i].type) || &#39;&#39;,
     definition: (nodeData[i] && nodeData[i].definition) || &#39;&#39;,
    });
   }
   let edges = [];
   for (let i = 0; i < relationData.length; i++) {
    edges.push({
     id: (relationData[i] && (relationData[i].id)) || &#39;&#39;,
     source: (relationData[i] && relationData[i].start.id) || &#39;&#39;,
     target: (relationData[i] && relationData[i].end.id) || &#39;&#39;,
     tag: (relationData[i] && relationData[i].name) || &#39;&#39;,
    });
   }
   this.forceChart(nodes, edges); // d3力导向图内容
  };
  this.props.dispatch(chartReq({ param: param }, callback));
 }

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

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

   render() {
  
    return (
     <Row style={{ minWidth: 900 }}>
      <p className="outerp">
       <p className="theChart" id="theChart" ref="theChart">
  
       </p>
      </p>
     </Row>
    );
   }
  }

  Chart.propTypes = {
   dispatch: PropTypes.func.isRequired,
  };
  
  function mapStateToProps(state) {
   return {
  
   };
  }
  
  const WrappedChart = Form.create({})(Chart);
  export default connect(mapStateToProps)(WrappedChart);
로그인 후 복사
2. 코드 분해


1. 컴포넌트


<p className="theChart" id="theChart" ref="theChart">
</p>
로그인 후 복사
전체 그림은 p로 그려집니다.


2. 노드 및 연결 구성


let nodes = []; // 节点
for (let i = 0; i < nodeData.length; i++) {
  nodes.push({
    id: (nodeData[i] && nodeData[i].id) || &#39;&#39;,
    name: (nodeData[i] && nodeData[i].name) || &#39;&#39;, // 节点名称
  });
}
let edges = []; // 连线
for (let i = 0; i < relationData.length; i++) {
  edges.push({
    id: (relationData[i] && (relationData[i].id)) || &#39;&#39;,
    source: (relationData[i] && relationData[i].start.id) || &#39;&#39;, // 开始节点
    target: (relationData[i] && relationData[i].end.id) || &#39;&#39;, // 结束节点
    tag: (relationData[i] && relationData[i].name) || &#39;&#39;, // 连线名称
  });
}
로그인 후 복사
특정 구성은 프로젝트 데이터를 기반으로 합니다.


3. 힘 모델 정의


const simulation = d3.forceSimulation(nodes) // 指定被引用的nodes数组
  .force(&#39;link&#39;, d3.forceLink(edges).id(d => d.id).distance(150))
  .force(&#39;collision&#39;, d3.forceCollide(1).strength(0.1))
  .force(&#39;center&#39;, d3.forceCenter(WIDTH / 2, HEIGHT / 2))
  .force(&#39;charge&#39;, d3.forceManyBody().strength(-1000).distanceMax(800));
로그인 후 복사
simulation.force()를 통해 힘을 설정합니다.


센터링: 중심 힘, 위치 설정 그래프의 중심점.


Collision: 노드 충돌력, .strength 매개변수 범위는 [0, 1]입니다.


링크: 연결의 힘, .distance는 연결 양쪽 끝의 노드 사이의 거리를 설정합니다.


Many-Body: .strength 매개변수가 양수이면 중력을 시뮬레이션하고, 음수이면 .distanceMax 매개변수가 최대 거리를 설정합니다.

  1. 위치 지정: 특정 방향으로 힘이 주어집니다.

  2. simulation.on을 통해 청력도 요소의 위치 변화를 모니터링하세요.
  3. 4. svg를 그리고
  4. const svg = d3.select(&#39;#theChart&#39;).append(&#39;svg&#39;) // 在id为‘theChart&#39;的标签内创建svg
       .style(&#39;width&#39;, WIDTH)
       .style(&#39;height&#39;, HEIGHT * 0.9)
       .on(&#39;click&#39;, () => {
        console.log(&#39;click&#39;, d3.event.target.tagName);
       })
       .call(zoom); // 缩放
    const g = svg.append(&#39;g&#39;); // 则svg中创建g
    로그인 후 복사

    svg를 만들고, svg에 g를 만들고, g 안에 노드 연결 및 기타 콘텐츠를 넣습니다.


select: 첫 번째 해당 요소 선택


selectAll: 모든 해당 요소 선택


append: 요소 생성


5 연결선 그리기

  1. const edgesLine = svg.select(&#39;g&#39;)
      .selectAll(&#39;line&#39;)
      .data(edges) // 绑定数据
      .enter() // 添加数据到选择集edgepath
      .append(&#39;path&#39;) // 生成折线
      .attr(&#39;d&#39;, (d) => { return d && &#39;M &#39; + d.source.x + &#39; &#39; + d.source.y + &#39; L &#39; + d.target.x + &#39; &#39; + d.target.y; }) // 遍历所有数据,d表示当前遍历到的数据,返回绘制的贝塞尔曲线
      .attr(&#39;id&#39;, (d, i) => { return i && &#39;edgepath&#39; + i; }) // 设置id,用于连线文字
      .attr(&#39;marker-end&#39;, &#39;url(#arrow)&#39;) // 根据箭头标记的id号标记箭头
      .style(&#39;stroke&#39;, &#39;#000&#39;) // 颜色
      .style(&#39;stroke-width&#39;, 1); // 粗细
    로그인 후 복사
  2. 연결 선은 베지어 곡선으로 그려집니다. (M 시작점

    viewBox: 실제 크기, 뷰포트를 채우기 위해 자동으로 크기가 조정됩니다
7. 노드 그리기


const defs = g.append(&#39;defs&#39;); // defs定义可重复使用的元素
const arrowheads = defs.append(&#39;marker&#39;) // 创建箭头
  .attr(&#39;id&#39;, &#39;arrow&#39;)
  // .attr(&#39;markerUnits&#39;, &#39;strokeWidth&#39;) // 设置为strokeWidth箭头会随着线的粗细进行缩放
  .attr(&#39;markerUnits&#39;, &#39;userSpaceOnUse&#39;) // 设置为userSpaceOnUse箭头不受连接元素的影响
  .attr(&#39;class&#39;, &#39;arrowhead&#39;)
  .attr(&#39;markerWidth&#39;, 20) // viewport
  .attr(&#39;markerHeight&#39;, 20) // viewport
  .attr(&#39;viewBox&#39;, &#39;0 0 20 20&#39;) // viewBox
  .attr(&#39;refX&#39;, 9.3 + R) // 偏离圆心距离
  .attr(&#39;refY&#39;, 5) // 偏离圆心距离
  .attr(&#39;orient&#39;, &#39;auto&#39;); // 绘制方向,可设定为:auto(自动确认方向)和 角度值
arrowheads.append(&#39;path&#39;)
  .attr(&#39;d&#39;, &#39;M0,0 L0,10 L10,5 z&#39;) // d: 路径描述,贝塞尔曲线
  .attr(&#39;fill&#39;, &#39;#000&#39;); // 填充颜色
로그인 후 복사

원을 노드로 만듭니다.


.call()은 드래그 기능을 호출합니다.

8. 노드 이름


    const nodesCircle = svg.select(&#39;g&#39;)
      .selectAll(&#39;circle&#39;)
      .data(nodes)
      .enter()
      .append(&#39;circle&#39;) // 创建圆
      .attr(&#39;r&#39;, 30) // 半径
      .style(&#39;fill&#39;, &#39;#9FF&#39;) // 填充颜色
      .style(&#39;stroke&#39;, &#39;#0CF&#39;) // 边框颜色
      .style(&#39;stroke-width&#39;, 2) // 边框粗细
      .on(&#39;click&#39;, (node) => { // 点击事件
        console.log(&#39;click&#39;);
      })
      .call(drag); // 拖拽单个节点带动整个图
    로그인 후 복사
  1. 텍스트가 노드의 상위 레이어에 있기 때문에 마우스 이벤트가 비활성화되지 않으면 텍스트를 클릭해도 노드 클릭 효과에 반응하지 않으며, 노드를 끌 수 없습니다.

  2. 9. 연결 이름


const nodesTexts = svg.select(&#39;g&#39;)
  .selectAll(&#39;text&#39;)
  .data(nodes)
  .enter()
  .append(&#39;text&#39;)
  .attr(&#39;dy&#39;, &#39;.3em&#39;) // 偏移量
  .attr(&#39;text-anchor&#39;, &#39;middle&#39;) // 节点名称放在圆圈中间位置
  .style(&#39;fill&#39;, &#39;black&#39;) // 颜色
  .style(&#39;pointer-events&#39;, &#39;none&#39;) // 禁止鼠标事件
  .text((d) => { // 文字内容
    return d && d.name; // 遍历nodes每一项,获取对应的name
  });
로그인 후 복사
10. 노드로 마우스를 이동하면 버블 프롬프트가 표시됩니다


const edgesText = svg.select(&#39;g&#39;).selectAll(&#39;.edgelabel&#39;)
  .data(edges)
  .enter()
  .append(&#39;text&#39;) // 为每一条连线创建文字区域
  .attr(&#39;class&#39;, &#39;edgelabel&#39;)
  .attr(&#39;dx&#39;, 80)
  .attr(&#39;dy&#39;, 0);
edgesText.append(&#39;textPath&#39;)// 设置文字内容
  .attr(&#39;xlink:href&#39;, (d, i) => { return i && &#39;#edgepath&#39; + i; }) // 文字布置在对应id的连线上
  .style(&#39;pointer-events&#39;, &#39;none&#39;)
  .text((d) => { return d && d.tag; });
로그인 후 복사

11. 그래프 요소


nodesCircle.append(&#39;title&#39;)
  .text((node) => { // .text设置气泡提示内容
    return node.definition;
  });
로그인 후 복사

12. Drag



simulation.on(&#39;tick&#39;, () => {
  // 更新节点坐标
  nodesCircle.attr(&#39;transform&#39;, (d) => {
    return d && &#39;translate(&#39; + d.x + &#39;,&#39; + d.y + &#39;)&#39;;
  });
  // 更新节点文字坐标
  nodesTexts.attr(&#39;transform&#39;, (d) => {
    return &#39;translate(&#39; + (d.x) + &#39;,&#39; + d.y + &#39;)&#39;;
  });
  // 更新连线位置
  edgesLine.attr(&#39;d&#39;, (d) => {
    const path = &#39;M &#39; + d.source.x + &#39; &#39; + d.source.y + &#39; L &#39; + d.target.x + &#39; &#39; + d.target.y;
    return path;
  });
  // 更新连线文字位置
  edgesText.attr(&#39;transform&#39;, (d, i) => {
    return &#39;rotate(0)&#39;;
  });
});
로그인 후 복사

13. Zoom


function onDragStart(d) {
  // console.log(&#39;start&#39;);
  // 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(&#39;start&#39;, onDragStart)
  .on(&#39;drag&#39;, dragging) // 拖拽过程
  .on(&#39;end&#39;, onDragEnd);
로그인 후 복사

3. 1. 연결선을 굵게 해주세요. 노드를 클릭하세요


function onZoomStart(d) {
  // console.log(&#39;start zoom&#39;);
}
function zooming(d) {
  // 缩放和拖拽整个g
  // console.log(&#39;zoom ing&#39;, d3.event.transform, d3.zoomTransform(this));
  g.attr(&#39;transform&#39;, d3.event.transform); // 获取g的缩放系数和平移的坐标值。
}
function onZoomEnd() {
  // console.log(&#39;zoom end&#39;);
}
const zoom = d3.zoom()
  // .translateExtent([[0, 0], [WIDTH, HEIGHT]]) // 设置或获取平移区间, 默认为[[-∞, -∞], [+∞, +∞]]
  .scaleExtent([1 / 10, 10]) // 设置最大缩放比例
  .on(&#39;start&#39;, onZoomStart)
  .on(&#39;zoom&#39;, zooming)
  .on(&#39;end&#39;, onZoomEnd);
로그인 후 복사

2. 클릭한 노드의 색상이 변경됩니다

nodesCircle.on(&#39;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;
    }
  });
})
로그인 후 복사

4. React


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

사용시 주의사항

그래프 때문에 동적이므로 여러 번 렌더링되면(렌더링이 여러 번 실행되고 여러 번 렌더링됨) 이전에 렌더링된 이미지를 덮어쓰지 않고 대신 여러 렌더링과 여러 이미지가 발생합니다. 구성도의 print() 함수를 componentDidMount()에 넣어서 실행시키면 한번만 렌더링됩니다. 노드 및 연결 데이터를 추가, 삭제, 수정한 후 다시 print() 함수를 호출하여 그래프를 재구성해야 합니다.

데이터를 얻을 수 있는 곳
데이터는 redux에서 얻지 않고, 요청을 보낸 후 콜백을 통해 직접 얻습니다.

관련 권장 사항:


d3 강제 방향 그래프 포커싱 구현 방법


D3.js를 사용하여 테이블 만들기 소개


Python을 사용하여 mp3 파일의 id3v1 정보를 읽고 쓰기

위 내용은 반응에서 d3 힘 방향 그래프를 작성하는 방법 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿