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

时间:2014-11-27 18:44:11

标签: javascript d3.js

使用d3树布局更新模型的最简单方法是什么。

这是一个例子 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;
}

1 个答案:

答案 0 :(得分:3)

你非常接近。您已将所有图纸代码提取到update,并且您已经注意到您需要再次调用它。您需要弄清楚如何修改模型以响应用户点击,然后使用新模型调用update

您遇到的事情是,当您再次呼叫update时,某些DOM节点已经在屏幕上。也就是说,输入选择将为空,但更新选择不会。最简单,最丑陋的方法是删除并重新添加所有节点:

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

General Update Pattern中解释了更好的方法(请务必查看全部三个)。您还应该阅读.enter() docs的最后一段。