D3.js SVG没有在正确的节点位置绘制 [英] D3.js SVGs are not being drawn at the right node position

查看:80
本文介绍了D3.js SVG没有在正确的节点位置绘制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现在不同节点绘制不同svg的树结构。

I am trying to implement a tree structure with different svgs drawn at different nodes.

这是小提琴- https://jsfiddle.net/L3j7voar/

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

var emptyDecisionBox = {
  "name": "newDecision",
  "id": "newId",
  "value": "notSure",
  "condition": "true",
};

var selectedNode;


var root = {
  "name": "Root",
  "type": "decision",
  "children": [{
      "name": "analytics",
      "type": "decision",
      "value": "a+b",
      "children": [{
        "name": "distinction",
        "type": "action",
        "condition": "true",
        "value": "5",
      }, {
        "name": "nonDistinction",
        "type": "action",
        "condition": "false",
        "value": "4"
      }],
    },
    {
      "name": "division",
      "type": "action",
      "value": "a-b",
      "children": [],
    }
  ]
};

var i = 0,
  duration = 750,
  rectW = 80,
  rectH = 40;

var tree = d3.layout.tree().nodeSize([120, 90]);



//LINK FUNCTION TO DRAW LINKS 
var linkFunc = function(d) {
  var source = {
    x: d.source.x,
    y: d.source.y + (rectH / 2)
  };
  var target = {
    x: d.target.x + (rectW / 2),
    y: d.target.y
  };

  // This is where the line bends
  var inflection = {
    x: target.x,
    y: source.y
  };
  var radius = 5;

  var result = "M" + source.x + ',' + source.y;
  
  if (source.x < target.x) {
    // Child is to the right of the parent
    result += ' H' + (inflection.x - radius);
  } else {
    result += ' H' + (inflection.x + radius);
  }

  // Curve the line at the bend slightly
  result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);

  result += 'V' + target.y;
  return result;
}

// END OF LINK FUNC //





// DRAW TREE //
var svg = d3.select(".tree-diagram").append("svg").attr("width", 1000).attr("height", 1000)
  .call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
  .attr("transform", "translate(" + 350 + "," + 20 + ")");

//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);

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

update(root);

d3.select(".tree-diagram").style("height", "1000px");

// END OF DRAW TREEE //



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 * 90;
  });

  // 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.x0 + "," + source.y0 + ")";
    })
    .on("click", click);
  
   nodeEnter.append('path')
      .attr("d", function(d){
          if(d.type==='decision'){
              return drawDiamond(d);
                 } else{
             return drawRect(d);
            }
         }).attr("stroke-width", 1)
         .attr('class','myPaths')
     .style("fill", function(d) {
      return "lightsteelblue";
         });   
         
  nodeEnter.append("text")
    .attr("x", rectW / 2)
    .attr("y", rectH / 2)
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function(d) {
      return d.name;
    });

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

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

/*    nodeExit.select("rect")
    .attr("width", rectW)
    .attr("height", rectH)
    //.attr("width", bbox.getBBox().width)""
    //.attr("height", bbox.getBBox().height)
    .attr("stroke", "black")
    .attr("stroke-width", 1);  */

  nodeExit.select("text");

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

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("x", rectW/2)
    .attr("y", rectH/2)
    .attr("d", linkFunc)
    .on('click', 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;
   var y; 
      
   if (d.source.x < d.target.x) {
    // Child is to the right of the parent
          x=  bbox.x + bbox.width;
          y=   bbox.y;
          plusButton
            .attr('transform', 'translate(' + x + ', ' + y + ')')
            .classed('hide', false);

      } else {
            x = bbox.x;
            y = bbox.y;
                plusButton
            .attr('transform', 'translate(' + x + ', ' + y + ')')
            .classed('hide', false);
                 }  
    })
    .on('blur', function(d, i) {
      plusButton
        .classed('hide', true);
    });

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

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", linkFunc)
    .remove();

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




// ON CLICK OF NODES 
function click(d) {
  console.log(d);
  selectedNode = d;
  var x = d.x;
  var y = d.y + 40;

  var m = d.x + 50;
  var h = d.y + 20;

  diamondImage
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x + 60;
  var h = d.y - 10;

  rectangleShape
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x - 40;
  var h = d.y + 20;

  diamondImageFalse
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x - 40;
  var h = d.y - 10;

  rectangleShapeFalse
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);
}

//Redraw for zoom
function redraw() {
  //console.log("here", d3.event.translate, d3.event.scale);
  svg.attr("transform",
    "translate(" + d3.event.translate + ")" +
    " scale(" + d3.event.scale + ")");
}

// oN CALL

function addElement(d) {
  console.log(d);

  d.children = [];
  d.children.push(emptyDecisionBox);
  update(root);
}





// draw elements //

function drawDiamond(centroid) {
  // Start at the top
  console.log(centroid);
  console.log("rectH", rectH,rectW)
  // Start at the top
  var result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);

  // Go right
  result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;

  // Bottom
  result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);

  // Left
  result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;

  // Close the shape
  result += 'Z';

  return result;
  }

function drawRect(centroid) {
  // Start at the top left
   console.log(centroid);
  var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);

  // Go right
  result += 'h' + rectW;

  // Go down
  result += 'v' + rectH;

  // Left
  result += 'h-' + rectW;

  // Close the shape
  result += 'Z';

    console.log(result);
  return result;
}

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');

var rectangleShape = svg.append('g')
  .classed('button', true)
  .classed('hide', true)
  .on('click', function() {
    removeAllButtonElements();
  })

rectangleShape
  .append('rect')
  .attr('width', 40)
  .attr('height', 20)
  .style('fill', 'orange');


var diamondImage = svg.append('g')
  .classed('button', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
    addElement(selectedNode);
    console.log("Clicked on Diamond");
    console.log("set hide to true");
    removeAllButtonElements();
  });

diamondImage
  .append('path')
  .attr('d', 'M 20 0 40 20 20 40 0 20 Z')
  .style("fill", 'orange');


var rectangleShapeFalse = svg.append('g')
  .classed('button', true)
  .classed('hide', true)
  .on('click', function() {
    console.log("rectangle clicked");
    removeAllButtonElements();
  })

rectangleShapeFalse
  .append('rect')
  .attr('width', 40)
  .attr('height', 20)
  .style('fill', 'orange');

var diamondImageFalse = svg.append('g')
  .classed('button', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
    console.log("Clicked on Diamond");
    console.log("set hide to true");
    removeAllButtonElements();
  });

diamondImageFalse
  .append('path')
  .attr('d', 'M 20 0 40 20 20 40 0 20 Z')
  .style("fill", 'orange');


function removeAllButtonElements() {
  plusButton.classed('hide', true);
  diamondImage.classed('hide', true);
  rectangleShape.classed('hide', true);
  diamondImageFalse.classed('hide', true);
  rectangleShapeFalse.classed('hide', true);
}


// draw elements end ..

As您可以看到,svg并不确切地位于它们应该位于的位置,但是比实际位置要比节点的位置低很多。

As you can see, the svg's are not exactly where they are supposed to be , but quite a bit lower than the actual position than where the nodes are.

我也在尝试添加子节点:-
如果单击根节点右侧的矩形节点,则可以在节点位置周围看到橙色正方形和菱形。如果单击右下方的菱形-它会绘制一个菱形,该菱形连接到上述节点,并且链接也过渡良好,但是节点和SVG停留在同一位置。

I am also trying to add child nodes:- If you click on the rectangle node to the right of the root node, you will be able to see orange squares and diamonds around the node position. If you click on the right bottom diamond - it draws a diamond connecting to the above node, and the links also transition well, but the nodes and SVG's are stuck at the same position.

推荐答案

是的,当您更改要静态绘制的节点时,它们的位置确实很好,并且文本也可以轻松更改。区别之一是,我将绘制节点从中心开始,而不是从左上角开始,因为这样比较漂亮。我还稍微改变了橙色形状的位置,以使被单击的蓝色节点居中。

You're right, when you change the nodes to be drawn statically, their positions are fine indeed, and the text can be easily changed too. One difference, I'd draw the nodes starting at the center, not at the top left, because that's prettier with transformations. I also changed the orange shape position a little bit to center the blue node that was clicked.

添加节点的一个问题:您每次都添加相同的对象。有关说明,请参见此主题。一种解决方法是每次复制对象或创建新对象。

One problem with adding nodes: you add the same object every time. See this toturial for an explanation. A workaround is to copy the object or create it new every time.

您还使用 d.children = []删除了所单击节点的所有子节点。 ; 。将其更改为仅在 d 没有子项的情况下执行。

You also deleted all children of the node you clicked on, with d.children = [];. Changing that to only execute if d has no children fixed that.

最后,我在 linkFunc 仅在一个节点位于另一个节点下方时才绘制一条垂直线。

Finally, I added a case to the linkFunc just draw a vertical line if one node is straight below the other.

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

function emptyDecisionBox() {
  return {
    "name": "newDecision",
    "id": "newId",
    "value": "notSure",
    "condition": "true",
  };
}

var selectedNode;

var root = {
  "name": "Root",
  "type": "decision",
  "children": [{
      "name": "analytics",
      "type": "decision",
      "value": "a+b",
      "children": [{
        "name": "distinction",
        "type": "action",
        "condition": "true",
        "value": "5",
      }, {
        "name": "nonDistinction",
        "type": "action",
        "condition": "false",
        "value": "4"
      }],
    },
    {
      "name": "division",
      "type": "action",
      "value": "a-b",
      "children": [],
    }
  ]
};

var i = 0,
  duration = 750,
  rectW = 80,
  rectH = 40;

var tree = d3.layout.tree().nodeSize([120, 90]);

//LINK FUNCTION TO DRAW LINKS 
var linkFunc = function(d) {
  var source = {
    x: d.source.x - (rectW / 2),
    y: d.source.y
  };
  var target = {
    x: d.target.x,
    y: d.target.y - (rectH / 2)
  };

  // This is where the line bends
  var inflection = {
    x: target.x,
    y: source.y
  };
  var radius = 5;

  var result = "M" + source.x + ',' + source.y;
  
  // If the source and target are on the same position, just draw a straight vertical line
  if (source.x !== target.x) {
    if (source.x < target.x) {
      // Child is to the right of the parent
      result += ' H' + (inflection.x - radius);
    } else {
      result += ' H' + (inflection.x + radius);
    }

    // Curve the line at the bend slightly
    result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);
  }

  result += 'V' + target.y;
  return result;
}

// DRAW TREE //
var svg = d3.select(".tree-diagram").append("svg").attr("width", 1000).attr("height", 1000)
  .call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
  .attr("transform", "translate(" + 350 + "," + 20 + ")");

//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);

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

update(root);

d3.select(".tree-diagram").style("height", "1000px");

// UPDATE TREE
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 * 90;
  });

  // 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.x0 + "," + source.y0 + ")";
    })
    .on("click", click);

  nodeEnter.append('path')
    .attr("d", function(d) {
      if (d.type === 'decision') {
        return drawDiamond();
      } else {
        return drawRect();
      }
    }).attr("stroke-width", 1)
    .attr('class', 'myPaths')
    .style("fill", "lightsteelblue");

  nodeEnter.append("text")
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .text(function(d) {
      return d.name;
    });

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

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .remove();

  nodeExit.select("text");

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

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("d", linkFunc)
    .on('click', 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;
      var y;

      if (d.source.x < d.target.x) {
        // Child is to the right of the parent
        x = bbox.x + bbox.width;
        y = bbox.y;
        plusButton
          .attr('transform', 'translate(' + x + ', ' + y + ')')
          .classed('hide', false);

      } else {
        x = bbox.x;
        y = bbox.y;
        plusButton
          .attr('transform', 'translate(' + x + ', ' + y + ')')
          .classed('hide', false);
      }
    })
    .on('blur', function(d, i) {
      plusButton
        .classed('hide', true);
    });

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

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", linkFunc)
    .remove();

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

// ON CLICK OF NODES 
function click(d) {
  console.log(d);
  selectedNode = d;

  var m = d.x + 40;
  var h = d.y;

  diamondImage
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x + 40;
  var h = d.y - 30;

  rectangleShape
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x - 70;
  var h = d.y;

  diamondImageFalse
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);

  var m = d.x - 70;
  var h = d.y - 30;

  rectangleShapeFalse
    .attr('transform', 'translate(' + m + ', ' + h + ')')
    .classed('hide', false);
}

//Redraw for zoom
function redraw() {
  //console.log("here", d3.event.translate, d3.event.scale);
  svg.attr("transform",
    "translate(" + d3.event.translate + ")" +
    " scale(" + d3.event.scale + ")");
}

// oN CALL

function addElement(d) {
  console.log(d);

  if(d.children === undefined) { d.children = []; }
  d.children.push(emptyDecisionBox());
  update(root);
}


// draw elements //

function drawDiamond() {
  // Start at the top
  var result = 'M0,' + (-rectH / 2);

  // Go right
  result += 'L' + (rectW / 2) + ',0';

  // Bottom
  result += 'L0,' + (rectH / 2);

  // Left
  result += 'L' + (-rectW / 2) + ',0';

  // Close the shape
  result += 'Z';

  return result;
}

function drawRect() {
  // Start at the top left
  var result = 'M' + (-rectW / 2) + ',' + (-rectH / 2);

  // Go right
  result += 'h' + rectW;

  // Go down
  result += 'v' + rectH;

  // Left
  result += 'h-' + rectW;

  // Close the shape
  result += 'Z';

  return result;
}

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');

var rectangleShape = svg.append('g')
  .classed('button', true)
  .classed('hide', true)
  .on('click', function() {
    removeAllButtonElements();
  })

rectangleShape
  .append('rect')
  .attr('width', 40)
  .attr('height', 20)
  .style('fill', 'orange');


var diamondImage = svg.append('g')
  .classed('button', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
    addElement(selectedNode);
    console.log("Clicked on Diamond");
    console.log("set hide to true");
    removeAllButtonElements();
  });

diamondImage
  .append('path')
  .attr('d', 'M 20 0 40 20 20 40 0 20 Z')
  .style("fill", 'orange');


var rectangleShapeFalse = svg.append('g')
  .classed('button', true)
  .classed('hide', true)
  .on('click', function() {
    console.log("rectangle clicked");
    removeAllButtonElements();
  })

rectangleShapeFalse
  .append('rect')
  .attr('width', 40)
  .attr('height', 20)
  .style('fill', 'orange');

var diamondImageFalse = svg.append('g')
  .classed('button', true)
  .classed('hide', true)
  .classed('scale', true)
  .on('click', function() {
    console.log("Clicked on Diamond");
    console.log("set hide to true");
    removeAllButtonElements();
  });

diamondImageFalse
  .append('path')
  .attr('d', 'M 20 0 40 20 20 40 0 20 Z')
  .style("fill", 'orange');


function removeAllButtonElements() {
  plusButton.classed('hide', true);
  diamondImage.classed('hide', true);
  rectangleShape.classed('hide', true);
  diamondImageFalse.classed('hide', true);
  rectangleShapeFalse.classed('hide', true);
}


// draw elements end ..

.node {
  cursor: pointer;
}

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

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

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


/* body {
  overflow: hidden;
} */

.button>path {
  stroke: blue;
  stroke-width: 1.5;
}

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

.hide {
  /*  display: none; */
  opacity: 0 !important;
  /*    pointer-events: none;  */
}

.link:hover {
  cursor: pointer;
  stroke-width: 8px;
}

.scale {
  /* transform: scale(0.4); */
}

.colorBlue {
  background-color: blue;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>

这篇关于D3.js SVG没有在正确的节点位置绘制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆