在d3.js中绘制bezierCurve [英] Drawing bezierCurve in d3.js

查看:74
本文介绍了在d3.js中绘制bezierCurve的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何在d3.js中使用bezierCurveTo方法绘制线条,以使线条如下图所示

我刚刚提到了

但是您可以轻松地通过此方法将曲线放置在链接的中间:

这是实际的情况:

  var data = {名称":父母",孩子":[{"name":孩子A","children":[{"name":"Grandchild1"},{"name":"Grandchild2"}]},{"name":孩子B",}]};var width = 400;var height = 300;保证金= {左:50,上:10,右:30,下:10}var svg = d3.select("body").append("svg").attr("width",width).attr("height",height);var g = svg.append("g").attr('transform','translate('+ margin.left +','+ margin.right +')');;var root = d3.hierarchy(data);var tree = d3.tree().size([height-margin.top-margin.bottom,width-margin.left-margin.right]);var linker = function(d){var x0 = d.source.x;var y0 = d.source.y;var y1 = d.target.y;var x1 = d.target.x;var k =(y1-y0)/2;var path = d3.path()path.moveTo(y0,x0)path.lineTo(y0 + k/2,x0)path.bezierCurveTo(y1-k,x0,y0 + k,x1,y1-k/2,x1);path.lineTo(y1,x1);返回path.toString();}var link = g.selectAll(.link").data(树(根).links()).enter().append("path").attr("class","link").attr("d",链接器);var node = g.selectAll(.node").data(root.descendants()).enter().append("g").attr("class",function(d){返回"node" +(d.children?"node--internal":"node--leaf");}).attr("transform",function(d){return"translate(" + d.y +," + d.x +)";})node.append("circle").attr("r",2.5);node.append("text").text(function(d){return d.data.name;}).attr('y',-10).attr('x',-10).attr('text-anchor','middle');  

  .node circle {填充:#fff;中风:steelblue;笔划宽度:3px;}.关联 {填充:无;中风:#ccc;笔划宽度:2px;}  

 <脚本src ="https://d3js.org/d3.v4.min.js"></script> 

但是,在阅读评论时,我注意到您的问题可能更多地是关于dagreD3的-这会大大改变事情. Dagre D3相对于D3提供了更好的易用性,但要付出一些D3的代价.灵活性.如果要为DagreD3提供某种类型的曲线,则应使用d3曲线或某些自定义曲线(如上面的链接答案所示).您可以在轻松添加边缘时指定曲线.

但是,这并不能解决与图像中同一点产生的边缘问题.我将提供一个基于d3的解决方案-可能会破坏边缘标签的放置,过渡等,因此如果您需要该功能,则应该对其进行一些扩展.我将从上方使用贝塞尔曲线发生器.以下内容受的启发:

  var g = new dagreD3.graphlib.Graph().setGraph({rankdir:'LR'}).setDefaultEdgeLabel(function(){return {};});g.setNode(0,{label:"0"});g.setNode(1,{label:"1"});g.setNode(2,{label:"2"});g.setNode(3,{label:"3"});g.setNode(4,{label:"4"});g.setEdge(0,1);g.setEdge(0,2);g.setEdge(1,3);g.setEdge(1,4);var render = new dagreD3.render().createEdgePaths(createEdgePaths);var svg = d3.select("svg"),svgGroup = svg.append("g"),zoom = d3.zoom().on("zoom",function(){svgGroup.attr("transform",d3.event.transform);});svg.call(zoom);render(svgGroup,g);函数createEdgePaths(选择,g,箭头){selection.selectAll("g.edgePath").data(g.edges()).进入().append(路径").attr("d",function(e){返回calcPoints(g,e);});}函数calcPoints(g,e){var source = g.node(e.v);var target = g.node(e.w);var x0 = source.x + source.width/2;var x1 = target.x-target.width/2;var y0 = source.y;var y1 = target.y;返回链接器(x0,y0,x1,y1)}函数链接器(x0,y0,x1,y1){var dx = x1-x0;var k = dx/3;var path = d3.path()path.moveTo(x0,y0)path.bezierCurveTo(x1-k,y0,x0,y1,x1-k,y1);path.lineTo(x1,y1);返回path.toString();}  

  path {中风:#333;笔划宽度:1.5px;填充:无;}rect {填充:无;中风:黑色;笔划宽度:1px;}  

 < script src ="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js></script>< script src ="https://cdn.jsdelivr.net/npm/dagre-d3@0.6.1/dist/dagre-d3.min.js"</script>< svg width ="800" height ="600"></svg>  

How to draw the line using bezierCurveTo method in d3.js such that lines appears as below image

I have just referred bezier Curve but i'm not getting any idea on it.

解决方案

There are a lot of ways this could be done. One could make a custom curve that achieves this.

But, we could keep it simpler too. The datum passed to the link generator, such as d3.linkHorizontal, from a d3 layout generally contains a source and target property, each of these usually contain x and y properties. Assuming this structure, we could create a function that uses these and creates and returns the appropriate path data with a bezier curve:

var linker = function(d) {
  var x0 = d.source.x;
  var y0 = d.source.y;
  var y1 = d.target.y;
  var x1 = d.target.x;
  var k = 120;
  
  var path = d3.path()
  path.moveTo(y0,x0)
  path.bezierCurveTo(y1-k,x0,y0,x1,y1-k,x1);
  path.lineTo(y1,x1);
  
  return path.toString();
}

The above is pretty basic, it uses d3.path but you could easily just construct the SVG path string yourself. There are lots of interactive bezier curve explorers online so you can figure out what control points work best. As the tree layout I've used is vertical, I've turned it horizontal by inverting x and y, which is why my coordinates are [y,x]. I use k above to offset the bezier curve to a small portion of the overall link on the left:

But you could easily toy with this to place the curve in the middle of the link:

Here's it in action:

var data = { "name": "Parent", "children": [ 
    { "name": "Child A", "children": [ { "name": "Grandchild1"}, {"name":"Grandchild2" } ] }, 
    { "name": "Child B", } 
    ] };

var width = 400;
var height = 300;

margin = {left: 50, top: 10, right:30, bottom: 10}

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

var root = d3.hierarchy(data);
      
var tree = d3.tree()
   .size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
   
var linker = function(d) {
  var x0 = d.source.x;
  var y0 = d.source.y;
  var y1 = d.target.y;
  var x1 = d.target.x;
  var k = (y1-y0)/2;
  
  var path = d3.path()
  path.moveTo(y0,x0)
  path.lineTo(y0+k/2,x0)
  path.bezierCurveTo(y1-k,x0,y0+k,x1,y1-k/2,x1);
  path.lineTo(y1,x1);
  
  return path.toString();
}

 var link = g.selectAll(".link")
    .data(tree(root).links())
    .enter().append("path")
      .attr("class", "link")
      .attr("d", linker);

  var node = g.selectAll(".node")
    .data(root.descendants())
    .enter().append("g")
      .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })

  node.append("circle")
      .attr("r", 2.5);
      
  node.append("text")
     .text(function(d) { return d.data.name; })
     .attr('y',-10)
     .attr('x',-10)
     .attr('text-anchor','middle');

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

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

<script src="https://d3js.org/d3.v4.min.js"></script>

But, in reading the comments I notice that your question may be more about dagreD3 - which changes things considerably. Dagre D3 offers better ease of use relative to D3 at the cost of the some of D3's flexibility. If you want to provide a certain type of curve to DagreD3, then you should use a d3 curve, or some custom curve (as in the linked answer above). You can specify the curve when adding edges easily enough.

But that doesn't solve the issue of edges originating from the same point as in your image. I'll provide a d3 based solution - which probably breaks edge label placement, transitions, etc - so it should be built out a bit if you need that functionality. I'll use the bezier generator from above. The following is inspired by this:

var g = new dagreD3.graphlib.Graph()
  .setGraph({rankdir: 'LR'})
  .setDefaultEdgeLabel(function() { return {}; });

g.setNode(0,  { label: "0"});
g.setNode(1,  { label: "1"});
g.setNode(2,  { label: "2"});
g.setNode(3,  { label: "3"});
g.setNode(4,  { label: "4"});

g.setEdge(0, 1);
g.setEdge(0, 2);
g.setEdge(1, 3);
g.setEdge(1, 4);

var render = new dagreD3.render().createEdgePaths(createEdgePaths);


var svg = d3.select("svg"),
    svgGroup = svg.append("g"),
    zoom = d3.zoom().on("zoom", function() {
      svgGroup.attr("transform", d3.event.transform);
    });
svg.call(zoom);

render(svgGroup, g);

function createEdgePaths(selection, g, arrows) {
   selection.selectAll("g.edgePath")
    .data(g.edges())
    .enter()
    .append("path")
    .attr("d", function(e) {
      return calcPoints(g,e);  
    });
}

function calcPoints(g, e) {
  var source = g.node(e.v);
  var target = g.node(e.w);
  var x0 = source.x + source.width/2;
  var x1 = target.x - target.width/2;
  var y0 = source.y;
  var y1 = target.y;
  return linker(x0,y0,x1,y1)
}
function linker(x0,y0,x1,y1) {
 var dx = x1 -x0;
 var k = dx/3;
      
 var path = d3.path()
 path.moveTo(x0,y0)
 path.bezierCurveTo(x1-k,y0,x0,y1,x1-k,y1);
 path.lineTo(x1,y1);
      
 return path.toString();
}

path {
  stroke: #333;
  stroke-width: 1.5px;
  fill: none;
}
rect {
  fill: none;
  stroke:black;
  stroke-width: 1px;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dagre-d3@0.6.1/dist/dagre-d3.min.js"></script>
<svg width="800" height="600"></svg>

这篇关于在d3.js中绘制bezierCurve的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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