Можно ли указать пользовательскую функцию силы для макета с направлением силы?

Я хочу поэкспериментировать с альтернативными семейными силовыми функциями для макетов графов, ориентированных на силу.

Для каждого узла n_i Я могу определить "функцию силы" f_i такой, что

  • f_i ( n_i ) тождественно ноль; а также
  • f_i ( n_j ), где n_i != n_j, это сила на узле n_i это связано с каким-то другим узлом n_j,

Чистая сила на узле n_i тогда должна быть векторная сумма сил f_i ( n_j ), где n_j распространяется на все остальные узлы 1.

Есть ли какой-нибудь способ указать d3.js использовать эти пользовательские функции форсирования в алгоритме компоновки?

[ Документация к силовому макету d3.js описывает различные способы настройки его встроенной функции силы, но я не смог найти способ полностью указать совершенно другую функцию силы, то есть функцию силы, которая не может быть достигнуто путем настройки параметров встроенной функции силы.]


1 IOW, никакие другие / дополнительные силы не должны действовать на узел n_i кроме тех, которые рассчитаны по его силовой функции f_i ,

2 ответа

Решение

Для этого вам нужно создать свой собственный макет. Я не знаю учебника по этому вопросу, но исходный код для существующего макета Force должен быть хорошей отправной точкой, так как, по звучанию, структура вашего пользовательского макета будет очень похожа на эту.

Да, ты можешь. Кредит идет к Шен Картер и его пример.

let margin = {
  top: 100,
  right: 100,
  bottom: 100,
  left: 100
};

let width = 960,
  height = 500,
  padding = 1.5, // separation between same-color circles
  clusterPadding = 6, // separation between different-color circles
  maxRadius = 12;

let n = 200, // total number of nodes
  m = 10, // number of distinct clusters
  z = d3.scaleOrdinal(d3.schemeCategory20),
  clusters = new Array(m);

let svg = d3.select('body')
  .append('svg')
  .attr('height', height)
  .attr('width', width)
  .append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');

let nodes = d3.range(200).map(() => {
  let i = Math.floor(Math.random() * m),
    radius = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
    d = {
      cluster: i,
      r: radius
    };
  if (!clusters[i] || (radius > clusters[i].r)) clusters[i] = d;
  return d;
});

let circles = svg.append('g')
  .datum(nodes)
  .selectAll('.circle')
  .data(d => d)
  .enter().append('circle')
  .attr('r', (d) => d.r)
  .attr('fill', (d) => z(d.cluster))
  .attr('stroke', 'black')
  .attr('stroke-width', 1);

let simulation = d3.forceSimulation(nodes)
  .velocityDecay(0.2)
  .force("x", d3.forceX().strength(.0005))
  .force("y", d3.forceY().strength(.0005))
  .force("collide", collide) // <<-------- CUSTOM FORCE
  .force("cluster", clustering)//<<------- CUSTOM FORCE 
  .on("tick", ticked);

function ticked() {
  circles
    .attr('cx', (d) => d.x)
    .attr('cy', (d) => d.y);
}

// Custom 'clustering' force implementation.
function clustering(alpha) {
  nodes.forEach(function(d) {
    var cluster = clusters[d.cluster];
    if (cluster === d) return;
    var x = d.x - cluster.x,
      y = d.y - cluster.y,
      l = Math.sqrt(x * x + y * y),
      r = d.r + cluster.r;
    if (l !== r) {
      l = (l - r) / l * alpha;
      d.x -= x *= l;
      d.y -= y *= l;
      cluster.x += x;
      cluster.y += y;
    }
  });
}
// Custom 'collide' force implementation.
function collide(alpha) {
  var quadtree = d3.quadtree()
    .x((d) => d.x)
    .y((d) => d.y)
    .addAll(nodes);

  nodes.forEach(function(d) {
    var r = d.r + maxRadius + Math.max(padding, clusterPadding),
      nx1 = d.x - r,
      nx2 = d.x + r,
      ny1 = d.y - r,
      ny2 = d.y + r;
    quadtree.visit(function(quad, x1, y1, x2, y2) {

      if (quad.data && (quad.data !== d)) {
        var x = d.x - quad.data.x,
          y = d.y - quad.data.y,
          l = Math.sqrt(x * x + y * y),
          r = d.r + quad.data.r + (d.cluster === quad.data.cluster ? padding : clusterPadding);
        if (l < r) {
          l = (l - r) / l * alpha;
          d.x -= x *= l;
          d.y -= y *= l;
          quad.data.x += x;
          quad.data.y += y;
        }
      }
      return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
    });
  });
}
<!doctype html>
<meta charset="utf-8">

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

Также здесь более углубленный взгляд на предмет.

Другие вопросы по тегам