Реакция + d3v4: проблема с перемещением узлов в Beeswarm Chart с использованием правильного движения - PullRequest
0 голосов
/ 09 ноября 2018

Мне удалось создать график 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;

Компонент макета Force

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>
    )
  }


}
...