Добавление групп все испортит в раскладке d3
У меня есть симулятор D3 Force, и если бы я должен был добавить узлы следующим образом:
node = node.data(nodes, function(d) { return d.id;});
node.exit().remove();
node = node.enter().append('circle')
.attr("class", function(d) {return d.type;})
.attr("r", 25)
.merge(node);
все работает нормально - круги добавляются в правильные места, и визуализированный HTML будет выглядеть так:
<svg width="1280" height="960">
<g transform="translate(640,480)">
<g stroke="#000" stroke-width="1.5">
<line x1="197.7877989370864" y1="16.96383936157134" x2="113.39655998594978" y2="176.9054238213185"></line>
<line x1="-99.71642802229279" y1="182.82652731678513" x2="-206.38001140055673" y2="35.62690731557146"></line>
<line x1="-111.21899770908817" y1="-104.07607869492837" x2="9.724648489851102" y2="-238.28831674029004"></line>
<line x1="-111.21899770908817" y1="-104.07607869492837" x2="73.66744043019104" y2="-114.11648500001087"></line>
<line x1="197.7877989370864" y1="16.96383936157134" x2="10.328317030872993" y2="37.5171491536661"></line>
<line x1="-99.71642802229279" y1="182.82652731678513" x2="10.328317030872993" y2="37.5171491536661"></line>
<line x1="-111.21899770908817" y1="-104.07607869492837" x2="10.328317030872993" y2="37.5171491536661"></line>
<line x1="197.7877989370864" y1="16.96383936157134" x2="73.66744043019104" y2="-114.11648500001087"></line>
</g>
<g prop="nodes" stroke="#000" stroke-width="1.5">
<circle fill="some_image.png" class="Net" r="25" cx="197.7877989370864" cy="16.96383936157134"></circle>
<circle fill="some_image.png" class="Net" r="25" cx="-99.71642802229279" cy="182.82652731678513"></circle>
<circle fill="some_image.png" class="Net" r="25" cx="-111.21899770908817" cy="-104.07607869492837"></circle>
<circle fill="some_image.png" class="Inst" r="25" cx="113.39655998594978" cy="176.9054238213185"></circle>
<circle fill="some_image.png" class="Inst" r="25" cx="-206.38001140055673" cy="35.62690731557146"></circle>
<circle fill="some_image.png" class="Inst" r="25" cx="9.724648489851102" cy="-238.28831674029004"></circle>
<circle fill="some_image.png" class="Inst" r="25" cx="73.66744043019104" cy="-114.11648500001087"></circle>
<circle fill="some_image.png" class="Internet" r="25" cx="10.328317030872993" cy="37.5171491536661"></circle>
</g>
</g>
</svg>
Но если бы я хотел добавить группы (мой окончательный дизайн требует фоновых изображений, меток и всяких дополнительных вещей), вот так:
node = node.data(nodes, function(d) { return d.id;});
node.exit().remove();
node.enter().append('g')
.attr('class', 'node')
.append('image')
.attr('xlink:href', 'some_image.png')
.append('text')
.text(function(d){return d.text;})
... and so on...
хотя мой код, кажется, интерпретируется правильно (я добавляю группы, добавляю к ним изображения и метки), группы остаются статичными и остаются в середине сима друг над другом. Также кажется, что преобразование координат идет к изображениям, а не к группе, что, я думаю, нарушает сим:
<svg width="1280" height="960">
<g transform="translate(640,480)">
<g stroke="#000" stroke-width="1.5">
<line x1="197.77682810226557" y1="16.981901068622136" x2="113.3585440445384" y2="176.90457630748227"></line>
<line x1="-99.99450481197604" y1="182.94091641902205" x2="-206.13047480355274" y2="35.36287517221039"></line>
<line x1="-111.19343747422879" y1="-103.71666033252438" x2="9.543859895654657" y2="-238.10758089494877"></line>
<line x1="-111.19343747422879" y1="-103.71666033252438" x2="73.69734375869983" y2="-114.13138675745854"></line>
<line x1="197.77682810226557" y1="16.981901068622136" x2="10.344170477990337" y2="37.84621823186521"></line>
<line x1="-99.99450481197604" y1="182.94091641902205" x2="10.344170477990337" y2="37.84621823186521"></line>
<line x1="-111.19343747422879" y1="-103.71666033252438" x2="10.344170477990337" y2="37.84621823186521"></line>
<line x1="197.77682810226557" y1="16.981901068622136" x2="73.69734375869983" y2="-114.13138675745854"></line>
</g>
<g prop="nodes" stroke="#000" stroke-width="1.5">
<g class="node"><image xlink:href="some_image.png" x="0" y="0" height="72" width="72" style="z-index: 3;"></image></g>
<g class="node"><image xlink:href="some_image.png" x="-7.373688780783198" y="6.754902942615239" height="72" width="72" style="z-index: 3;"></image></g>
<g class="node"><image xlink:href="some_image.png" x="1.2363864559502138" y="-14.087985964343622" height="72" width="72" style="z-index: 3;"></image></g>
<g class="node"><image xlink:href="some_image.png" x="10.538470205147267" y="13.745568221620495" height="72" width="72" style="z-index: 3;"></image></g>
<g class="node"><image xlink:href="some_image.png" x="-19.694269706308575" y="-3.4836390075862327" height="72" width="72" style="z-index: 3;"></image></g>
<g class="node"><image xlink:href="some_image.png" x="18.866941955758957" y="-12.001604111035421" height="72" width="72" style="z-index: 3;"></image></g>
<g class="node"><image xlink:href="some_image.png" x="-6.358980820385529" y="23.65509169134563" height="72" width="72" style="z-index: 3;"></image></g>
<g class="node"><image xlink:href="some_image.png" x="-12.194453649142762" y="-23.479678451778437" height="72" width="72" style="z-index: 3;"></image></g>
</g>
</g>
</svg>
Я довольно уверен, что использование групп все портит, но не могу понять, как правильно их использовать.
Ценю любую помощь.
Вот полная раскладка силы в виде фрагмента:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Parse tester</title>
<script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
</head>
<body>
<script>
var nodes = [
{id:0 , label:'branch1' , name:'branch1'},
{id:1 , label:'branch2' , name:'branch2'},
{id:2 , label:'branch3' , name:'branch3'},
{id:3 , label:'leaf1' , name:'leaf1'},
{id:4 , label:'leaf2' , name:'leaf2'},
{id:5 , label:'leaf3' , name:'leaf3'},
{id:6 , label:'center' , name:'center'},
{id:7 , label:'leaf23' , name:'leaf23'}
];
var links = [
{source:0 ,target:3, distance:150, weight:1},
{source:1 ,target:4, distance:150, weight:1},
{source:2 ,target:5, distance:150, weight:1},
{source:7 ,target:0, distance:150, weight:1},
{source:7 ,target:1, distance:150, weight:1},
{source:7 ,target:2, distance:150, weight:1},
{source:1 ,target:6, distance:150, weight:1},
{source:2 ,target:6, distance:150, weight:1}
];
//D3 stuff
var width=640, height = 480;
// add a SVG to the body for our viz
var svg=d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink(links).distance(200))
.force("x", d3.forceX())
.force("y", d3.forceY())
.alphaTarget(1)
.on("tick", ticked);
var g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
link = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".link"),
node = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".node");
restart();
function restart() {
// Apply the general update pattern to the nodes.
node = node.data(nodes, function(d) { return d.id;});
node.exit().remove();
node = node.enter()
.append('g')
.append('image')
.attr('xlink:href', 'http://i.imgur.com/Rx4N3wh.png')
.attr('width',25)
.attr('height',25)
.attr('x', function (d) {return d.x;})
.attr('y', function (d) {return d.y;})
.merge(node);
node.enter().selectAll('g').append('text')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.attr('y', -40)
.text(function (d) {
return d.label
});
// Apply the general update pattern to the links.
link = link.data(links, function(d) { return d.source.id + "-" + d.target.id; });
link.exit().remove();
link = link.enter().append("line").merge(link);
// Update and restart the simulation.
simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.alpha(1).restart();
}
//*/
function ticked() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
</script>
1 ответ
Во-первых, я заработал вторую скрипку, там были дополнительные} и пара запятых между цепочечными методами: fiddle.
Итак, в первом скрипте все прекрасно работает, насколько я понимаю: ссылки и узлы перемещаются, как это диктуется силовой разметкой. На второй скрипке ссылки продолжают двигаться, но теперь узлы g
с изображением не двигайся вообще.
Насколько я понимаю, ключевой вопрос: "Почему g
узел нарушает компоновку силы?", но, кажется, есть также некоторые потенциальные вопросы о функции тика и элементах вложенности в каждом g
узел.
Функция Force Tick и обновление шаблона
Давайте посмотрим на функцию галочки, которую вы используете для обоих:
function ticked() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
Тиковая функция вызывается каждый тик, она обновляет каждый узел на основе его данных. Симуляция силы не манипулирует визуальными элементами вообще, она манипулирует массивом данных узлов. При инициализации сила d3 создает новые свойства для каждого узла в массиве данных, например, те, которые представляют скорость и местоположение. Таким образом, d3 обновляет ваши данные, а не ваши элементы. Вот почему нам нужна функция галочки.
Теперь вышеприведенное не является типичным шаблоном обновления в d3 (но оно канонично для силы d3). Типичный шаблон обновления обычно выглядит так:
d3.selectAll(".node")
.data(data)
.attr("...")
.attr("...")...
Цепочка может быть разбита с помощью промежуточных вариантов выхода / ввода / объединения
Однако с объектами, которыми являются узлы в вашем массиве данных, d3 не копирует данные, чтобы связать их с каждым элементом, d3 фактически связывает каждый элемент с элементом в массиве данных. Что означает, что с node.attr("cx", function(d) {
, d
ссылается на связанный / связанный обновленный элемент в массиве данных, нет необходимости selection.data()
,
Я упоминаю об этом, потому что это нетипично, не очень хорошо известно (по моему мнению) и не объяснено в примерах или руководствах о том, почему сила использует (или может использовать) другой шаблон обновления. Кроме того, это могло быть источником путаницы, учитывая ваш комментарий: "Координаты определяются симом и постоянно динамически пересчитываются. Это то, что озадачивает меня в целом"
Что такое node
Выбор node
должен быть выбор g
элементы, но это на самом деле, выбор image
элементы:
node = node.enter()
.append('g') // returns a selection of `g` elements
.append('image') // returns a selection of `image` elements.
...
Элементы, которые вы выбираете, не являются g
с, но они ребенок image
элементы. И объединение их с другими элементами может вызвать проблемы. Разбейте цепочку, продолжайте node
в качестве выбора ваших узлов, в этом случае ваш g
элементы. Затем мы можем добавить к каждому узлу столько детей, сколько захотим, с большей легкостью.
(ради этого, вот скрипка с этим изменением, но мы еще не рассмотрели, почему ничего еще не движется).
Что ломает тик?
Как я отметил в комментариях, вы изменили свой узел с circle
к g
и вы заметили, что ваши узлы изначально размещены, но не обновляются. Это потому, что вам нужно изменить тиковую функцию. Вы обновляете узлы так:
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
Но, узел теперь выбор g
с и g
S не позиционируются cx
а также cy
атрибутов. Давайте изменим это на:
node.attr("transform", function(d) { return "translate("+[d.x,d.y]+")"; })
Вот обновленная скрипка с узлами, перемещающимися с каждым обновлением. Но теперь необходимо исправить положение.
позиционирование
Теперь мы обновляем узлы каждый тик, и каждый узел g
переводится так, что [0,0] является центром этого узла. Изображения, которые я вижу, имеют площадь 25 пикселей, поэтому для центрирования изображения нам нужно использовать отрицательные значения 12,5 для позиций x и y для каждого изображения ( скрипка).
Я не использую dx или dy для позиционирования изображения, как в вашей второй скрипке, потому что, поставив g
, Я могу расположить все относительно узла гораздо проще, и мне нужно только обновить один элемент для каждого узла каждый тик, g
, В противном случае мне придется обновлять все ярлыки, изображения и т. Д. Каждый тик.
Скрипка в скобках выше также не позиционирует узлы изначально - вы можете сделать это, но а) проще не позиционировать их, б) вы должны быть очень зоркими, чтобы увидеть их неуместными до первого тика - но некоторые люди очень зоркие, так что при входе их не помешает (здесь я делаю это не ради краткости).
Почему ярлыки не появляются
Я оставил код метки так же, как и рудиментарный блок кода (сначала я его не видел), но теперь мы можем взглянуть поближе:
node.enter().selectAll('g').append('text')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.attr('y', -40)
.text(function (d) {
return d.label
});
node.enter()
возвращает заполнители для каждого узла, который должен быть введен (так же, как при использовании для g
родители. Но эти заполнители не содержат каких-либо g
с, так node.enter().selectAll("g")
будет пустым, следовательно, текст не будет добавлен ни к одному элементу.
Мы хотим, чтобы каждый узел имел текст, и каждый узел находится в выделении node
так что мы просто используем:
node.append("text")....
Вот обновленная скрипка с вашими ярлыками.
Например, вы можете добавить к узлам любых других потомков.
Вам не нужно использовать.data() или что-либо еще для детей, потому что d3 предоставит каждому ребенку те же данные, что и у его родителя.
Даже если вы использовали node.append()
во второй скрипке, node
представлена подборка image
s - и вы не можете добавлять текст к изображениям - поэтому текст не будет виден.
И чтобы сохранить ответ немного более замкнутым, вот фрагмент конечного результата:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Parse tester</title>
<script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
</head>
<body>
<script>
var nodes = [
{id:0 , label:'branch1' , name:'branch1'},
{id:1 , label:'branch2' , name:'branch2'},
{id:2 , label:'branch3' , name:'branch3'},
{id:3 , label:'leaf1' , name:'leaf1'},
{id:4 , label:'leaf2' , name:'leaf2'},
{id:5 , label:'leaf3' , name:'leaf3'},
{id:6 , label:'center' , name:'center'},
{id:7 , label:'leaf23' , name:'leaf23'}
];
var links = [
{source:0 ,target:3, distance:150, weight:1},
{source:1 ,target:4, distance:150, weight:1},
{source:2 ,target:5, distance:150, weight:1},
{source:7 ,target:0, distance:150, weight:1},
{source:7 ,target:1, distance:150, weight:1},
{source:7 ,target:2, distance:150, weight:1},
{source:1 ,target:6, distance:150, weight:1},
{source:2 ,target:6, distance:150, weight:1}
];
//D3 stuff
var width=640, height = 480;
// add a SVG to the body for our viz
var svg=d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-1000))
.force("link", d3.forceLink(links).distance(200))
.force("x", d3.forceX())
.force("y", d3.forceY())
.alphaTarget(1)
.on("tick", ticked);
var g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"),
link = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".link"),
node = g.append("g").attr("stroke", "#000").attr("stroke-width", 1.5).selectAll(".node");
restart();
function restart() {
// Apply the general update pattern to the nodes.
node = node.data(nodes, function(d) { return d.id;});
node.exit().remove();
node = node.enter()
.append('g')
.attr("class","node")
.merge(node)
node.append('image')
.attr('xlink:href', 'http://i.imgur.com/Rx4N3wh.png')
.attr('width',25)
.attr('height',25)
.attr('x', -12.5)
.attr('y', -12.5)
node.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '.35em')
.attr('y', -40)
.text(function (d) {
return d.label
});
node.append("rect")
.attr("x", -12.5)
.attr("y", -12.5)
.attr("width",25)
.attr("height",25)
.attr("stroke-width", 4)
.attr("stroke","steelblue")
.attr("fill","none")
// Apply the general update pattern to the links.
link = link.data(links, function(d) { return d.source.id + "-" + d.target.id; });
link.exit().remove();
link = link.enter().append("line").merge(link);
// Update and restart the simulation.
simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.alpha(1).restart();
}
//*/
function ticked() {
node.attr("transform", function(d) { return "translate("+[d.x,d.y]+")"; })
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
</script>