D3js избегать пересечений силового направления графа - PullRequest
0 голосов
/ 29 ноября 2018

Есть пример силового графа, который я пытался нарисовать с помощью d3.js.

У меня вообще 3 больших вопроса.И это код (вы можете запустить фрагмент кода ниже, он может работать):

function getRandomInt(max, min = 0) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function fdSortShit(g, nodeDimensions) {
  const gNodes = [];
  const gLinks = [];
  g.children().forEach(child => {
    gNodes.push({
      id: child,
      w: nodeDimensions[child].w,
      h: nodeDimensions[child].h,
      radius:
        Math.sqrt(
          nodeDimensions[child].w * nodeDimensions[child].w + nodeDimensions[child].h * nodeDimensions[child].h
        ) / 2
    });
  });
  g.edges().forEach(edge => {
    gLinks.push({ source: edge.v, target: edge.w });
  });
  const data = {
    nodes: gNodes,
    links: gLinks
  };
  const nodes = data.nodes;
  const links = data.links;

  const linkNodeRad = 5;
  const linkNodes = [];
  links.forEach((link, idx) => {
    if (link.source != link.target) {
      linkNodes.push({
        id: `link-node-${idx}`,
        source: nodes.filter(e => {
          return e.id == link.source;
        })[0],
        target: nodes.filter(e => {
          return e.id == link.target;
        })[0],
        radius: linkNodeRad
      });
    }
  });

  const width = 800;
  const height = 600;

  var svg = d3
    .select("body")
    .append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("viewBox", "-400, -300, 800, 600");

  function forceSimulation(nodes, links) {
    return d3
      .forceSimulation(nodes)
      .force("link", d3.forceLink(links).id(d => d.id))
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter())
      .force(
        "collision",
        d3.forceCollide().radius(function(d) {
          return d.radius;
        })
      );
  }

  var link = svg
    .selectAll(".link")
    .attr("stroke", "#fff")
    .data(links)
    .enter()
    .append("line")
    .attr("class", "link");

  var node = svg
    .append("g")
    .selectAll("g")
    .data(nodes)
    .enter()
    .append("g");

  var circles = node
    .append("circle")
    .attr("class", "node")
    .attr("r", node => {
      return node.radius;
    });
  var text = node
    .append("text")
    .text(d => {
      return d.id;
    })
    .attr("class", "node-caption")
    .attr("x", 0)
    .attr("y", 0);

  var linkNode = svg
    .selectAll(".link-node")
    .data(linkNodes)
    .enter()
    .append("circle")
    .attr("class", "link-node")
    .attr("r", linkNodeRad);

  function ticked() {
    link
      .attr("x1", d => d.source.x)
      .attr("y1", d => d.source.y)
      .attr("x2", d => d.target.x)
      .attr("y2", d => d.target.y);

    node.attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });

    linkNode
      .attr("cx", function(d) {
        return (d.x = (d.source.x + d.target.x) * 0.5);
      })
      .attr("cy", function(d) {
        return (d.y = (d.source.y + d.target.y) * 0.5);
      });
  }

  forceSimulation(nodes.concat(linkNodes), links)
    .on("tick", ticked)
    .on("end", () => {
      console.warn("END");
    });
}
  
const coords = {};
const size = { min: 10, max: 30 };
const dotStr = "graph g { a--a;a--b;a--b;a--c;a--d;a--e;b--b1;c--c1;c--c2;d--d1;d--d2;d--d3;d--d4;e--e1;v--w;v--x;v--y;w--z;w--w1;x--x1;x--x2;y--y1;y--y2;y--y3;y--y4;z--z1;v--a; }";
const g = graphlibDot.read(dotStr);
g.children().forEach(child => {
  const x = getRandomInt(1024 - 10, 10);
  const y = getRandomInt(768 - 10, 10);
  coords[child] = {
    x: x,
    y: y,
    w: getRandomInt(size.max, size.min),
    h: getRandomInt(size.max, size.min)
  };
});

fdSortShit(g, coords);
svg {
  background-color: lightgray;
}
circle.node {
  fill: lightcoral;
}
circle.link-node {
  fill: rgba(0, 0, 255, 0.2);
  /* fill: transparent; */
}
line.link {
  stroke: lightseagreen;
}
text.node-caption {
  font: normal 10px courier new;
}
<script src="https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.2/dist/graphlib-dot.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Изображение выглядит так:

enter image description here

ПервыйВопрос в том, что делать, чтобы избежать пересечений?enter image description here Я знаю, что не могу увернуться от всех пересечений ребер, но хочу минимизировать их.Этот пример представляет собой древовидный граф без циклов.Я знаю, что есть способ построить его без пересечения краев.Но я не знаю, как это сделать с помощью этого алгоритма.
enter image description here Но все равно раздражает пересечение.

Второй вопрос: как насчет НЕ симулировать силы ввремя (мне не нужна анимация), но просто чтобы получить окончательный результат?Когда я использую forceSimulation.on("end", cb), это здорово, но задержка между пуском и остановкой велика ... но этот график - всего лишь маленький пример.Я не могу так долго ждать большего.

И третий вопрос: как применить принудительно заданные настройки?Сила энергии, жесткость, отталкивание, демпфирование и т. Д.?Не могу найти их на d3 @ 5

Окончательный результат, которого хочет мой руководитель проекта:

  • без перекрытия узлов;
  • минимизация пересечений ребер-ребер;
  • свести к минимуму пересечения ребер.

Я готов к диалогу.

1 Ответ

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

Вы применяете настройки силы в части инициализации.Вот пример -

var simulation = d3.forceSimulation()                              //Modify link distance/strength here
    .force("link", d3.forceLink().id(function (d) { return d.id; }).distance(80).strength(1))
    .force("charge", d3.forceManyBody().strength(-15)) //Charge strength is here
    .force("center", d3.forceCenter(width / 2, height / 2));

Это может быть использовано для решения одной из ваших проблем ... если вы установите силу "заряда" на какое-то большое отрицательное число, например -150, узлы будут сильно отталкиватьсятак что они не перекрываются и не имеют никаких ссылок.Если вы вообще не хотите перетаскивать график, это все, что вам нужно, чтобы избежать наложений.

Побочным эффектом сильно отрицательного заряда является то, что график устанавливается довольно быстро, и, как вы этого не делаетеЕсли вы хотите смоделировать силы в реальном времени после начального отображения, вы можете вызвать simulation.stop(), чтобы остановить или остановить симуляцию.

...