简体   繁体   English

d3使用动态数据更新树布局

[英]d3 update tree layout with dynamic data

What's the easiest way to update model with d3 tree layout. 用d3树形布局更新模型的最简单方法是什么。

here's the example http://jsfiddle.net/mnk/vfro9tkz/ 这是示例http://jsfiddle.net/mnk/vfro9tkz/

var data = {
  name: 'Music',
  children: [{
    name: 'Rock',
    children: [{
      name: 'Two'
    }, {
      name: 'Three',
      children: [{
        name: 'A'
      }, {
        name: 'Bonjourallo'
      }, {
        name: 'Coco coco coco coco'
      }]
    }, {
      name: 'Four'
    }]
  }, {
    name: 'Rap',
    children: [{
      name: 'Hip-Hop/Rap'
    }]
  }]
};
var svg = d3.select('body').append('svg');

svg.attr('width', 700)
  .attr('height', 400);

var tree = d3.layout.tree().size([600, 300]);

function update(model) {

  var nodes = tree.nodes(model);
  var links = tree.links(nodes);

  nodes.forEach(function(d, i) {
    d.index = d.parent ? d.parent.children.indexOf(d) : 0;
    d.width = getNameLength(d.name);

    if (!hasNephewOrChildren(d)) {
      d.x = getHorizontalPosition(d)
      d.y = (d.parent ? d.parent.y : 0) + 40;
      d.mode = 'horizontal';
    } else {
      d.x = d.depth * 60;
      d.y = getVerticalPosition(d);
      d.mode = 'vertical';
    }
  });

  var node = svg.selectAll('.node')
    .data(nodes)
    .enter()
    .append('g').attr('class', 'node')
    .attr('opacity', 1)
    .attr('visibility', function(d) {
      return d.depth ? 'visible' : 'hidden'
    })
    .attr('transform', function(d, i) {
      return 'translate(' + d.x + ',' + d.y + ')'
    });


  var lineFunction = d3.svg.line()
    .x(function(d) {
      return d.x;
    })
    .y(function(d) {
      return d.y;
    })
    .interpolate("linear");

  var paths = svg.selectAll('g.node').append("path")
    .attr("d", function(d) {
      return lineFunction(generatePath(d));
    })
    .attr("stroke", "#aaa")
    .attr("stroke-width", 1)
    .attr("fill", "none");

  function generatePath(d) {
    var points = [];

    if (d.depth > 1) {
      if (d.mode === 'horizontal') {
        points.push({
          x: d.parent.x - d.x + d.parent.width,
          y: -25
        });
        points.push({
          x: d.width / 2,
          y: -25
        });
        points.push({
          x: d.width / 2,
          y: 0
        });
      } else {
        points.push({
          x: d.parent.x - d.x + d.parent.width / 2,
          y: d.parent.y - d.y + 30
        });
        points.push({
          x: d.parent.x - d.x + d.parent.width / 2,
          y: 15
        });
        points.push({
          x: d.parent.x - d.x + d.parent.width / 2 + 15,
          y: 15
        });
      }
    }
    return points;
  }

  node.append('rect')
    .attr('class', 'rect')
    .attr('width', function(d, i) {
      return d.width
    })
    .attr('height', 30)
    .attr('rx', 15)

  node.append('text')
    .text(function(d) {
      return d.name
    })
    .attr('x', 10)
    .attr('y', 20);

  var close = node.append('g')
    .attr('class', 'remove-icon-group')
    .on('click', function(d) {
      console.log('todo remove d and all childrens.');
      // update(model);
    })
    .attr('transform', function(d, i) {
      return 'translate(' + (d.width - 15) + ',15)'
    });

  close.append('circle')
    .attr('class', 'remove-icon')
    .attr('r', 10)

  close.append('line')
    .attr('x1', -4)
    .attr('x2', 4)
    .attr('y1', -4)
    .attr('y2', 4)
    .attr('stroke', '#a0a0a0')
    .attr('stroke-width', 1);


  close.append('line')
    .attr('x1', 4)
    .attr('x2', -4)
    .attr('y1', -4)
    .attr('y2', 4)
    .attr('stroke', '#a0a0a0')
    .attr('stroke-width', 1);
}

update(data);

function getLastDescendant(d) {
  if (d.children && d.children.length) {
    return getLastDescendant(d.children[d.children.length - 1]);
  }

  return d;
}

function hasNephewOrChildren(d) {
  var siblings = d.parent ? d.parent.children : [d];
  var hasChildren = false;

  siblings.forEach(function(sibling) {
    if (sibling.children && sibling.children.length) {
      hasChildren = true;
    }
  });

  return hasChildren;
}

function getHorizontalPosition(d) {
  if (d.index === 0) {
    return d.parent ? d.parent.x + 60 : 0;
  }

  var prevSibling = d.parent.children[d.index - 1];

  return prevSibling.x + prevSibling.width + 10;
}

function getVerticalPosition(d) {
  var prevY = (d.parent ? d.parent.y : -40);

  if (d.index) {
    var prevSibling = d.parent.children[d.index - 1];
    var lastDescendant = getLastDescendant(prevSibling);
    prevY = lastDescendant.y;
  }

  return prevY + 40;
}

function getNameLength(str) {
  var length = str.length * 8;
  return length < 60 ? 60 + 30 : length + 30;
}

You're very close. 你很亲密 You already have all the drawing code extracted to update , and there's a place where you've commented out that you need to call it again. 您已经提取了所有绘制代码​​以进行update ,并且在某个地方已注释掉需要再次调用它。 You need to figure out how to modify the model in response to user clicks, and then call update with the new model. 您需要弄清楚如何响应用户单击来修改模型,然后使用新模型调用update

The thing you'll encounter is that when you call update again, some DOM nodes will already be onscreen. 您将遇到的事情是,当您再次调用update时,某些DOM节点将已经出现在屏幕上。 That is, the enter selection will be empty, but the update selection will not be. 也就是说,输入选择将为空,但更新选择将不会为空。 The simplest, and ugliest, way to handle this is to remove and re-add all the nodes: 处理此问题的最简单,最丑陋的方法是删除并重新添加所有节点:

svg.selectAll('.node').remove();
svg.selectAll('.node')
    .data(nodes)
    .enter()
    .append("g") // and so on

The better way to do it is explained in the General Update Pattern (be sure to see all three). 常规更新模式中介绍了执行此操作的更好方法(请确保同时查看所有三个方法)。 You should also read the last paragraph of the .enter() docs . 您还应该阅读.enter()docs的最后一段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM