이 글은 주로 React에서 d3 force-directed graph를 작성하는 방법을 소개하고 있습니다.
d3js는 데이터를 기반으로 문서를 조작할 수 있는 JavaScript 라이브러리입니다. 데이터는 HTML, CSS, SVG 및 Canvas를 사용하여 표시할 수 있습니다. 힘 방향 그래프를 사용하여 노드 간의 다대다 관계를 나타낼 수 있습니다.
성취 효과: 연결에 화살표가 있습니다. 노드를 클릭하면 노드의 색상과 연결된 선의 두께가 변경되고 확대/축소 및 드래그할 수 있습니다.
버전: 4. 2. 코드 분해하기
1.Components
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 }}> <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. 노드 및 연결 구성
<p className="theChart" id="theChart" ref="theChart"> </p>
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) || '', // 连线名称 }); }
중심화: 중심 힘, 그래프 중심점 위치 설정 .
Collision: 노드 충돌력, .strength 매개변수 범위는 [0, 1]입니다.
링크: 연결의 힘, .distance는 연결 양쪽 끝의 노드 사이의 거리를 설정합니다.Many-Body: .strength 매개변수가 양수이면 중력을 시뮬레이션하고, 음수이면 .distanceMax 매개변수가 최대 거리를 설정합니다.
4. svg를 그립니다
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));
5. 연결 그리기
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
7. 노드 그리기
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); // 粗细
8. 노드 이름
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'); // 填充颜色
9. 연결 이름
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); // 拖拽单个节点带动整个图
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 });
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; });
nodesCircle.append('title') .text((node) => { // .text设置气泡提示内容 return node.definition; });
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)'; }); });
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);
4. 사용 시 주의사항 in React
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);
그래프 구성 위치
그래프는 동적이기 때문에 여러 번 렌더링하면(렌더링을 여러 번 실행하고 여러 번 렌더링하는 경우) 대신 이전에 렌더링된 그래프를 덮어쓰지 않습니다. 여러 렌더링과 여러 그래프가 발생합니다. 구성도의 print() 함수를 componentDidMount()에 넣어서 실행시키면 한번만 렌더링됩니다.노드 및 연결 데이터를 추가, 삭제, 수정한 후 다시 print() 함수를 호출하여 그래프를 재구성해야 합니다.
데이터를 얻을 수 있는 곳데이터는 redux에서 얻지 않고, 요청을 보낸 후 콜백을 통해 직접 얻습니다. 5. 유용한 정보: d3 프로젝트 검색 URL
D3js 모든 프로젝트 검색 http://blockbuilder.org/search/위 내용은 앞으로 모든 사람에게 도움이 되기를 바랍니다. .
관련 기사:
vue에서 정방향 새로 고침 및 역방향 새로 고침 없음 효과를 얻는 방법Vue2.5의 Table 및 Pagination 구성 요소를 통해 페이징 기능을 구현하는 방법 Bootstrap을 통합하는 방법 라라벨에 4개?
js를 사용하여 선택 옵션을 동적으로 추가하는 방법(자세한 튜토리얼)
vue.js를 사용하여 원활한 스크롤 효과를 얻는 방법
위 내용은 반응을 사용하여 d3 힘 방향 그래프를 작성하는 방법(자세한 튜토리얼)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!