D3: Использование принудительного макета для облаков слов

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

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

Я объявляю силы глобальными, а затем останавливаю и запускаю их, когда пользователь нажимает кнопку:

var force1 = d3.layout.force()
    .size([width, height])
    .charge(0)
    .gravity(0.02)
    .on("tick", ticka);

//layout for node chart
var force2 = d3.layout.force()
    .size([width, height])
    .charge(-50)
    .gravity(0.005)
    .linkDistance(120)
    .on("tick", tickb);

//layout for bubble chart
var force3 = d3.layout.force()
    .size([width, height])
    .charge(0)
    .gravity(0.02)
    .on("tick", tickc);

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

Код для создания данных узла выглядит следующим образом:

nodes = splicedCounts.map(function(d, e) {
    var choice;
    var i = 0,
    r = d[1],
    d = { count: d[1],
          sentiment: d[2]/d[1],
          cluster: i,
          radius: radScale(r),
          name: d[0],
          index: e,
          x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
          y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
    };
    if (!clusters[i] || (r > clusters[i].radius))
        clusters[i] = d;
    return d;
});

Чтобы этот вопрос был относительно кратким, код, который я использую для рисования пузырьковой диаграммы, является производным от этого примера: http://bl.ocks.org/mbostock/7881887 и код для рисования диаграммы узлов также являются общими (я буду рад предоставить этот код, если он поможет решить мою проблему).

Вот где возникает моя проблема:

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

Полные функции «галочки» для диаграммы слов приведены ниже.

function tickc(e) {
    node = nodeGroup.selectAll(".node");
    var nodeText = nodeGroup.selectAll(".node text");
    node.each(cluster(5 * e.alpha * e.alpha));
    var k = e.alpha;
    nodeText.each(function(a, i) {
        var compWidth = d3.select(this).attr("bWidth");
        var compHeight = d3.select(this).attr("bHeight");
        nodes.slice(i + 1).forEach(function(b) {
          // console.log(a);
          var lineWidthA = a["name"].length * a["radius"]/2.5;
          var lineHeightA = a["radius"]/0.9;

          var lineWidthB = b["name"].length * b["radius"]/2.5;
          var lineHeightB = b["radius"]/0.9;
          dx =  (a.x - b.x)
          dy =  (a.y - b.y)    
          adx = Math.abs(dx)
          ady = Math.abs(dy)
          mdx = (1 + 0.07) * (lineWidthA + lineWidthB)/2
          mdy = (1 + 0.07) * (lineHeightA + lineHeightB)/2
          if (adx < mdx  &&  ady < mdy) {          
            l = Math.sqrt(dx * dx + dy * dy)

            lx = (adx - mdx) / l * k
            ly = (ady - mdy) / l * k

            // 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
          }
        });
  });
node.select("circle")
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });
node.select("text")
    .attr("x", function(d) { return d.x; })
    .attr("y", function(d) { return d.y; });
}
// Move d to be adjacent to the cluster node.
function cluster2(alpha) {
  return 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["name"].length * d["radius"]) + (cluster["name"].length * cluster["radius"]);

  };
}

Я не знал, как завершить функцию кластеризации, чтобы правильно перемещать узлы. Я попытался адаптировать стандартную функцию кластера, т.е.

// Move d to be adjacent to the cluster node.
function cluster(alpha) {
  return 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.radius + cluster.radius;
    if (l != r) {
      l = (l - r) / l * alpha;
      d.x -= x *= l;
      d.y -= y *= l;
      cluster.x += x;
      cluster.y += y;
    }
  };
} 

чтобы быть более похожим на вышеупомянутую прямоугольную компоновку сил кластера, но без везения (боюсь, у меня больше нет копий моих точных попыток).

Боюсь, я не могу прикреплять изображения из-за отсутствия у меня репутации, но я могу попытаться найти способ предоставить их, если это поможет. Проблема перекрытия с облаком слов незначительна (большинство слов разрешается в смежные, но не соприкасающиеся позиции), но, если возможно, я бы хотел, чтобы она разрешилась так же идеально, как пузырьковая диаграмма. Я почти уверен, что эти проблемы возникли из-за а.) незавершенной функции кластера и б.) моего взлома с использованием длины и радиуса текста для оценки размера текста, а не правильных координат ограничивающей рамки, но я не уверен, как именно это исправить эти вещи.


person Jess    schedule 14.08.2014    source источник
comment
Я мог бы вернуться к вам, если я получу это реализовано. Я знаю, что NYTimes сделали что-то подобное с облаками слов-пузырей: nytimes.com/interactive/2012/09/06/us/politics/   -  person Aman    schedule 03.05.2015
comment
Не выполняйте обнаружение столкновений, вместо этого поместите отрицательное целое число в параметр заряда, тогда узлы отталкиваются друг от друга.   -  person David Lemon    schedule 18.06.2015
comment
Верна ли рекомендация @DavidLemon? Если кто-то может это проверить, было бы полезно получить ответ.   -  person Peter Behr    schedule 29.03.2017
comment
У меня точно такая же проблема. @DavidLemon проблема с зарядом в том, что он не работает для прямоугольников. Я видел демонстрацию с использованием внутренних кругов и размещением над ними прямоугольников, но слова в большинстве случаев слишком длинны для этого подхода.   -  person leAthlon    schedule 25.08.2017


Ответы (1)


Я бы рекомендовал использовать пакет d3-cloud, который должен делать многое из того, что вам нужно. Если нет, то, по крайней мере, это хорошая отправная точка https://github.com/jasondavies/d3-cloud/blob/master/build/d3.layout.cloud.js

Похоже, это работает путем вычисления границ для каждого слова, а затем разрешения коллизий между этими границами. Вы можете увидеть это здесь

person Ian    schedule 19.07.2017