我设法创建了一个Beeswarm图,该图基于通过下拉列表选择的新数据来更新节点位置。但是,我无法对节点进行编程以使用正确的运动来移动位置。当前,在初始渲染时,我看到一个径向聚类/碰撞运动,该运动定位了所有节点。每次更改下拉选择时,都会再次发生此动作以渲染新节点。但是,我希望看到一个不错的“拉动”动作,将节点从当前位置移动到新位置。
我正在使用React绘制所有节点,d3计算x和y。
Beeswarm组件
class BeeswarmPlot extends Component {
createNodes = (data) => {
...
}
render() {
// Receives data from another component and creates nodes for each record
var nodes = this.createNodes(this.props.data)
return (
<svg>
<ForceGraph
nodes={nodes}
/>
</svg>
)
}
}
export default BeeswarmPlot;
强制布局组件
class ForceGraph extends Component {
constructor() {
super();
this.state = {nodes: []};
}
componentDidMount() {
this.updateNodePositions(this.props.nodes)
}
componentDidUpdate(prevProps, prevState) {
if (this.props.nodes != prevProps.nodes) {
this.updateNodePositions(this.props.nodes)
}
}
componentWillUnmount() {
this.force.stop()
}
updateNodePositions = (nodes) => {
this.force = d3.forceSimulation(nodes)
.force("x", d3.forceX(d => d.cx))
.force("y", d3.forceY(d => d.cy))
.force("collide", d3.forceCollide(3))
this.force.on('tick', () => this.setState({nodes}))
}
render() {
const {nodes} = this.state
return (
<Dots
data={nodes}
/>
)
}
}
export default ForceGraph;
编辑:使用状态更新从父级收到的作为道具的节点位置是一个坏主意,因为页面会随着每个状态的更改而重新呈现。我将永远无法实现节点位置的过渡运动!
因此,我决定将方法更改为使用道具来更新节点位置而不更改状态。 但是,现在,我无法移动节点位置,无论我在下拉菜单中选择什么,这些位置都将保持在原处。export default class BubbleChart extends Component {
constructor(props) {
super(props)
this.bubbles = null;
this.nodes = [];
this.forceStrength = 0.03;
// Initialize a template of force layout.
this.simulation = d3.forceSimulation()
.velocityDecay(0.2)
.force('x', d3.forceX().strength(this.forceStrength).x(d => d.x))
.force('y', d3.forceY().strength(this.forceStrength).y(d => d.y))
.force('charge', d3.forceManyBody().strength(this.charge))
.force("collide", d3.forceCollide(3))
}
componentDidMount() {
this.container = select(this.refs.container)
// Calculate node positions from raw data received. The output of this function will create positions that overlap which each other. Separation of the nodes will be dealt with in force layout
this.createNodes()
// Create circles for the nodes
this.renderNodes()
// ticked function helps to adjust node positions based on what i specifies in force layout template above
this.simulation.nodes(this.nodes)
.on('tick', this.ticked)
}
// When new props (raw data) are received, this fires off
componentDidUpdate() {
// Recalculates node positions based on new props
this.createNodes()
// Adjust node positions
this.simulation.nodes(this.nodes)
.on('tick', this.ticked)
// I think this 're-energises' force layout to enable nodes to move to their new positions
this.simulation.alpha(1).restart()
}
charge = (d) => {
return -Math.pow(d.radius, 2.0) * this.forceStrength;
}
createNodes = () => {
var data = this.props.lapsData.slice()
....MORE CODE.....
this.nodes = nodes
}
renderNodes = () => {
this.bubbles = this.container.selectAll('.bubble')
.data(this.nodes, d => d.id)
.enter().append('circle')
.attr('r', d => d.radius)
.attr('fill', d => d.color)
}
ticked = () => {
this.bubbles
.attr('cx', d => d.x)
.attr('cy', d => d.y)
}
render() {
return (
<svg width={this.wrapper.width} height={this.wrapper.height}>
<g transform={"translate(" + (this.axisSpace.width + this.margins.left) + "," + (this.margins.top) + ")"} ref='container' />
</svg>
)
}
}