Есть много способов сделать это. Можно сделать пользовательскую кривую , которая достигает этого.
Но мы могли бы сделать это и проще. Данные, передаваемые в генератор ссылок, такие как d3.linkHorizontally, из макета d3 обычно содержат свойства источника и цели, каждое из которых обычно содержит свойства x и y. Предполагая эту структуру, мы могли бы создать функцию, которая использует их, а также создает и возвращает соответствующие данные пути с кривой Безье:
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();
}
Выше приведено довольно базовое значение c, оно использует d3. путь , но вы можете легко создать строку пути SVG самостоятельно. В Интернете есть множество интерактивных исследователей кривой Безье, чтобы вы могли выяснить, какие контрольные точки работают лучше всего. Поскольку макет дерева, который я использовал, является вертикальным, я повернул его по горизонтали, инвертировав x и y, поэтому мои координаты [y, x] . Я использую k
выше, чтобы сместить кривую Безье на небольшую часть общей ссылки слева:
Но вы можете легко играть с чтобы поместить кривую в середину ссылки:
Вот оно в действии:
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 = 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();
}
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>
Но, читая комментарии, я замечаю, что ваш вопрос может быть больше о dagreD3 - что значительно меняет дело. Dagre D3 предлагает лучшую простоту использования по сравнению с D3 за счет некоторой гибкости D3. Если вы хотите предоставить определенный тип кривой для DagreD3, вам следует использовать кривую d3 или некоторую пользовательскую кривую (как в связанном ответе выше). Вы можете указать кривую при добавлении ребер достаточно легко.
Но это не решает проблему краев, исходящих из той же точки, что и на вашем изображении. Я предоставлю решение на основе d3 - которое, вероятно, нарушает размещение меток ребер, переходы и т. Д. c - так что оно должно быть построено немного, если вам нужна эта функциональность. Я буду использовать генератор Безье сверху. 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>