這篇文章一直在醞釀中峭跳,一直不知以什么方式來寫咙冗。想來想去最終還是平白直述的方式來講:從創(chuàng)建項目,引入D3翔烁,開始繪制圖形弦牡,添加修改元素友驮,最后完成。
React
React
是一個用于構建用戶界面的JavaScript庫喇伯。它使創(chuàng)建可重用組件變得容易喊儡,這些組件可以組合在一起形成更復雜的組件。
這些組件能夠自己維護它們的狀態(tài)稻据。
D3
D3.js
是一個基于數(shù)據(jù)操作文檔的JavaScript庫艾猜。D3幫助您使用HTML买喧、SVG和CSS來處理數(shù)據(jù)。D3對web標準的強調為您提供了現(xiàn)代瀏覽器的全部功能匆赃,而不需要將自己綁定到專用框架淤毛,它結合了強大的可視化組件和數(shù)據(jù)驅動的DOM操作方法。
安裝React算柳,添加D3
安裝react最簡單的方法是使用react團隊的create-react-app模板低淡。
在本地機器上全局安裝,以便可以重用瞬项,在終端上運行以下命令:
npm install -g create-react-app
接下來使用create-react-app模板創(chuàng)建一個新的app
create-react-app react-d3-network-visual-graph
cd react-d3-network-visual-graph
yarn add d3
現(xiàn)在準備工作做完了蔗蹋,我們可以在React中使用D3進行數(shù)據(jù)可視化開發(fā)了。
啟動項目
yarn start
使用自己喜歡的編輯器(我用vscode)打開工程囱淋。
這是當前在瀏覽器中呈現(xiàn)的組件猪杭。我們需要刪除render()方法的內(nèi)容,替換成我們自己的內(nèi)容妥衣。
在src目錄中創(chuàng)建一個Graph文件皂吮,添加index.js,用來創(chuàng)建關系圖譜税手。添加畫圖用的數(shù)據(jù)蜂筹,我們直接寫在data.js。
index.js文件結構
import React, {Component} from 'react';
import './index.css';
import * as d3 from 'd3';
import data from './data';
class NetworkVisualGraph extends Component {
constructor(props) {
super(props)
this.state = {
svg: null,
simulation: null
};
}
componentDidMount() {
this.drawGraph();
}
...
...
...
render() {
return <div className="network-visual">
<h1>關系圖譜</h1>
<div className="content" id="networkVisual"></div>
</div>
}
}
export default NetworkVisualGraph;
接下來添加各個元素
添加箭頭
添加正向和反向箭頭芦倒,節(jié)點間連線箭頭不用每次都創(chuàng)建箭頭線艺挪,直接調用創(chuàng)建好的箭頭線。
initMarker() {
const svg = this.state.svg;
svg.append('defs').selectAll('marker')
.data(['back', 'face'])
.enter().append('marker')
.attr('id', d => 'marker-' + d)
.attr('viewBox', '0, 0, 6, 6')
.attr('refX', 18)
.attr('refY', 3)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0 0 L6 3 L0 6 Z')
.attr('fill', '#999');
}
添加邊兵扬,返回links模擬連線
initLink(links) {
const svg = this.state.svg;
const gLists = svg.append('g')
.attr('class', 'links')
.selectAll('g')
.data(links)
.enter().append('g');
return gLists.append('path')
.attr('class', 'link')
.attr('marker-end', (d) => 'url(#marker-face)');
}
添加節(jié)點闺属,返回節(jié)點
畫圓形節(jié)點,可以根據(jù)節(jié)點的類型不一樣設置不同的顏色周霉,不同的內(nèi)容顯示。
initNode(nodes) {
const svg = this.state.svg;
const gLists = svg.append('g')
.attr('class', 'nodes')
.selectAll('g')
.data(nodes)
.enter()
.append('g')
.attr('class', (d) => 'node-' + d.id)
.call(d3.drag()
.on('start', this.dragStarted.bind(this))
.on('drag', this.dragged.bind(this))
.on('end', this.dragEnded.bind(this)));
const circleLists = gLists.append('circle')
.attr('r', 20)
.attr('fill', (d) => {
// TODO:根據(jù)節(jié)點選擇顏色
return '#81bef4';
});
const textLists = gLists.append('text')
.attr('class', (d) => 'node-icon icon-' + d.id)
.text(d => {
// TODO:根據(jù)節(jié)點選擇圖標
return 'ICON';
});
return { node: circleLists, textNode: textLists };
}
畫箭頭線弧度
根據(jù)需要設置不同弧度的箭頭線亚皂,變化箭頭線的形狀俱箱。給simulation.tick
生成線的時候調用
linkArc(d) {
const dx = d.target.x - d.source.x;
const dy = d.target.y - d.source.y;
const dr = Math.sqrt(dx * dx + dy * dy);
return 'M' + d.source.x + ',' + d.source.y + 'A0,' + dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y;
}
模擬節(jié)點和線的變換
initTicked(link, node) {
node
.attr('cx', d => d.x)
.attr('cy', d => d.y);
link.attr('d', d => this.linkArc(d))
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
}
拖拽
拖拽節(jié)點事件
dragStarted(d) {
const simulation = this.state.simulation;
if (!d3.event.active) simulation.alphaTarget(0.4).restart();
d.fx = d.x;
d.fy = d.y;
}
dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
dragEnded(d) {
const simulation = this.state.simulation;
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = d3.event.x;
d.fy = d3.event.y;
}
初始化對象
const svg = d3.select('#networkVisual')
.append('svg')
.attr('width', '100%')
.attr('height', '100%')
.call(d3.zoom().scaleExtent([0.2, 3]).on('zoom', () => {
d3.zoomTransform(d3.selectAll());
}));
const width = window.innerWidth;
const height = window.innerHeight - 80;
const eles = data.elements;
const simulation = d3.forceSimulation()
.force('link', d3.forceLink().id(d => d.id).distance(60))
.force('charge', d3.forceManyBody().strength(-300).distanceMin(100).distanceMax(800))
.force('collision', d3.forceCollide(40))
.force('center', d3.forceCenter(width / 2, height / 2));
this.setState({ svg, simulation }, () => {
this.initMarker();
const link = this.initLink(eles.edges);
const { node } = this.initNode(eles.nodes);
node.append('title')
.text((d) => d.id);
simulation
.nodes(eles.nodes)
.on('tick', this.initTicked.bind(this, link, node));
simulation.force('link')
.links(eles.edges);
});
到目前基本點的繪制完成。
以上就是整個繪制流程灭必。選擇是使用svg方式繪制關系圖譜狞谱,整個dom非常好操作,如果需要新增其他功能可以在dom節(jié)點上進行操作禁漓。