未在强制布局d3上绘制链接

时间:2018-04-10 14:17:43

标签: reactjs d3.js force-layout

我正在尝试使用reactd3创建强制布局。我被困在为什么我的链接没有被创建。点击和拖动也不起作用,但这是现在的另一个问题(如果在这个线程上也解决了,我会很高兴)。我基本上从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"));

这里的主要问题是我的链接没有获得xy属性。我通过控制台记录我的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);
  };

我在控制台记录中发现的非常奇怪。

enter image description here

该对象确实具有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>
    );
  }
}

1 个答案:

答案 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