Успокоить начальный тик раскладки силы
Я только начал баловаться с d3 и считаю, что кривая обучения довольно крутая. Этот процесс совершенно отличается от того, к чему я привык, и математика в большинстве случаев лежит у меня над головой.
В любом случае, мой проект состоит из силового макета, представляющего карту интеграций между системами. Эта часть работает очень хорошо, но у меня есть одна серьезная проблема, которая также представлена в демонстрации силовой направленности макета на сайте Майкла Бостока: когда узлы инициируются, они кажутся оторванными от холста. После этого вступает в действие некоторая серьезная математическая физика, имитирующая гравитационное притяжение, которое отправляет узлы по довольно запутанной траектории назад и вперед, пока они не успокоятся и не примут некоторые случайные координаты. Хотя эти движения кажутся крутыми при первом запуске демонстрации, когда вы пытаетесь просмотреть состояние сетевых интерфейсов с точки зрения компании, которой она управляет, а серверы просто не останавливаются, через некоторое время она становится утомительной.
Я уверен, что у меня есть правильная настройка макета для этого проекта, потому что я хочу, чтобы серверы автоматически размещались, я хочу визуализировать связи между ними. Я, однако, амбивалентен в отношении гравитационного эффекта.
Я думаю; Можно ли вручную установить начальное положение каждого узла, чтобы я мог расположить их ближе к гравитационному центру и немного сократить "время отскока"?
6 ответов
Все ответы, приведенные выше, неправильно поняли вопрос Ойстейна Амундсена.
Единственный способ стабилизировать усилие при его запуске - установить правильные значения для node.x и node.ya. Обратите внимание, что узлом являются данные d3.js, а не представленный тип DOM.
Например, если вы загружаете
nodes = [{"id": a}, {"id": b}, {"id": c}]
в
d3.layout.force().nodes(nodes)
Вы должны установить все.x и.y всех элементов в массиве узлов, это будет так (в coffeescript)
nodes = [{"id": a}, {"id": b}, {"id": c}]
for i in [0..2]
nodes[i].x = 500 #the initial x position of node
nodes[i].y = 300 #the initial y position of node
d3.layout.force().nodes(nodes).links(links)
тогда узлы начнут с позиции force.start(). это позволит избежать хаоса.
Внутренне, при "нормальном" использовании, раскладка силы неоднократно вызывает свою собственную tick()
метод асинхронно (через setInterval
или же requestAnimationFrame
), пока макет не остановится на решении. В этот момент его alpha()
значение равно или приближается к 0.
Итак, если вы хотите "перемотать" через этот процесс решения, вы можете синхронно вызвать это tick()
Метод снова и снова, пока альфа макета не достигнет значения, которое для ваших конкретных требований будет представлять собой "достаточно близкое" решение. Вот так:
var force = d3.layout.force(),
safety = 0;
while(force.alpha() > 0.05) { // You'll want to try out different, "small" values for this
force.tick();
if(safety++ > 500) {
break;// Avoids infinite looping in case this solution was a bad idea
}
}
if(safety < 500) {
console.log('success??');
}
После выполнения этого кода вы можете нарисовать свой макет на основе состояния узлов. Или, если вы рисуете свой макет путем привязки к тиковому событию (т.е. force.on('tick', drawMyLayout)
), вы захотите выполнить связывание после выполнения этого кода, потому что в противном случае вы будете без необходимости визуализировать макет сотни раз синхронно во время while
петля.
JohnS свел этот подход к одной краткой функции. Смотрите его ответ где-нибудь на этой странице.
Я имел дело с чем-то вроде этого некоторое время назад. Есть несколько вещей, чтобы рассмотреть.
1) Повторяющиеся тики имитируют систему, которая приходит в равновесие. Таким образом, невозможно избежать вызова тика столько раз, сколько необходимо, до того, как система установится и у вас будет автоматическая разметка. Тем не менее, вам не нужно обновлять визуализацию каждый тик, чтобы симуляция работала! На самом деле, итерации будут идти намного быстрее, если вы этого не сделаете. Соответствующая часть моего кода выглядит так:
var iters = 600; // You can get decent results from 300 if you are pressed for time
var thresh = 0.001;
if(!hasCachedLayout || optionsChanged || minorOptionsChanged) {
force.start(); // Defaults to alpha = 0.1
if(hasCachedLayout) {
force.alpha(optionsChanged ? 0.1 : 0.01);
}
for (var i = iters; i > 0; --i) {
force.tick();
if(force.alpha() < thresh) {
//console.log("Reached " + force.alpha() + " for " + data.nodes.length + " node chart after " + (iters - i) + " ticks.");
break;
}
}
force.stop();
}
Это выполняется синхронно, и после запуска я создаю элементы dom для всех узлов и ссылок. Для небольших графиков это выполняется довольно быстро, но вы обнаружите, что для больших графиков (более 100 узлов) существует задержка - они просто намного дороже в вычислительном отношении.
2) Вы можете кэшировать / посеять макеты. Расположение сил будет равномерно распределять узлы при инициализации, если не задана позиция! Поэтому, если вы убедитесь, что ваши узлы имеют атрибуты x и y, они будут использованы. В частности, когда я обновляю существующий график, я снова буду использовать позиции x и y из предыдущего макета.
Вы найдете хороший исходный макет, вам потребуется гораздо меньше итераций для достижения стабильной конфигурации. (Это то, что hasCachedLayout отслеживает в коде выше). NB. Если вы повторно используете одни и те же узлы из одного и того же макета, вам также нужно будет установить px и py в NaN, иначе вы получите странные результаты.
Основываясь на других ответах, я сделал этот метод:
function forwardAlpha(layout, alpha, max) {
alpha = alpha || 0;
max = max || 1000;
var i = 0;
while(layout.alpha() > alpha && i++ < max) layout.tick();
}
Может быть force.friction(0.5)
, или какое-то другое число ниже, чем по умолчанию 0,9, поможет? По крайней мере, это дает менее хаотичное впечатление при загрузке страницы.
var width = 960,
height = 500;
var fill = d3.scale.category20();
var force = d3.layout.force()
.size([width, height])
.nodes([{}]) // initialize with a single node
.linkDistance(30)
.charge(-60)
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("mousedown", mousedown);
svg.append("rect")
.attr("width", width)
.attr("height", height);
var nodes = force.nodes(),
links = force.links(),
node = svg.selectAll(".node"),
link = svg.selectAll(".link");
// var cursor = svg.append("circle")
// .attr("r", 30)
// .attr("transform", "translate(-100,-100)")
// .attr("class", "cursor");
restart();
function mousedown() {
var point = d3.mouse(this),
node = {
x: width / 2,
y: height / 2,
"number": Math.floor(Math.random() * 100)
},
n = nodes.push(node);
// add links to any nearby nodes
/* nodes.forEach(function(target) {
var x = target.x - node.x,
y = target.y - node.y;
if (Math.sqrt(x * x + y * y) < 30) {
links.push({source: node, target: target});
}
});
*/
restart();
}
function tick() {
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;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function restart() {
link = link.data(links);
link.enter().insert("line", ".node")
.attr("class", "link");
node = node.data(nodes);
// node.enter().insert("circle", ".cursor")
// .attr("class", "node")
// .attr("r", 5)
// .call(force.drag);
var nodeEnter = node.enter().insert("svg:g", ".cursor")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 5)
nodeEnter.append("svg:text")
.attr("class", "textClass")
.attr("x", 14)
.attr("y", ".31em")
.text(function(d) {
return d.number;
});
force.start();
}
rect {
fill: none;
pointer-events: all;
}
.node {
fill: #000;
}
.cursor {
fill: none;
stroke: brown;
pointer-events: none;
}
.link {
stroke: #999;
}
.textClass {
stroke: #323232;
font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
font-weight: normal;
stroke-width: .5;
font-size: 14px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Пример того, что вы можете искать. Он устанавливает атрибуты x & y новых узлов перед их вставкой в макет. Желаемое местоположение - центр элемента svg.