簡體   English   中英

如何使用React創建d3力布局圖

[英]How to create a d3 force layout graph using React

我想使用ReactJS創建一個d3力布局圖

我使用React + d3創建了其他圖形,如餅圖,折線圖,直方圖。 現在我想知道如何構建像d3力布局這樣涉及物理和用戶交互的svg圖形。

以下是我想要構建的示例http://bl.ocks.org/mbostock/4062045

由於D3和React在過去三年里沒有減少流行度,我認為一個更具體的答案可能會幫助那些想要在React中制作D3力量布局的人。

創建D3圖可以與任何其他D3圖完全相同。 但您也可以使用React替換D3的輸入,更新和退出功能。 所以React負責渲染線條,圓圈和svg。

當用戶應該能夠與圖表進行大量交互時,這可能會有所幫助。 用戶可以在圖形的節點和鏈接中添加,刪除,編輯和執行大量其他操作。

以下示例中有3個組件。 App組件保存應用程序的狀態。 特別是具有節點和鏈接數據的2個標准數組應該傳遞給D3的d3.forceSimulation函數。

然后是鏈接的一個組件和節點的一個組件。 您可以使用React通過直線和圓圈執行任何操作。 例如,您可以使用React的onClick

函數enterNode(selection)enterLink(selection)渲染線條和圓圈。 這些函數在Node和Link組件中調用。 這些組件在將節點'和鏈接'數據傳遞給這些輸入函數之前將其作為prop。

函數updateNode(selection)updateLink(selection)更新節點和鏈接的位置。 它們是從D3的tick函數調用的。

我使用了來自Shirley WuReact + D3力布局示例中的這些函數。

只能在下面的示例中添加節點。 但我希望它展示了如何使用React使力布局更具交互性。

Codepen實例

 /////////////////////////////////////////////////////////// /////// Functions and variables /////////////////////////////////////////////////////////// var FORCE = (function(nsp) { var width = 1080, height = 250, color = d3.scaleOrdinal(d3.schemeCategory10), initForce = (nodes, links) => { nsp.force = d3.forceSimulation(nodes) .force("charge", d3.forceManyBody().strength(-200)) .force("link", d3.forceLink(links).distance(70)) .force("center", d3.forceCenter().x(nsp.width / 2).y(nsp.height / 2)) .force("collide", d3.forceCollide([5]).iterations([5])); }, enterNode = (selection) => { var circle = selection.select('circle') .attr("r", 25) .style("fill", function (d) { if (d.id > 3) { return 'darkcyan' } else { return 'tomato' }}) .style("stroke", "bisque") .style("stroke-width", "3px") selection.select('text') .style("fill", "honeydew") .style("font-weight", "600") .style("text-transform", "uppercase") .style("text-anchor", "middle") .style("alignment-baseline", "middle") .style("font-size", "10px") .style("font-family", "cursive") }, updateNode = (selection) => { selection .attr("transform", (d) => "translate(" + dx + "," + dy + ")") .attr("cx", function(d) { return dx = Math.max(30, Math.min(width - 30, dx)); }) .attr("cy", function(d) { return dy = Math.max(30, Math.min(height - 30, dy)); }) }, enterLink = (selection) => { selection .attr("stroke-width", 3) .attr("stroke", "bisque") }, updateLink = (selection) => { selection .attr("x1", (d) => d.source.x) .attr("y1", (d) => d.source.y) .attr("x2", (d) => d.target.x) .attr("y2", (d) => d.target.y); }, updateGraph = (selection) => { selection.selectAll('.node') .call(updateNode) selection.selectAll('.link') .call(updateLink); }, dragStarted = (d) => { if (!d3.event.active) nsp.force.alphaTarget(0.3).restart(); d.fx = dx; d.fy = dy }, dragging = (d) => { d.fx = d3.event.x; d.fy = d3.event.y }, dragEnded = (d) => { if (!d3.event.active) nsp.force.alphaTarget(0); d.fx = null; d.fy = null }, drag = () => d3.selectAll('g.node') .call(d3.drag() .on("start", dragStarted) .on("drag", dragging) .on("end", dragEnded) ), tick = (that) => { that.d3Graph = d3.select(ReactDOM.findDOMNode(that)); nsp.force.on('tick', () => { that.d3Graph.call(updateGraph) }); }; nsp.width = width; nsp.height = height; nsp.enterNode = enterNode; nsp.updateNode = updateNode; nsp.enterLink = enterLink; nsp.updateLink = updateLink; nsp.updateGraph = updateGraph; nsp.initForce = initForce; nsp.dragStarted = dragStarted; nsp.dragging = dragging; nsp.dragEnded = dragEnded; nsp.drag = drag; nsp.tick = tick; return nsp })(FORCE || {}) //////////////////////////////////////////////////////////////////////////// /////// class App is the parent component of Link and Node //////////////////////////////////////////////////////////////////////////// class App extends React.Component { constructor(props) { super(props) this.state = { addLinkArray: [], name: "", nodes: [{ "name": "fruit", "id": 0 }, { "name": "apple", "id": 1 }, { "name": "orange", "id": 2 }, { "name": "banana", "id": 3 } ], links: [{ "source": 0, "target": 1, "id": 0 }, { "source": 0, "target": 2, "id": 1 }, { "source": 0, "target": 3, "id": 2 } ] } this.handleAddNode = this.handleAddNode.bind(this) this.addNode = this.addNode.bind(this) } componentDidMount() { const data = this.state; FORCE.initForce(data.nodes, data.links) FORCE.tick(this) FORCE.drag() } componentDidUpdate(prevProps, prevState) { if (prevState.nodes !== this.state.nodes || prevState.links !== this.state.links) { const data = this.state; FORCE.initForce(data.nodes, data.links) FORCE.tick(this) FORCE.drag() } } handleAddNode(e) { this.setState({ [e.target.name]: e.target.value }); } addNode(e) { e.preventDefault(); this.setState(prevState => ({ nodes: [...prevState.nodes, { name: this.state.name, id: prevState.nodes.length + 1, x: FORCE.width / 2, y: FORCE.height / 2 }], name: '' })); } render() { var links = this.state.links.map((link) => { return ( < Link key = { link.id } data = { link } />); }); var nodes = this.state.nodes.map((node) => { return ( < Node data = { node } name = { node.name } key = { node.id } />); }); return ( < div className = "graph__container" > < form className = "form-addSystem" onSubmit = { this.addNode.bind(this) } > < h4 className = "form-addSystem__header" > New Node < /h4> < div className = "form-addSystem__group" > < input value = { this.state.name } onChange = { this.handleAddNode.bind(this) } name = "name" className = "form-addSystem__input" id = "name" placeholder = "Name" / > < label className = "form-addSystem__label" htmlFor = "title" > Name < /label> < / div > < div className = "form-addSystem__group" > < input className = "btnn" type = "submit" value = "add node" / > < /div> < / form > < svg className = "graph" width = { FORCE.width } height = { FORCE.height } > < g > { links } < /g> < g > { nodes } < /g> < / svg > < /div> ); } } /////////////////////////////////////////////////////////// /////// Link component /////////////////////////////////////////////////////////// class Link extends React.Component { componentDidMount() { this.d3Link = d3.select(ReactDOM.findDOMNode(this)) .datum(this.props.data) .call(FORCE.enterLink); } componentDidUpdate() { this.d3Link.datum(this.props.data) .call(FORCE.updateLink); } render() { return ( < line className = 'link' / > ); } } /////////////////////////////////////////////////////////// /////// Node component /////////////////////////////////////////////////////////// class Node extends React.Component { componentDidMount() { this.d3Node = d3.select(ReactDOM.findDOMNode(this)) .datum(this.props.data) .call(FORCE.enterNode) } componentDidUpdate() { this.d3Node.datum(this.props.data) .call(FORCE.updateNode) } render() { return ( < g className = 'node' > < circle onClick = { this.props.addLink } /> < text > { this.props.data.name } < /text> < / g > ); } } ReactDOM.render( < App / > , document.querySelector('#root')) 
 .graph__container { display: grid; grid-template-columns: 1fr 1fr; } .graph { background-color: steelblue; } .form-addSystem { display: grid; grid-template-columns: min-content min-content; background-color: aliceblue; padding-bottom: 15px; margin-right: 10px; } .form-addSystem__header { grid-column: 1/-1; text-align: center; margin: 1rem; padding-bottom: 1rem; text-transform: uppercase; text-decoration: none; font-size: 1.2rem; color: steelblue; border-bottom: 1px dotted steelblue; font-family: cursive; } .form-addSystem__group { display: grid; margin: 0 1rem; align-content: center; } .form-addSystem__input, input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active { outline: none; border: none; border-bottom: 3px solid teal; padding: 1.5rem 2rem; border-radius: 3px; background-color: transparent; color: steelblue; transition: all .3s; font-family: cursive; transition: background-color 5000s ease-in-out 0s; } .form-addSystem__input:focus { outline: none; background-color: platinum; border-bottom: none; } .form-addSystem__input:focus:invalid { border-bottom: 3px solid steelblue; } .form-addSystem__input::-webkit-input-placeholder { color: steelblue; } .btnn { text-transform: uppercase; text-decoration: none; border-radius: 10rem; position: relative; font-size: 12px; height: 30px; align-self: center; background-color: cadetblue; border: none; color: aliceblue; transition: all .2s; } .btnn:hover { transform: translateY(-3px); box-shadow: 0 1rem 2rem rgba(0, 0, 0, .2) } .btnn:hover::after { transform: scaleX(1.4) scaleY(1.6); opacity: 0; } .btnn:active, .btnn:focus { transform: translateY(-1px); box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .2); outline: 0; } .form-addSystem__label { color: lightgray; font-size: 20px; font-family: cursive; font-weight: 700; margin-left: 1.5rem; margin-top: .7rem; display: block; transition: all .3s; } .form-addSystem__input:placeholder-shown+.form-addSystem__label { opacity: 0; visibility: hidden; transform: translateY(-4rem); } .form-addSystem__link { grid-column: 2/4; justify-self: center; align-self: center; text-transform: uppercase; text-decoration: none; font-size: 1.2rem; color: steelblue; } 
 <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> </script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script> <div id="root"></div> 

Colin Megill有一篇很棒的博客文章: http//formidable.com/blog/2015/05/21/react-d3-layouts/ 還有一個工作jsbin http://jsbin.com/fanofa/14/embed?js,output 有一個b.locks.org帳戶,JMStewart,他有一個有趣的實現,它在d3代碼中包含了React: http ://bl.ocks.org/JMStewart/f0dc27409658ab04d1c8。

在React中實施強制布局的每個人都注意到輕微的性能損失。 對於復雜的圖表(超過100個節點),這變得更加嚴重。

注意:對於施加力的反應運動存在一個未解決的問題 (否則這將是一個很好的反應解決方案),但它已經沉默。

**這不是回答,但是STACKOVERFLOW沒有為我添加評論的設施。 **

我的問題是對文森特來說。 代碼完美編譯,但是當我運行它時,背景會以藍色繪制,但圖形實際上會在左上角呈現4個點。 這一切都得到了解決。 我嘗試了可能的方法,但似乎總是在左上角只有4個點得到相同的結果。 我的電子郵件ID是RVELUTHATTIL@YAHOO.COM。 如果您有這個問題可以讓我知道,我將不勝感激

 /////////////////////////////////////////////////////////// /////// Functions and variables /////////////////////////////////////////////////////////// var FORCE = (function(nsp) { var width = 1080, height = 250, color = d3.scaleOrdinal(d3.schemeCategory10), initForce = (nodes, links) => { nsp.force = d3.forceSimulation(nodes) .force("charge", d3.forceManyBody().strength(-200)) .force("link", d3.forceLink(links).distance(70)) .force("center", d3.forceCenter().x(nsp.width / 2).y(nsp.height / 2)) .force("collide", d3.forceCollide([5]).iterations([5])); }, enterNode = (selection) => { var circle = selection.select('circle') .attr("r", 25) .style("fill", function (d) { if (d.id > 3) { return 'darkcyan' } else { return 'tomato' }}) .style("stroke", "bisque") .style("stroke-width", "3px") selection.select('text') .style("fill", "honeydew") .style("font-weight", "600") .style("text-transform", "uppercase") .style("text-anchor", "middle") .style("alignment-baseline", "middle") .style("font-size", "10px") .style("font-family", "cursive") }, updateNode = (selection) => { selection .attr("transform", (d) => "translate(" + dx + "," + dy + ")") .attr("cx", function(d) { return dx = Math.max(30, Math.min(width - 30, dx)); }) .attr("cy", function(d) { return dy = Math.max(30, Math.min(height - 30, dy)); }) }, enterLink = (selection) => { selection .attr("stroke-width", 3) .attr("stroke", "bisque") }, updateLink = (selection) => { selection .attr("x1", (d) => d.source.x) .attr("y1", (d) => d.source.y) .attr("x2", (d) => d.target.x) .attr("y2", (d) => d.target.y); }, updateGraph = (selection) => { selection.selectAll('.node') .call(updateNode) selection.selectAll('.link') .call(updateLink); }, dragStarted = (d) => { if (!d3.event.active) nsp.force.alphaTarget(0.3).restart(); d.fx = dx; d.fy = dy }, dragging = (d) => { d.fx = d3.event.x; d.fy = d3.event.y }, dragEnded = (d) => { if (!d3.event.active) nsp.force.alphaTarget(0); d.fx = null; d.fy = null }, drag = () => d3.selectAll('g.node') .call(d3.drag() .on("start", dragStarted) .on("drag", dragging) .on("end", dragEnded) ), tick = (that) => { that.d3Graph = d3.select(ReactDOM.findDOMNode(that)); nsp.force.on('tick', () => { that.d3Graph.call(updateGraph) }); }; nsp.width = width; nsp.height = height; nsp.enterNode = enterNode; nsp.updateNode = updateNode; nsp.enterLink = enterLink; nsp.updateLink = updateLink; nsp.updateGraph = updateGraph; nsp.initForce = initForce; nsp.dragStarted = dragStarted; nsp.dragging = dragging; nsp.dragEnded = dragEnded; nsp.drag = drag; nsp.tick = tick; return nsp })(FORCE || {}) //////////////////////////////////////////////////////////////////////////// /////// class App is the parent component of Link and Node //////////////////////////////////////////////////////////////////////////// class App extends React.Component { constructor(props) { super(props) this.state = { addLinkArray: [], name: "", nodes: [{ "name": "fruit", "id": 0 }, { "name": "apple", "id": 1 }, { "name": "orange", "id": 2 }, { "name": "banana", "id": 3 } ], links: [{ "source": 0, "target": 1, "id": 0 }, { "source": 0, "target": 2, "id": 1 }, { "source": 0, "target": 3, "id": 2 } ] } this.handleAddNode = this.handleAddNode.bind(this) this.addNode = this.addNode.bind(this) } componentDidMount() { const data = this.state; FORCE.initForce(data.nodes, data.links) FORCE.tick(this) FORCE.drag() } componentDidUpdate(prevProps, prevState) { if (prevState.nodes !== this.state.nodes || prevState.links !== this.state.links) { const data = this.state; FORCE.initForce(data.nodes, data.links) FORCE.tick(this) FORCE.drag() } } handleAddNode(e) { this.setState({ [e.target.name]: e.target.value }); } addNode(e) { e.preventDefault(); this.setState(prevState => ({ nodes: [...prevState.nodes, { name: this.state.name, id: prevState.nodes.length + 1, x: FORCE.width / 2, y: FORCE.height / 2 }], name: '' })); } render() { var links = this.state.links.map((link) => { return ( < Link key = { link.id } data = { link } />); }); var nodes = this.state.nodes.map((node) => { return ( < Node data = { node } name = { node.name } key = { node.id } />); }); return ( < div className = "graph__container" > < form className = "form-addSystem" onSubmit = { this.addNode.bind(this) } > < h4 className = "form-addSystem__header" > New Node < /h4> < div className = "form-addSystem__group" > < input value = { this.state.name } onChange = { this.handleAddNode.bind(this) } name = "name" className = "form-addSystem__input" id = "name" placeholder = "Name" / > < label className = "form-addSystem__label" htmlFor = "title" > Name < /label> < / div > < div className = "form-addSystem__group" > < input className = "btnn" type = "submit" value = "add node" / > < /div> < / form > < svg className = "graph" width = { FORCE.width } height = { FORCE.height } > < g > { links } < /g> < g > { nodes } < /g> < / svg > < /div> ); } } /////////////////////////////////////////////////////////// /////// Link component /////////////////////////////////////////////////////////// class Link extends React.Component { componentDidMount() { this.d3Link = d3.select(ReactDOM.findDOMNode(this)) .datum(this.props.data) .call(FORCE.enterLink); } componentDidUpdate() { this.d3Link.datum(this.props.data) .call(FORCE.updateLink); } render() { return ( < line className = 'link' / > ); } } /////////////////////////////////////////////////////////// /////// Node component /////////////////////////////////////////////////////////// class Node extends React.Component { componentDidMount() { this.d3Node = d3.select(ReactDOM.findDOMNode(this)) .datum(this.props.data) .call(FORCE.enterNode) } componentDidUpdate() { this.d3Node.datum(this.props.data) .call(FORCE.updateNode) } render() { return ( < g className = 'node' > < circle onClick = { this.props.addLink } /> < text > { this.props.data.name } < /text> < / g > ); } } ReactDOM.render( < App / > , document.querySelector('#root')) 
 .graph__container { display: grid; grid-template-columns: 1fr 1fr; } .graph { background-color: steelblue; } .form-addSystem { display: grid; grid-template-columns: min-content min-content; background-color: aliceblue; padding-bottom: 15px; margin-right: 10px; } .form-addSystem__header { grid-column: 1/-1; text-align: center; margin: 1rem; padding-bottom: 1rem; text-transform: uppercase; text-decoration: none; font-size: 1.2rem; color: steelblue; border-bottom: 1px dotted steelblue; font-family: cursive; } .form-addSystem__group { display: grid; margin: 0 1rem; align-content: center; } .form-addSystem__input, input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active { outline: none; border: none; border-bottom: 3px solid teal; padding: 1.5rem 2rem; border-radius: 3px; background-color: transparent; color: steelblue; transition: all .3s; font-family: cursive; transition: background-color 5000s ease-in-out 0s; } .form-addSystem__input:focus { outline: none; background-color: platinum; border-bottom: none; } .form-addSystem__input:focus:invalid { border-bottom: 3px solid steelblue; } .form-addSystem__input::-webkit-input-placeholder { color: steelblue; } .btnn { text-transform: uppercase; text-decoration: none; border-radius: 10rem; position: relative; font-size: 12px; height: 30px; align-self: center; background-color: cadetblue; border: none; color: aliceblue; transition: all .2s; } .btnn:hover { transform: translateY(-3px); box-shadow: 0 1rem 2rem rgba(0, 0, 0, .2) } .btnn:hover::after { transform: scaleX(1.4) scaleY(1.6); opacity: 0; } .btnn:active, .btnn:focus { transform: translateY(-1px); box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .2); outline: 0; } .form-addSystem__label { color: lightgray; font-size: 20px; font-family: cursive; font-weight: 700; margin-left: 1.5rem; margin-top: .7rem; display: block; transition: all .3s; } .form-addSystem__input:placeholder-shown+.form-addSystem__label { opacity: 0; visibility: hidden; transform: translateY(-4rem); } .form-addSystem__link { grid-column: 2/4; justify-self: center; align-self: center; text-transform: uppercase; text-decoration: none; font-size: 1.2rem; color: steelblue; } 
 <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> </script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script> <div id="root"></div> 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM