动态力导向图d3.js

时间:2017-10-13 10:40:07

标签: javascript d3.js d3-force-directed

我正在尝试创建一个动态d3.js力导向图表。我使用generateData函数创建一些数据,然后使用updateData每3秒更新一次这些数据。

初始数据显示良好,但随后我添加的新节点堆叠在SVG的左上方,链接完全消失。

希望有更多d3.js经验的人可以帮助我。请参阅下面的代码。



<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.0/Chart.bundle.min.js"></script>
<canvas id="myChart" width="400" height="400"></canvas>
&#13;
let nodes = [];
let previousNodes = [];
let links = [];

let index = 0;
const familyTypes = ['Temperature', 'Energy', 'Thermostat', 'Humidity', 'Light']

generateData = (num) => {
  const sensors = [];
  for (let i = 0; i < num; i++) {
    index++;
    const sensor = {
      id: (index).toString(),
      familyType: familyTypes[Math.floor(Math.random() * 5)]
    };
    sensors.push(sensor);
  }
  return sensors;
}

updateData = (currentSensors) => {
  const resultSensors = JSON.parse(JSON.stringify(currentSensors));

  // Removes elements
  resultSensors.splice(2, 5);

  // Adds elements
  const newSensors = generateData(5);
  newSensors.map(sensor => resultSensors.push(sensor));

  return resultSensors;
}

updateNode = (newNodes) => {
  const resultNodes = JSON.parse(JSON.stringify(previousNodes));
  const previousIds = previousNodes.map(node => node.id);
  const ids = newNodes.map(node => node.id);

  const idAdded = ids.filter(node => previousIds.indexOf(node) === -1);
  const idRemoved = previousIds.filter(node => ids.indexOf(node) === -1);

  const nodeRemoved = previousNodes.filter(previousNode => idRemoved.indexOf(previousNode.id) > -1);
  const nodeAdded = newNodes.filter(node => idAdded.indexOf(node.id) > -1);

  nodeRemoved.forEach(node => resultNodes.splice(previousNodes.indexOf(node), 1));
  nodeAdded.forEach(node => resultNodes.push(node));

  previousNodes = JSON.parse(JSON.stringify(newNodes));
  return resultNodes;
}

computeLinks = (newNodes) => {
  const groupedObject = _.groupBy(nodes, 'familyType');
  newLinks = [];
  Object.keys(groupedObject).map((key, i) => {
    const sensorsByType = groupedObject[key];
    for (let j = 0; j < sensorsByType.length; j++) {
      if (j > 0) newLinks.push({ source: sensorsByType[j], target: sensorsByType[j - 1] });
    }
  });

  return newLinks;
}

var width = window.innerWidth
var height = window.innerHeight

var color = d3.scaleOrdinal(d3.schemeCategory20);

var svg = d3.select('svg')
svg.attr('width', width).attr('height', height)

var linkElements,
  nodeElements,
  textElements

var linkGroup = svg.append('g').attr('class', 'links')
var nodeGroup = svg.append('g').attr('class', 'nodes')
var textGroup = svg.append('g').attr('class', 'texts')

var simulation = d3
  .forceSimulation()
  .force('link', d3.forceLink().id(d => d.id))
  .force('charge', d3.forceManyBody())
  .force('center', d3.forceCenter(width / 2, height / 2));

var data = generateData(20);
nodes = updateNode(data);
links = computeLinks(data);

updateGraph = () => {
  // links
  linkElements = linkGroup.selectAll('line')
    .data(links, link =>  link.target.id + link.source.id)

  linkElements.exit().remove()

  var linkEnter = linkElements
    .enter().append('line')
    .attr('stroke-width', 1)
    .attr('stroke', '#d2d7d3')

  linkElements = linkEnter.merge(linkElements)

  // nodes
  nodeElements = nodeGroup.selectAll('circle')
    .data(nodes, node => node.id)

  nodeElements.exit().remove()

  var nodeEnter = nodeElements
    .enter()
    .append('circle')
    .attr('r', 5)
    .attr('fill', node => color(node.familyType))
    .call(d3.drag()
      .on('start', dragstarted)
      .on('drag', dragged)
      .on('end', dragended));

  nodeElements = nodeEnter.merge(nodeElements)

  // texts
  textElements = textGroup.selectAll('text')
    .data(nodes, node => node.id )

  textElements.exit().remove()

  var textEnter = textElements
    .enter()
    .append('text')
    .text(node => node.familyType )
    .attr('font-size', 15)
    .attr('dx', 15)
    .attr('dy', 4)

  textElements = textEnter.merge(textElements)
}

dragstarted = (d) => {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

dragged = (d) => {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

dragended = (d) => {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

function updateSimulation() {
  updateGraph()

  simulation.nodes(nodes).on('tick', () => {
    nodeElements
      .attr('cx', function (node) { return node.x })
      .attr('cy', function (node) { return node.y })
    textElements
      .attr('x', function (node) { return node.x })
      .attr('y', function (node) { return node.y })
    linkElements
      .attr('x1', function (link) { return link.source.x })
      .attr('y1', function (link) { return link.source.y })
      .attr('x2', function (link) { return link.target.x })
      .attr('y2', function (link) { return link.target.y })
  })

  simulation.force('link').links(links)
}

updateSimulation()

setInterval(() => {
  data = updateData(data);
  nodes = updateNode(data);
  updateSimulation()
}, 3000)
&#13;
&#13;
&#13;

0 个答案:

没有答案