Ограничение перемещений узлов в границе ориентированного графа D3

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

Я интегрировал приведенный ниже направленный граф в свой угловой код. http://bl.ocks.org/fancellu/2c782394602a93921faff74e594d1bb1

HTML-тег:

<svg width = "960" height = "600"></svg>

Javascript-код:

var colors = d3.scaleOrdinal(d3.schemeCategory10);

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    node,
    link;

svg.append('defs').append('marker')
    .attrs({
        'id': 'arrowhead',
        'viewBox': '-0 -5 10 10',
        'refX': 13,
        'refY': 0,
        'orient': 'auto',
        'markerWidth': 13,
        'markerHeight': 13,
        'xoverflow': 'visible'
    })
    .append('svg:path')
    .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
    .attr('fill', '#999')
    .style('stroke', 'none');

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function (d) { return d.id; }).distance(100).strength(1))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));

d3.json("graph.json", function (error, graph) {
    if (error) throw error;
    update(graph.links, graph.nodes);
})

function update(links, nodes) {
    link = svg.selectAll(".link")
        .data(links)
        .enter()
        .append("line")
        .attr("class", "link")
        .attr('marker-end', 'url(#arrowhead)')

    link.append("title")
        .text(function (d) { return d.type; });

    edgepaths = svg.selectAll(".edgepath")
        .data(links)
        .enter()
        .append('path')
        .attrs({
            'class': 'edgepath',
            'fill-opacity': 0,
            'stroke-opacity': 0,
            'id': function (d, i) { return 'edgepath' + i }
        })
        .style("pointer-events", "none");

    edgelabels = svg.selectAll(".edgelabel")
        .data(links)
        .enter()
        .append('text')
        .style("pointer-events", "none")
        .attrs({
            'class': 'edgelabel',
            'id': function (d, i) { return 'edgelabel' + i },
            'font-size': 10,
            'fill': '#aaa'
        });

    edgelabels.append('textPath')
        .attr('xlink:href', function (d, i) { return '#edgepath' + i })
        .style("text-anchor", "middle")
        .style("pointer-events", "none")
        .attr("startOffset", "50%")
        .text(function (d) { return d.type });

    node = svg.selectAll(".node")
        .data(nodes)
        .enter()
        .append("g")
        .attr("class", "node")
        .call(d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            //.on("end", dragended)
        );

    node.append("circle")
        .attr("r", 5)
        .style("fill", function (d, i) { return colors(i); })

    node.append("title")
        .text(function (d) { return d.id; });

    node.append("text")
        .attr("dy", -3)
        .text(function (d) { return d.name + ":" + d.label; });

    simulation
        .nodes(nodes)
        .on("tick", ticked);

    simulation.force("link")
        .links(links);
}

function ticked() {
    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 + ")"; });

    edgepaths.attr('d', function (d) {
        return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
    });

    edgelabels.attr('transform', function (d) {
        if (d.target.x < d.source.x) {
            var bbox = this.getBBox();

            rx = bbox.x + bbox.width / 2;
            ry = bbox.y + bbox.height / 2;
            return 'rotate(180 ' + rx + ' ' + ry + ')';
        }
        else {
            return 'rotate(0)';
        }
    });
}

function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart()
    d.fx = d.x;
    d.fy = d.y;
}

function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
424
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Просто добавьте функцию для ограничения допустимых значений x/y вашими границами:

function checkBounds(d){
  if (d.x < 0) d.x = 0;
  if (d.x > width) d.x = width;
  if (d.y < 0) d.y = 0;
  if (d.y > width) d.y = height;
}

Затем вызовите его в методе тика:

link
  .attr("x1", function (d) { 
      checkBounds(d.source);
      return d.source.x; 
    })
  .attr("y1", function (d) { 
      return d.source.y; 
    })
  .attr("x2", function (d) { 
      checkBounds(d.target);
      return d.target.x; 
    })
  .attr("y2", function (d) { 
    return d.target.y; 
    });

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

Пример запуска:

<!DOCTYPE html>

<html>
  <head>
    <style type = "text/css">
        .node {}

        .link { stroke: #999; stroke-opacity: .6; stroke-width: 1px; }

        svg {border: 1px solid black}
    </style>

  </head>

  <body>
    <script src = "https://d3js.org/d3.v4.min.js" type = "text/javascript"></script>
    <script src = "https://d3js.org/d3-selection-multi.v1.js"></script>

    <svg width = "400" height = "400"></svg>
    <script>
      var colors = d3.scaleOrdinal(d3.schemeCategory10);

      var svg = d3.select("svg"),
          width = +svg.attr("width"),
          height = +svg.attr("height"),
          node,
          link;

      svg.append('defs').append('marker')
          .attrs({
              'id': 'arrowhead',
              'viewBox': '-0 -5 10 10',
              'refX': 13,
              'refY': 0,
              'orient': 'auto',
              'markerWidth': 13,
              'markerHeight': 13,
              'xoverflow': 'visible'
          })
          .append('svg:path')
          .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
          .attr('fill', '#999')
          .style('stroke', 'none');

      var simulation = d3.forceSimulation()
          .force("link", d3.forceLink().id(function (d) { return d.id; }).distance(100).strength(1))
          .force("charge", d3.forceManyBody())
          .force("center", d3.forceCenter(width / 2, height / 2));

      var graph = {
        "nodes": [
          {
            "name": "Peter",
            "label": "Person",
            "id": 1
          },
          {
            "name": "Michael",
            "label": "Person",
            "id": 2
          },
          {
            "name": "Neo4j",
            "label": "Database",
            "id": 3
          },
          {
            "name": "Graph Database",
            "label": "Database",
            "id": 4
          }
        ],
        "links": [
          {
            "source": 1,
            "target": 2,
            "type": "KNOWS",
            "since": 2010
          },
          {
            "source": 1,
            "target": 3,
            "type": "FOUNDED"
          },
          {
            "source": 2,
            "target": 3,
            "type": "WORKS_ON"
          },
          {
            "source": 3,
            "target": 4,
            "type": "IS_A"
          }
        ]
      };

      update(graph.links, graph.nodes);

      function update(links, nodes) {
          link = svg.selectAll(".link")
              .data(links)
              .enter()
              .append("line")
              .attr("class", "link")
              .attr('marker-end', 'url(#arrowhead)')

          link.append("title")
              .text(function (d) { return d.type; });

          edgepaths = svg.selectAll(".edgepath")
              .data(links)
              .enter()
              .append('path')
              .attrs({
                  'class': 'edgepath',
                  'fill-opacity': 0,
                  'stroke-opacity': 0,
                  'id': function (d, i) { return 'edgepath' + i }
              })
              .style("pointer-events", "none");

          edgelabels = svg.selectAll(".edgelabel")
              .data(links)
              .enter()
              .append('text')
              .style("pointer-events", "none")
              .attrs({
                  'class': 'edgelabel',
                  'id': function (d, i) { return 'edgelabel' + i },
                  'font-size': 10,
                  'fill': '#aaa'
              });

          edgelabels.append('textPath')
              .attr('xlink:href', function (d, i) { return '#edgepath' + i })
              .style("text-anchor", "middle")
              .style("pointer-events", "none")
              .attr("startOffset", "50%")
              .text(function (d) { return d.type });

          node = svg.selectAll(".node")
              .data(nodes)
              .enter()
              .append("g")
              .attr("class", "node")
              .call(d3.drag()
                  .on("start", dragstarted)
                  .on("drag", dragged)
                  //.on("end", dragended)
              );

          node.append("circle")
              .attr("r", 5)
              .style("fill", function (d, i) { return colors(i); })

          node.append("title")
              .text(function (d) { return d.id; });

          node.append("text")
              .attr("dy", -3)
              .text(function (d) { return d.name + ":" + d.label; });

          simulation.nodes(nodes)
              .on("tick", ticked);

          simulation.force("link")
              .links(links);
      }

      function ticked() {

          link
              .attr("x1", function (d) { 
                  checkBounds(d.source);
                  return d.source.x; 
                })
              .attr("y1", function (d) { 
                  return d.source.y; 
                })
              .attr("x2", function (d) { 
                  checkBounds(d.target);
                  return d.target.x; 
                })
              .attr("y2", function (d) { 
                return d.target.y; 
                });

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

          edgepaths.attr('d', function (d) {
              return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
          });

          edgelabels.attr('transform', function (d) {
              if (d.target.x < d.source.x) {
                  var bbox = this.getBBox();

                  rx = bbox.x + bbox.width / 2;
                  ry = bbox.y + bbox.height / 2;
                  return 'rotate(180 ' + rx + ' ' + ry + ')';
              }
              else {
                  return 'rotate(0)';
              }
          });
      }

      function dragstarted(d) {
          if (!d3.event.active) simulation.alphaTarget(0.3).restart()
          d.fx = d.x;
          d.fy = d.y;
      }

      function dragged(d) {
          d.fx = d3.event.x;
          d.fy = d3.event.y;
      }

      function checkBounds(d){
        if (d.x < 0) d.x = 0;
        if (d.x > width) d.x = width;
        if (d.y < 0) d.y = 0;
        if (d.y > width) d.y = height;
      }
    </script>
  </body>
</html>

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