我正在尝试使用react
和d3
创建强制布局。我被困在为什么我的链接没有被创建。点击和拖动也不起作用,但这是现在的另一个问题(如果在这个线程上也解决了,我会很高兴)。我基本上从here复制粘贴了强制布局的代码,并将其转换为丑陋的React15
代码。我的代码看起来像这样
import PropTypes from "prop-types";
import React from "react";
import { render } from "react-dom";
import d3 from "d3";
import * as force from "d3-force";
import * as selection from "d3-selection";
import * as drag from "d3-drag";
const style = {
height: "200px",
width: "415px",
// padding: '15px',
boxShadow: "grey 0px 0px 3px 1px",
margin: "5px",
paddingLeft: "15px",
marginRight: "20px"
};
export default class ForcedGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
graphData: this.props.nodeLinkObject || {
nodes: [
{ id: "node1", group: 1 },
{ id: "node2", group: 2 },
{ id: "node3", group: 3 },
{ id: "node4", group: 2 },
{ id: "node5", group: 3 },
{ id: "node6", group: 3 }
],
links: [
{ source: "node1", target: "node2", value: 5 },
{ source: "node1", target: "node4", value: 10 },
{ source: "node2", target: "node3", value: 12 },
{ source: "node4", target: "node5", value: 9 },
{ source: "node4", target: "node6", value: 3 }
]
}
};
}
shouldComponentUpdate() {
// Prevents component re-rendering
return false;
}
setRef = component => {
// D3 Code to create the chart
// using this._rootNode as container
const svg = component;
const width = 960;
const height = 600;
const simulation = force
.forceSimulation()
.force("link", force.forceLink().id(d => d.id))
.force("charge", force.forceManyBody())
.force("center", force.forceCenter(width / 2, height / 2));
const graph = this.state.graphData;
this.drawGraph(svg, graph, simulation);
};
dragstarted = (d, simulation) => {
if (!selection.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
};
dragged = d => {
d.fx = selection.event.x;
d.fy = selection.event.y;
};
dragended = (d, simulation) => {
if (!selection.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
};
drawGraph = (svg, graph, simulation) => {
const colorMap = {
"1": "red",
"2": "blue",
"3": "green"
};
const node = selection
.select(svg)
.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 10)
.attr("fill", d => colorMap[d.group])
.attr("transform", "translate(200,100)")
.call(
drag
.drag()
.on("start", d => this.dragstarted(d, simulation))
.on("drag", d => this.dragged(d))
.on("end", d => this.dragended(d, simulation))
);
const link = selection
.select(svg)
.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", d => Math.sqrt(d.value))
.attr("fill", "red")
.attr("transform", "translate(200,100)");
node.append("title").text(d => d.id);
simulation.nodes(graph.nodes).on("tick", this.ticked(link, node));
simulation.force("link").links(graph.links);
};
ticked = (link, node) => {
node.attr("cx", d => d.x * 5).attr("cy", d => d.y * 5);
link
.attr("x1", d => d.source.x * 5)
.attr("y1", d => d.source.y * 5)
.attr("x2", d => d.target.x * 5)
.attr("y2", d => d.target.y * 5);
};
render() {
return (
<div className="columns large-12 small-12 medium-12" style={style}>
<svg width="960" height="600" ref={this.setRef} />
</div>
);
}
}
ForcedGraph.propTypes = {
nodeLinkObject: PropTypes.shape({
nodes: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
group: PropTypes.number
})
),
links: PropTypes.arrayOf(
PropTypes.shape({
source: PropTypes.string,
target: PropTypes.string,
value: PropTypes.number
})
)
}).isRequired
};
render(<ForcedGraph />, document.getElementById("root"));
这里的主要问题是我的链接没有获得x
和y
属性。我通过控制台记录我的this.ticked
方法得到了很多。
ticked = (link, node) => {
node.attr("cx", d => d.x * 5).attr("cy", d => d.y * 5);
link
.attr("x1", d => {
console.log(d, d.source);
return d.source.x * 5;
})
.attr("y1", d => d.source.y * 5)
.attr("x2", d => d.target.x * 5)
.attr("y2", d => d.target.y * 5);
};
我在控制台记录中发现的非常奇怪。
该对象确实具有source.x属性,但在访问时它不存在。所以不知何故,在我访问它之后,该对象发生了变异。或者可能是别的东西。
我已复制了我的问题here
如果这个问题得到解决,我会很高兴的。它整天都在煎我的大脑。我通过单独导入模块解决了所选答案和拖拽的问题(或者至少我认为这是改变一切的内容)
import PropTypes from 'prop-types';
import React from 'react';
import {render} from 'react-dom';
import {forceSimulation, forceLink, forceManyBody, forceCenter} from 'd3-force';
import {select} from 'd3-selection';
import {drag} from 'd3-drag';
export default class ForcedGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
graphData: this.props.nodeLinkObject || {
nodes: [
{id: 'node1', group: 1},
{id: 'node2', group: 2},
{id: 'node3', group: 3},
{id: 'node4', group: 2},
{id: 'node5', group: 3},
{id: 'node6', group: 3},
],
links: [
{source: 'node1', target: 'node2', value: 5},
{source: 'node1', target: 'node4', value: 10},
{source: 'node2', target: 'node3', value: 12},
{source: 'node4', target: 'node5', value: 9},
{source: 'node4', target: 'node6', value: 3},
],
},
};
}
shouldComponentUpdate() {
// Prevents component re-rendering
return false;
}
setRef = component => {
// D3 Code to create the chart
// using this._rootNode as container
const svg = component;
const width = 415;
const height = 200;
const simulation = forceSimulation()
.force('link', forceLink().id(d => d.id))
.force('charge', forceManyBody())
.force('center', forceCenter(width / 2, height / 2));
const graph = this.state.graphData;
this.drawGraph(svg, graph, simulation);
};
dragstarted = (simulation, d) => {
if (!getEvent().active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
};
dragged = d => {
d.fx = getEvent().x;
d.fy = getEvent().y;
};
dragended = (simulation, d) => {
if (!getEvent().active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
};
drawGraph = (svg, graph, simulation) => {
const colorMap = {
'1': 'red',
'2': 'blue',
'3': 'green',
};
const node = select(svg)
.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(graph.nodes)
.enter()
.append('circle')
.attr('r', 10)
.attr('fill', d => colorMap[d.group])
.attr('transform', 'translate(0,0)')
.call(
drag()
.on('start', d => this.dragstarted(simulation, d))
.on('drag', d => this.dragged(d))
.on('end', d => this.dragended(simulation, d))
);
const link = select(svg)
.append('g')
.attr('class', 'links')
.selectAll('line')
.data(graph.links)
.enter()
.append('line')
.attr('stroke-width', d => Math.sqrt(d.value))
.attr('style', 'stroke: #999; stroke-opacity: 0.6;')
.attr('transform', 'translate(0,0)');
node.append('title').text(d => d.id);
simulation.nodes(graph.nodes).on('tick', () => this.ticked(link, node));
simulation.force('link').links(graph.links);
};
ticked = (link, node) => {
node.attr('cx', d => d.x).attr('cy', d => d.y);
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
};
render() {
return (
<div className="column large-12 medium-12 small-12" style={style}>
<svg width="415" height="200" ref={this.setRef} />
</div>
);
}
}
答案 0 :(得分:2)
看起来.on('tick', eventListener)
(https://github.com/d3/d3-force/blob/master/README.md#simulation_on)期望函数作为其第二个参数。在您的情况下,您的第二个参数是this.ticked(link, node)
,它将被调用一次并返回 undefined 。您可以勾选返回函数:
ticked = (link, node) => { //needs to return function
return () => {
node.attr("cx", d => d.x * 5).attr("cy", d => d.y * 5);
link
.attr("x1", d => {
console.log(d, d.source);
return d.source.x * 5;
})
.attr("y1", d => d.source.y * 5)
.attr("x2", d => d.target.x * 5)
.attr("y2", d => d.target.y * 5);
}
};
或者,可能更清洁并且与您在其他地方所做的一致 - 您的第二个参数可能是一个调用勾选的箭头函数:
simulation.nodes(graph.nodes).on("tick", () => this.ticked(link, node));
此外,您可能希望在链接上设置笔划(我们似乎还没有.links类的任何CSS):
const link = selection
.select(svg)
.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", d => Math.sqrt(d.value))
.attr("style", "stroke: #ff0000; stroke-opacity: 0.6;")
.attr("transform", "translate(0,0)");
关于鼠标事件监听器,看起来在代码沙箱中工作的模块化d3导入存在问题 - d3Drag看起来并没有注入正确的d3Selection实例。使用最新的独立版本的d3,拖动处理似乎按预期工作。
略微修改的工作示例:https://codesandbox.io/s/xvr5orqj4o