D3.js разложил квадраты по кругу с силой - PullRequest
0 голосов
/ 28 ноября 2018

Я хочу распределить квадраты с содержимым по кругу с помощью силы () и столкновения, чтобы квадраты не перекрывали друг друга и основной круг.Есть мысли как это сделать?Должен ли я использовать ссылки, чтобы сделать это?Вот скрипка http://jsfiddle.net/benderlio/usbq839m/3/ Иногда квадраты растекаются нормально, но большую часть времени квадраты сдвигаются, как на картинке.

enter image description here

    var force = d3.forceSimulation(nodes)
      .force("charge", d3.forceManyBody(1130))
     // .force('link', d3.forceLink().links(links))
      .on("tick", function () {

        var k = this.alpha(),
          kg = k * .02,
          spaceAround = 0.04;

        //console.log('', nodes);

        nodes.forEach(function (a, i) {
                       ...
        });

        svg.selectAll("rect")
          .style("fill", function (d) {
            return "#ccc"
          })
          .attr("x", function (d) {
            return d.x - d.width / 2;
          })
          .attr("y", function (d) {
            return d.y - d.height / 2;
          });

        svg.selectAll("line")
          .attr("x2", function (d) {
            return d.x;
          })
          .attr("y2", function (d) {
            return d.y;
          })

        svg.selectAll("circle.end")
          .attr("cx", function (d) {
            return d.x;
          })
          .attr("cy", function (d) {
            return d.y;
          })

      });

Спасибо.

UPD:

Добавлен основной фиксированный квадрат, теперь он выглядит лучше.Но похоже, что я должен что-то сделать с «порядком столкновений».Квадраты перевернуты.Есть мысли как это исправить?enter image description here

Ответы [ 2 ]

0 голосов
/ 29 ноября 2018

Ваша сила столкновения слишком велика.Сделайте это немного меньше.

              lx *= 0.3;
              ly *= 0.3;

Центральный прямоугольник может быть добавлен с помощью

nodes.push( { x:500, y:500, width:300, height:300, fx:500, fy:500, center: true });

Рисование ректов теперь должно быть отфильтровано

  var g = svg.selectAll("g")
    .data(nodes.filter(d => !d.center))
    .enter()
    .append("g")

И еслиВы также рисуете прямоугольник в центре, вы должны добавить класс

  g.append("rect")
    .attr("opacity", 0.5)
    .attr("class", "node")
    .attr("fill", "#ccc" )
    .attr("width", d => d.width )
    .attr("height", d => d.height )
    .attr("rx", 10)
    .attr("ry", 10);

И в тик-функции фильтра на ритах с классом

svg.selectAll("rect.node")
  .attr("x", d => d.x - d.width / 2 )
  .attr("y", d => d.y - d.height / 2 );

Добавить тест, чтобы игнорировать центр rect

          if (a.center) return;

Обновлен код силы

        nodes.forEach(function (a, i) {
          if (a.center) return;  // ignore this node
          // Apply gravity forces.
          a.x += (a.gravity.x - a.x) * kg;
          a.y += (a.gravity.y - a.y) * kg;
          nodes.slice(i + 1).forEach(function (b) {
            dx = (a.x - b.x)
            dy = (a.y - b.y)
            adx = Math.abs(dx)
            ady = Math.abs(dy)
            mdx = (1 + spaceAround) * (a.width + b.width) / 2
            mdy = (1 + spaceAround) * (a.height + b.height) / 2
            if (adx < mdx && ady < mdy) {
              l = Math.sqrt(dx * dx + dy * dy)
              lx = (adx - mdx) / l * k
              ly = (ady - mdy) / l * k

              lx *= 0.3;
              ly *= 0.3;

              // choose the direction with less overlap
              if (lx > ly && ly > 0) lx = 0;
              else if (ly > lx && lx > 0) ly = 0;
              dx *= lx
              dy *= ly
              a.x -= dx
              a.y -= dy
              b.x += dx
              b.y += dy
            }
          });
        });

Редактировать

Модификация будет делать движение независимым от расстояния прямоугольникацентры.Перемещайте только определенную величину в направлении, противоположном другому центру.

Текущий метод изменяет узлы, находящиеся позже в списке, уже на новую позицию и основывает столкновение следующих узлов на этих новых позициях.Лучше сначала рассчитать движение на основе всех узлов за время N и в конце применить это движение.Это делается в симуляции силы путем вычисления / изменения скорости (d.vx, d.vy) узла.Симуляция применит скорость на тике.

0 голосов
/ 28 ноября 2018

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

  1. Когда вы определяете "целевые точки" для алгоритма гравитации Rects, сделайтеони не на границе круга, а на некотором расстоянии.

    x = ((150 + radius) * Math.cos(angle)) + (width / 2) + offset; // Calculate the x position of the element.
    y = ((150 + radius) * Math.sin(angle)) + (width / 2) + offset; // Calculate the y position of the element.
    
  2. Если вы хотите, чтобы риты не сталкивались с кругом, вам нужна специальная сила.Поскольку точная формула для столкновения прямоугольника непроста, возможно, достаточно рассматривать прямоугольники как круги, поэтому добавьте это после nodes.forEach(...):

    nodes.forEach(function(a) {
       const diag = Math.sqrt(a.width ** 2 + a.height ** 2) / 2;
       const rad = 150;
       const [cx, cy] = [500, 500];
       const [dx, dy] = [a.x - cx, a.y - cy];
       const dist = Math.sqrt(dx ** 2 + dy ** 2);
       const shift = Math.max(0, rad + diag - dist);
       a.x += shift * dx / dist;
       a.y += shift * dy / dist;
    })
    

screenshot

...