D3.js-单击连接TreeLayout中的节点的链接时添加一个按钮

问题描述

我正在寻找一种将按钮添加到连接D3的TreeLayout中的节点的链接方法。 单击此按钮后,我必须在其下方添加一个菱形/矩形节点。下图只是UX可视化。该示例可能是D3中的简单树状布局。

enter image description here

示例D3树状布局:

var jsonData = getData();

var margin = {
    top: 20,right: 120,bottom: 20,left: 120
  },width = 960 - margin.right - margin.left,height = 500 - margin.top - margin.bottom;

var i = 0,duration = 750,root;

var tree = d3.layout.tree()
  .size([height,width]);

var diagonal = d3.svg.diagonal()
  .projection(function(d) {
    return [d.y,d.x];
  });

var svg = d3.select("body").append("svg")
  .attr("width",width + margin.right + margin.left)
  .attr("height",height + margin.top + margin.bottom)
  .append("g")
  .attr("transform","translate(" + margin.left + "," + margin.top + ")");

root = jsonData;
root.x0 = height / 2;
root.y0 = 0;

update(root);

//svg.style("height","500px");

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),links = tree.links(nodes);

  // normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 180;
  });

  // Update the nodes…
  var node = svg.selectAll("g.node")
    .data(nodes,function(d) {
      return d.id || (d.id = ++i);
    });

  // Enter any new nodes at the parent's prevIoUs position.
  var nodeEnter = node.enter().append("g")
    .attr("class","node")
    .attr("transform",function(d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
    })
    .on("click",click);

  nodeEnter.append("circle")
    .attr("r",1e-6)
    .attr('stroke',function(d) {
      return d.color ? d.color : 'blue';
    })
    .style("fill",function(d) {
      return d._children ? "#ccc" : "#fff";
    });

  nodeEnter.append("text")
    .attr("x",function(d) {
      return d.children || d._children ? -13 : 13;
    })
    .attr("dy",".35em")
    .attr("text-anchor",function(d) {
      return d.children || d._children ? "end" : "start";
    })
    .text(function(d) {
      return d.name;
    })
    .style("fill-opacity",1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform",function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

  nodeUpdate.select("circle")
    .attr("r",10)
    .style("fill",function(d) {
      var collapseColor = d.color ? d.color : '#ccc';
      return d._children ? collapseColor : "#fff";
    });

  nodeUpdate.select("text")
    .style("fill-opacity",1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform",function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  nodeExit.select("circle")
    .attr("r",1e-6);

  nodeExit.select("text")
    .style("fill-opacity",1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
    .data(links,function(d) {
      return d.target.id;
    });

  // Enter any new links at the parent's prevIoUs position.
  link.enter().insert("path","g")
    .attr("class","link")
    .attr("d",function(d) {
      var o = {
        x: source.x0,y: source.y0
      };
      return diagonal({
        source: o,target: o
      });
    });

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d",diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d",function(d) {
      var o = {
        x: source.x,y: source.y
      };
      return diagonal({
        source: o,target: o
      });
    })
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}


function getData() {
  return {
    "name": "js","parent": "null","color": "green","children": [{
        "name": "frameworks","parent": "js","color": "red","children": [{
            "name": "Angular","parent": "frameworks","size": 75
          },{
            "name": "Backbone","size": 15
          },{
            "name": "Ember","size": 5
          },{
            "name": "Aurelia","size": 5
          }
        ]
      },{
        "name": "libraries","color": "blue","children": [{
            "name": "jQuery","parent": "libraries","size": 70
          },{
            "name": "YUI","size": 30
          },{
            "name": "Dojo","size": 10
          },{
            "name": "Prototype",{
            "name": "MooTools",{
            "name": "ExtJS","size": 5
          }
        ]
      }
    ]
  };
};
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke-width: 3px;
}

.node text {
  font: 12px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>

用户单击(或将鼠标悬停在任何链接上)时,应显示“ +”按钮。我想出了绘制+的SVG路径。

<svg height="400" width="450">
  <path d= "M0 6 H12 M6 0 V12" stroke="blue" stroke-width="1.5" />
</svg>

但是我仍然坚持将其转换为按钮并将其附加到链接并使其可单击的操作。

解决方法

我将按钮添加到您的示例中,但尚未使其执行任何操作。只是,当您将鼠标悬停在链接上时,该按钮就会显示在该链接的中间,并且当您单击它时,它会记录到控制台。

现在,您将来可能需要做一些事情:

  1. Increase the hitbox链接以使其更易于使用;
  2. 通过写入全局变量clickedLink或通过在链接on('click',...)内的on('mouseenter') 内重新分配,来知道单击了哪个链接;
  3. 通过添加一个新节点并在整个数据集(简单但计算量大)或源节点及其子节点上调用update来更改数据对象。

var jsonData = getData();

var margin = {
    top: 20,right: 120,bottom: 20,left: 120
  },width = 960 - margin.right - margin.left,height = 500 - margin.top - margin.bottom;

var i = 0,duration = 750,root;

var tree = d3.layout.tree()
  .size([height,width]);

var diagonal = d3.svg.diagonal()
  .projection(function(d) {
    return [d.y,d.x];
  });

var svg = d3.select("body").append("svg")
  .attr("width",width + margin.right + margin.left)
  .attr("height",height + margin.top + margin.bottom)
  .append("g")
  .attr("transform","translate(" + margin.left + "," + margin.top + ")");

// New part
var plusButton = svg
  .append('g')
  .classed('button',true)
  .classed('hide',true)
  .on('click',function() {
    console.log("CLICKED");
  });

plusButton
  .append('rect')
  .attr('transform','translate(-8,-8)') // center the button inside the `g`
  .attr('width',16)
  .attr('height',16)
  .attr('rx',2);

plusButton
  .append('path')
  .attr('d','M-6 0 H6 M0 -6 V6');

root = jsonData;
root.x0 = height / 2;
root.y0 = 0;

update(root);

//svg.style("height","500px");

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 180;
  });

  // Update the nodes…
  var node = svg.selectAll("g.node")
    .data(nodes,function(d) {
      return d.id || (d.id = ++i);
    });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
    .attr("class","node")
    .attr("transform",function(d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
    })
    .on("click",click);

  nodeEnter.append("circle")
    .attr("r",1e-6)
    .attr('stroke',function(d) {
      return d.color ? d.color : 'blue';
    })
    .style("fill",function(d) {
      return d._children ? "#ccc" : "#fff";
    });

  nodeEnter.append("text")
    .attr("x",function(d) {
      return d.children || d._children ? -13 : 13;
    })
    .attr("dy",".35em")
    .attr("text-anchor",function(d) {
      return d.children || d._children ? "end" : "start";
    })
    .text(function(d) {
      return d.name;
    })
    .style("fill-opacity",1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform",function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

  nodeUpdate.select("circle")
    .attr("r",10)
    .style("fill",function(d) {
      var collapseColor = d.color ? d.color : '#ccc';
      return d._children ? collapseColor : "#fff";
    });

  nodeUpdate.select("text")
    .style("fill-opacity",1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform",function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  nodeExit.select("circle")
    .attr("r",1e-6);

  nodeExit.select("text")
    .style("fill-opacity",1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
    .data(links,function(d) {
      return d.target.id;
    });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path","g")
    .attr("class","link")
    .attr("d",function(d) {
      var o = {
        x: source.x0,y: source.y0
      };
      return diagonal({
        source: o,target: o
      });
    })
    // New part
    .on('mouseenter',function(d,i) {
      // Use the native SVG interface to get the bounding box to
      // calculate the center of the path
      var bbox = this.getBBox();
      var x = bbox.x + bbox.width/2,y = bbox.y + bbox.height/2;
      plusButton
        .attr('transform','translate(' + x + ',' + y + ')')
        .classed('hide',false);
    })
    .on('mouseleave',i) {
      plusButton
        .classed('hide',true);
    });

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d",diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d",function(d) {
      var o = {
        x: source.x,y: source.y
      };
      return diagonal({
        source: o,target: o
      });
    })
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}


function getData() {
  return {
    "name": "js","parent": "null","color": "green","children": [{
        "name": "frameworks","parent": "js","color": "red","children": [{
            "name": "Angular","parent": "frameworks","size": 75
          },{
            "name": "Backbone","size": 15
          },{
            "name": "Ember","size": 5
          },{
            "name": "Aurelia","size": 5
          }
        ]
      },{
        "name": "libraries","color": "blue","children": [{
            "name": "jQuery","parent": "libraries","size": 70
          },{
            "name": "YUI","size": 30
          },{
            "name": "Dojo","size": 10
          },{
            "name": "Prototype",{
            "name": "MooTools",{
            "name": "ExtJS","size": 5
          }
        ]
      }
    ]
  };
};
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke-width: 3px;
}

.node text {
  font: 12px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 2px;
}


/* New part */
.button > path {
  stroke: blue;
  stroke-width: 1.5;
}

.button > rect {
  fill: #ddd;
  stroke: grey;
  stroke-width: 1px;
}


.hide {
  opacity: 0 !important;
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>