d3.js network curved path control [closed]

2020-08-09 04:41发布

问题:

//latest fiddle http://jsfiddle.net/rbLk2fbe/2/

I am trying to build this particular chart where I can control the curved paths to show a network. Two trunks that are spaced correctly - and then curved branches that leaf off to the various people.

 var w = 600;
 var h = 600;


 var data = [{
   "userName": "You",
   "userImage": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSTzjaQlkAJswpiRZByvgsb3CVrfNNLLwjFHMrkZ_bzdPOWdxDE2Q"
 }, {
   "userName": "Johnny",
   "userImage": "https://crossovercomicblog.files.wordpress.com/2012/08/johnny-depp-sexy.jpg"
 }, {
   "userName": "Jeri",
   "userImage": "https://68.media.tumblr.com/avatar_3b6d6241698f_128.png"
 }, {
   "userName": "Charlize",
   "userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
 },{
   "userName": "Charlize",
   "userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
 },{
   "userName": "Charlize",
   "userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
 },{
   "userName": "Charlize",
   "userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
 },{
   "userName": "Angelina",
   "userImage": "https://pbs.twimg.com/profile_images/713650489032908800/nO1dMt6M_400x400.jpg"
 },  {
   "userName": "Them",
   "userImage": "https://68.media.tumblr.com/avatar_8f199caf2d82_128.png"
 }];

 var viz = d3.select("#viz")
   .append("svg")
   .attr("width", w)
   .attr("height", h)
   .append("g")
   .attr("transform", "translate(40,100)");


 var patternsSvg = viz.append('g')
   .attr('class', 'patterns');

 var labelholder = viz.append("g")
   .attr("class", "labelholder");

 var treeholder = viz.append("g")
   .attr("class", "treeholder");

 var userholder = viz.append("g")
   .attr("class", "userholder");



 var smallRadius = 20;
 var bigRadius = 30;

 var smallX = smallRadius + (bigRadius/2);
 var bigX = (bigRadius*2) + (smallRadius/2);

 var verticalGap = (bigRadius * 2) - 5;

 var count = data.length;


 var extendedY = (count-2 * (smallRadius*2)) + ((bigRadius*2) * 2);
 var arcRadiusLeft = (bigRadius / 2);
 var arcRadiusRight = -(bigRadius / 2);


 $.each(data, function(index, value) {
   var defs = patternsSvg.append('svg:defs');

   //big design   
   defs.append('svg:pattern')
     .attr('id', index + "--" + value.userName.toLowerCase())
     .attr('width', 1)
     .attr('height', 1)
     .append('svg:image')
     .attr('xlink:href', value.userImage)
     .attr('x', 0)
     .attr('y', 0)
     .attr('width', bigRadius * 2)
     .attr('height', bigRadius * 2);

   //small design
   defs.append('svg:pattern')
     .attr('id', index + "-" + value.userName.toLowerCase())
     .attr('width', 1)
     .attr('height', 1)
     .append('svg:image')
     .attr('xlink:href', value.userImage)
     .attr('x', 0)
     .attr('y', 0)
     .attr('width', smallRadius * 2)
     .attr('height', smallRadius * 2);

 });



 //plot people circles
 var circle = userholder.append("g").selectAll("circle")
   .data(data);

 circle
   .enter()
   .append("svg:circle")
   .attr("id", function(d) {
     return d.userName.toLowerCase();
   })
   .attr("r", function(d, i) {
     var rad = smallRadius;

     //first and last items -- so you and them
     if (i == 0 || i == count - 1) {
       rad = bigRadius;
     }
     return rad;
   })
   .attr("cx", function(d, i) {
     var cx;
     if (i == 0) {
       cx = 0; //first one 
     } else if (i == count - 1) {
       cx = bigX; //last one
     } else {
       cx = smallX; //small ones
     }
     return cx;
   })
   .attr("cy", function(d, i) {
     var cy;
     if (i == 0) {
       cy = 0;
     } else if (i == count - 1) {
       cy = verticalGap * (i-1) + extendedY + bigRadius;
     } else {
       cy = verticalGap * i
     }
     return cy;
   })
   .style("fill", function(d, i) {
     var id = i + "-" + d.userName.toLowerCase(); //small circles

     //large circles
     if (i == 0 || i == count - 1) {
       id = i + "--" + d.userName.toLowerCase();
     }
     return "url(#" + id + ")";
   });
 //plot people circles



 //__labels  
 var labelholder = d3.select(".labelholder");

 //__ enter
 var labels = labelholder.selectAll("text")
   .data(data);

 labels.enter()
   .append("text")
   .attr("text-anchor", "left")

 //__ update            
 labels
   .attr("x", function(d, i) {

     var displacement = (bigRadius/2) + smallRadius;
     var cx = (smallRadius * 2);
     if (i == 0) {
       cx = bigRadius;
       displacement = bigRadius/2;
     }
     if (i == count - 1) {
       cx = (bigRadius * 2) + bigRadius;
       displacement = bigRadius;
     }

     cx += displacement;

     return cx;
   })
   .attr("y", function(d, i) {
     var cy = verticalGap * i;
     if (i == count - 1) {
       cy += extendedY - (bigRadius/2);
     }
     return cy;
   })
   .text(function(d) {
     return d.userName;
   });
 //__labels


 var backbone = treeholder.append("g")
   .append("svg:path");

 backbone.attr("d", function(d, i) {
   var sx = (bigRadius / 2) - (bigRadius / 2);
   var tx = (bigRadius / 2) - (bigRadius / 2);

   var r = smallRadius;
   if (i == 0 || i == count - 1) {
     r = bigRadius;
   }

   var sy = ((r / 2) * i) + (r);
   var ty = verticalGap * (count - 2) - arcRadiusLeft;
   var dr = 0;

   return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
 });

var displaceYBackboneRight = (bigRadius / 2) + 5; 

 var backbone = treeholder.append("g")
   .append("svg:path");

 backbone.attr("d", function(d, i) {
   var sx = (bigRadius * 2) + smallRadius/2;
   var tx = (bigRadius * 2) + smallRadius/2;

   var r = smallRadius;
   if (i == 0 || i == count - 1) {
     r = bigRadius;
   }

   var sy = ((r / 2) * i) + (r) + smallRadius + displaceYBackboneRight;

   var ty = verticalGap * (count - 2) + extendedY;

   var dr = 0;

   return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
 });


 //branches on the left 
 var leftpath = treeholder.append("g").selectAll("path.leftpath")
   .data(data)

 leftpath
   .enter().append("svg:path")
   .attr("class", function(d) {
     return "leftpath";
   });

 leftpath.attr("d", function(d, i) {
   var sx = 0;
   var tx = arcRadiusLeft;

   var sy = verticalGap * i - arcRadiusLeft;
   var ty = verticalGap * i;

   if (i != 0 && i != count - 1) {
     return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
   }

 });




 //branches on the left 
 var rightpath = treeholder.append("g").selectAll("path.rightpath")
   .data(data)

 rightpath
   .enter().append("svg:path")
   .attr("class", function(d) {
     return "rightpath";
   });

 rightpath.attr("d", function(d, i) {
   var sx = (bigRadius*2) + (smallRadius/2);
   var tx = arcRadiusRight + (bigRadius*2) + (smallRadius/2);

   var sy = verticalGap * i + (bigRadius / 2);
   var ty = verticalGap * i + arcRadiusRight + (bigRadius / 2);

   if (i != 0 && i != count - 1) {
     return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
   }


 });

//old http://jsfiddle.net/NYEaX/1811/

So the key here is to create the small arcs correctly using maths.

Something like this

path.attr("d", function (d, i) {
    const sx = 0;    
    const sy = height/2;

    //width - total width of chart from subject a to subject b

    const a = width/2;
    //a - distance between subject a and center

    const b = (1.5-i)*distanceBetween;
    //b - distance between trait 1 and trait 2 (between the dots)

    const c = Math.sqrt(a * a + b * b);
    //c - is the diagonal distance between subject a and trait 1

    const angle=Math.atan(a/b);
    //angle - between group to subject a

    const r=1/2*c/Math.cos(angle);
    //r - 1/2 the distance of c -- divided by cos of angle -- will create a radius to draw the arc


    //also equals c/b*(c/2)
//    const r=c/b*(c/2);
    return `M${sx},${sy} A${r},${r} 0 0,${b>0?1:0} ${width},${height/2}`;
});

回答1:

Live demo: http://jsfiddle.net/blackmiaool/857edt69/3/

The first link in the question provides complete solution.

There's nothing difficult in the question. What you need is just a decent coordinate system.

Code to set the d of avatars:

.attr("cx", function (d, i) {
    var cx;
    if (i == 0) {
        cx = 0; //first one 
    } else if (i == count - 1) {
        cx = bigX; //last one
    } else {
        cx = smallX; //small ones
    }
    return cx;
})
.attr("cy", function (d, i) {
    var cy;
    if (i == 0) {
        cy = 0;
    } else if (i == count - 1) {
        cy = verticalGap * (i - 1) + extendedY;
    } else {
        cy = verticalGap * i
    }
    return cy;
})

Code to shape left arcs:

leftpath.attr("d", function (d, i) {
    var dist = bigRadius * 2;

    var sx = 0;
    var tx = arcRadiusLeft;

    var sy = verticalGap * i - arcRadiusLeft;
    var ty = verticalGap * i;

    if (i != 0 && i != count - 1) {
        return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
    }
});

The left arcs are handled. I think you can handle the right ones yourself.



回答2:

//latest js fiddle - stable with control for all paths in place - based on blackmiaool's answer.

http://jsfiddle.net/rbLk2fbe/2/

 var w = 600;
 var h = 600;


 var data = [{
   "userName": "You",
   "userImage": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSTzjaQlkAJswpiRZByvgsb3CVrfNNLLwjFHMrkZ_bzdPOWdxDE2Q"
 }, {
   "userName": "Johnny",
   "userImage": "https://crossovercomicblog.files.wordpress.com/2012/08/johnny-depp-sexy.jpg"
 }, {
   "userName": "Jeri",
   "userImage": "https://68.media.tumblr.com/avatar_3b6d6241698f_128.png"
 }, {
   "userName": "Charlize",
   "userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
 },{
   "userName": "Charlize",
   "userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
 },{
   "userName": "Charlize",
   "userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
 },{
   "userName": "Charlize",
   "userImage": "https://cdn.imza.com/indir/logo/128/charlize-theron.png"
 },{
   "userName": "Angelina",
   "userImage": "https://pbs.twimg.com/profile_images/713650489032908800/nO1dMt6M_400x400.jpg"
 },  {
   "userName": "Them",
   "userImage": "https://68.media.tumblr.com/avatar_8f199caf2d82_128.png"
 }];

 var viz = d3.select("#viz")
   .append("svg")
   .attr("width", w)
   .attr("height", h)
   .append("g")
   .attr("transform", "translate(40,100)");


 var patternsSvg = viz.append('g')
   .attr('class', 'patterns');

 var labelholder = viz.append("g")
   .attr("class", "labelholder");

 var treeholder = viz.append("g")
   .attr("class", "treeholder");

 var userholder = viz.append("g")
   .attr("class", "userholder");



 var smallRadius = 20;
 var bigRadius = 30;

 var smallX = smallRadius + (bigRadius/2);
 var bigX = (bigRadius*2) + (smallRadius/2);

 var verticalGap = (bigRadius * 2) - 5;

 var count = data.length;


 var extendedY = (count-2 * (smallRadius*2)) + ((bigRadius*2) * 2);
 var arcRadiusLeft = (bigRadius / 2);
 var arcRadiusRight = -(bigRadius / 2);


 $.each(data, function(index, value) {
   var defs = patternsSvg.append('svg:defs');

   //big design   
   defs.append('svg:pattern')
     .attr('id', index + "--" + value.userName.toLowerCase())
     .attr('width', 1)
     .attr('height', 1)
     .append('svg:image')
     .attr('xlink:href', value.userImage)
     .attr('x', 0)
     .attr('y', 0)
     .attr('width', bigRadius * 2)
     .attr('height', bigRadius * 2);

   //small design
   defs.append('svg:pattern')
     .attr('id', index + "-" + value.userName.toLowerCase())
     .attr('width', 1)
     .attr('height', 1)
     .append('svg:image')
     .attr('xlink:href', value.userImage)
     .attr('x', 0)
     .attr('y', 0)
     .attr('width', smallRadius * 2)
     .attr('height', smallRadius * 2);

 });



 //plot people circles
 var circle = userholder.append("g").selectAll("circle")
   .data(data);

 circle
   .enter()
   .append("svg:circle")
   .attr("id", function(d) {
     return d.userName.toLowerCase();
   })
   .attr("r", function(d, i) {
     var rad = smallRadius;

     //first and last items -- so you and them
     if (i == 0 || i == count - 1) {
       rad = bigRadius;
     }
     return rad;
   })
   .attr("cx", function(d, i) {
     var cx;
     if (i == 0) {
       cx = 0; //first one 
     } else if (i == count - 1) {
       cx = bigX; //last one
     } else {
       cx = smallX; //small ones
     }
     return cx;
   })
   .attr("cy", function(d, i) {
     var cy;
     if (i == 0) {
       cy = 0;
     } else if (i == count - 1) {
       cy = verticalGap * (i-1) + extendedY + bigRadius;
     } else {
       cy = verticalGap * i
     }
     return cy;
   })
   .style("fill", function(d, i) {
     var id = i + "-" + d.userName.toLowerCase(); //small circles

     //large circles
     if (i == 0 || i == count - 1) {
       id = i + "--" + d.userName.toLowerCase();
     }
     return "url(#" + id + ")";
   });
 //plot people circles



 //__labels  
 var labelholder = d3.select(".labelholder");

 //__ enter
 var labels = labelholder.selectAll("text")
   .data(data);

 labels.enter()
   .append("text")
   .attr("text-anchor", "left")

 //__ update            
 labels
   .attr("x", function(d, i) {

     var displacement = (bigRadius/2) + smallRadius;
     var cx = (smallRadius * 2);
     if (i == 0) {
       cx = bigRadius;
       displacement = bigRadius/2;
     }
     if (i == count - 1) {
       cx = (bigRadius * 2) + bigRadius;
       displacement = bigRadius;
     }

     cx += displacement;

     return cx;
   })
   .attr("y", function(d, i) {
     var cy = verticalGap * i;
     if (i == count - 1) {
       cy += extendedY - (bigRadius/2);
     }
     return cy;
   })
   .text(function(d) {
     return d.userName;
   });
 //__labels


 var backbone = treeholder.append("g")
   .append("svg:path");

 backbone.attr("d", function(d, i) {
   var sx = (bigRadius / 2) - (bigRadius / 2);
   var tx = (bigRadius / 2) - (bigRadius / 2);

   var r = smallRadius;
   if (i == 0 || i == count - 1) {
     r = bigRadius;
   }

   var sy = ((r / 2) * i) + (r);
   var ty = verticalGap * (count - 2) - arcRadiusLeft;
   var dr = 0;

   return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
 });

var displaceYBackboneRight = (bigRadius / 2) + 5; 

 var backbone = treeholder.append("g")
   .append("svg:path");

 backbone.attr("d", function(d, i) {
   var sx = (bigRadius * 2) + smallRadius/2;
   var tx = (bigRadius * 2) + smallRadius/2;

   var r = smallRadius;
   if (i == 0 || i == count - 1) {
     r = bigRadius;
   }

   var sy = ((r / 2) * i) + (r) + smallRadius + displaceYBackboneRight;

   var ty = verticalGap * (count - 2) + extendedY;

   var dr = 0;

   return "M" + sx + "," + sy + "A" + dr + "," + dr + " 0 0,1 " + tx + "," + ty;
 });


 //branches on the left 
 var leftpath = treeholder.append("g").selectAll("path.leftpath")
   .data(data)

 leftpath
   .enter().append("svg:path")
   .attr("class", function(d) {
     return "leftpath";
   });

 leftpath.attr("d", function(d, i) {
   var sx = 0;
   var tx = arcRadiusLeft;

   var sy = verticalGap * i - arcRadiusLeft;
   var ty = verticalGap * i;

   if (i != 0 && i != count - 1) {
     return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
   }

 });




 //branches on the left 
 var rightpath = treeholder.append("g").selectAll("path.rightpath")
   .data(data)

 rightpath
   .enter().append("svg:path")
   .attr("class", function(d) {
     return "rightpath";
   });

 rightpath.attr("d", function(d, i) {
   var sx = (bigRadius*2) + (smallRadius/2);
   var tx = arcRadiusRight + (bigRadius*2) + (smallRadius/2);

   var sy = verticalGap * i + (bigRadius / 2);
   var ty = verticalGap * i + arcRadiusRight + (bigRadius / 2);

   if (i != 0 && i != count - 1) {
     return "M" + sx + "," + sy + "A" + arcRadiusLeft + "," + arcRadiusLeft + " 0 0,0 " + tx + "," + ty;
   }


 });