Как вставить узел в ломаную линию в gojs, сохранив позиции точек в ссылках по обе стороны от нового узла - PullRequest
0 голосов
/ 31 августа 2018

Я задал вопрос о StackOverflow ( Ищу библиотеку Javascript для отображения и редактирования сетей узлов и ребер ) и указал на пример соединения gojs.

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

Приложение, которое я пытаюсь создать, - это редактирование границ на карте:

  • узел = место, где встречаются три или границы
  • ссылка = участок границы между двумя узлами.

Следовательно, узлы и ссылки не имеют маркировки (узлы - это просто маленькие кружки, ссылки - просто полилинии).

Я попытался адаптировать образец сращивания (https://gojs.net/temp/splicing.html) соответствующим образом. Основные характеристики, которые мне нужны, помимо того, что делает образец:

  • выбор места размещения нового узла на связи между существующими
  • сохранение формы полилиний.

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

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

Я пытался сделать это путем переопределения методов LinkReshapingTool с использованием механизма расширения, описанного в https://gojs.net/latest/intro/extensions.html (вместо создания подкласса).

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

Я пробовал разные вещи, но безуспешно, например:

  • отсрочка сращивания до завершения работы инструмента
  • передача точек в измененные ссылки различными способами (массив v список v строка)
  • помещая обработку в различные переопределенные методы.

Мой текущий код указан ниже. Прошу прощения за грубые стилистические ошибки - я не опытный программист JavaScript.

<!DOCTYPE html>  <!-- HTML5 document type -->
<!--
Adapted from splicing example from gojs.net
 -->
<html>
<head>
  <!-- use go-debug.js when developing and go.js when deploying -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/1.8.28/go-debug.js"></script>
</head>
<body>

<div id="myDiagramDiv"
     style="width:400px; height:300px; background-color: #DAE4E4;"></div>

<script>
  var $ = go.GraphObject.make;

  // state variables to remember what to do after reshaping tool aborted
  var doSpliceNode     = false;
  var doSpliceIntoLink = null;
  var doSplicePoint    = null;
  var doSpliceIndex    = -1;  

  // diagram
  var myDiagram = $(go.Diagram, "myDiagramDiv",
                     {
                       "undoManager.isEnabled": true
                     });

  var tool = myDiagram.toolManager.linkReshapingTool;

  // Override doMouseDown on linkreshapingtool.  If user clicks on a handle with SHIFT pressed, want to insert
  // a new node at that point rather than use the default behaviour to (further) reshape the link, and moreover
  // want to retain the points in the link.  I.e. turn one of the points in the link into a new node.
  // (Existing gojs splicing example does not do this: it just puts a new node at the midpoint of the existing
  // link, with no regard to the points along the link.)
  tool.doMouseDown = function() {

    console.log("mousedown at (" + this.Fp.M + "," + this.Fp.N + ")");
    console.log(" on link from " + this.adornedLink.fromNode + " to " + this.adornedLink.toNode);
    console.log(" with shift pressed? " + myDiagram.lastInput.shift);


    var spliced = false;

    if (myDiagram.lastInput.shift)
    {

      // work out which of the points on the link was clicked
      var link   = this.adornedLink;
      var numpts = link.pointsCount;
      var i;
      var x = this.Fp.M; // @@TODO - by inspection in debugger this contains the X coord, but what's documented place to get this?
      var y = this.Fp.N; // @@TODO - ditto for Y coord

      for (i = 1; !spliced && (i < numpts - 1); i++)
      {
        if ((link.getPoint(i).x == x) && (link.getPoint(i).y == y))
        {
           console.log(" .. at point " + i);

           // Store off what to do.  (This used to be done inline - deferred to after as one of the things
           // to try to make it work.)
           doSpliceNode     = true;
           doSpliceIntoLink = link;
           doSplicePoint    = new go.Point(x, y);
           doSpliceIndex    = i;  

           spliced = true;
        }
      }
    }

    //if (!doSpliceNode)
    { 
      console.log(".. call base class doMouseDown");
      go.LinkReshapingTool.prototype.doMouseDown.call(tool);
    }
  }

  // Override doMouseUp as well.  If we had decided during mousedown to do the splice, then stop the tool now.
  tool.doMouseUp = function()
  {
    // First call base class
    go.LinkReshapingTool.prototype.doMouseUp.call(tool);

    if (doSpliceNode)
    {
      // Doing splice - stop tool
      console.log("STOP TOOL");
      this.stopTool();
      this.doDeactivate();
    }
  }  

  // Finally, override doStop to actually do the splice
  tool.doStop = function() {

    console.log("doStop");

    // First call base class
    go.LinkReshapingTool.prototype.doStop.call(tool);


    if (doSpliceNode)
    {
      // now splice the node
      console.log("splice node");
      spliceNewNodeIntoLink2(doSpliceIntoLink, doSplicePoint, doSpliceIndex);  // @@TODO make it respect points in existing link before and after
    }  

    // Reset everything
    doSpliceNode     = false;
    doSpliceIntoLink = null;
    doSplicePoint    = null;
    doSpliceIndex    = -1;  
  } 

  // Debug variable for inspecting later - not functional                    
  var debugLastLink = null;                  

  // Model, node and links for this application.  Based heavily on https://gojs.net/temp/splicing.html and adapted as needed.

  var myModel = $(go.GraphLinksModel);

  myDiagram.nodeTemplate = $(go.Node,
                             "Auto",
                             new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
                             $(go.Shape, "Circle", { width: 6, height: 6, strokeWidth: 0 }));



  myDiagram.linkTemplate =
        $(go.Link,
          {
            relinkableFrom: true, relinkableTo: true,
            reshapable: true, resegmentable: true,
          /*  selectionAdornmentTemplate:     @@ COMMENT OUT - NOT NEEDED
              $(go.Adornment,
                $(go.Shape, { isPanelMain: true, stroke: "dodgerblue", strokeWidth: 2 }),
                $(go.Shape, "PlusLine",
                  {
                    isActionable: true,  // so that click works in an Adornment
                    width: 16, height: 16, stroke: "green", strokeWidth: 4, background: "transparent",
                    segmentOffset: new go.Point(8, 0),
                    click: function(e, shape) {
                      alert(e);
                      var link = shape.part.adornedPart;
                      var p0 = link.getPoint(0);
                      var p1 = link.getPoint(link.pointsCount - 1);
                      var pt = new go.Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);

                      // @@TODO - instead, find the position where the mouse was clicked and place the node there
                      // ... need to work out which segment of polyline this was in so as to calculate new lines

                      // @@TODO - handle drag of node so that it just affects segments of lines immediately into it, rather than
                      // blatting over top of them

                      // @@TODO - what is object e and its components

                      spliceNewNodeIntoLink(link, pt);
                    },
                    cursor: "pointer"
                  })
              ), */
            toShortLength: 1
          },
          new go.Binding("points").makeTwoWay(),   // Use the points information from the linkDataArray initializer
          $(go.Shape, { strokeWidth: 2 })
        );

/*      function spliceNewNodeIntoLink(link, pt) {  // @@ original version no longer called
        link.diagram.commit(function(diag) {
          var tokey = link.toNode.key;
          // add a new node
          var newnodedata = { text: "on link", location: go.Point.stringify(pt) };
          diag.model.addNodeData(newnodedata);
          // and splice it in by changing the existing link to refer to the new node
          diag.model.setToKeyForLinkData(link.data, newnodedata.key);
          // and by adding a new link from the new node to the original "toNode"
          diag.model.addLinkData({ from: newnodedata.key, to: tokey });
          // optional: select the new node
          diag.select(diag.findNodeForData(newnodedata));
        }, "spliced in node on a link");
      }  */

      // Utility function used in one attempt to get this to work.  Initializers in nodeDataArray do it via an array of numbers,
      // so try that here.
      function toArray(nodelist)
      {
        var returnarray = new Array();
        var i;

        for (i = 0; i < nodelist.size; i++)
        {
          var pt = nodelist.elt(i);
          returnarray.push(pt.x);
          returnarray.push(pt.y);
        }

        return returnarray;
      }

      // Function to splice the new node into the link.  Parameters are
      // - link:  the link to splice into
      // - pt:    the point within the link to turn into a node
      // - index: index into existing polyline of that point
      function spliceNewNodeIntoLink2(link, pt, index) {
        link.diagram.commit(function(diag) {

          var oldlinkpointslist = link.points;
          var link1pointslist = new go.List(go.Point);
          var link2pointslist = new go.List(go.Point);
          var i;

          // Create new points list, from "from" node to new node to be added
          for (i = 0; i <= index; i++)
          {
            var point = new go.Point(link.getPoint(i).x, link.getPoint(i).y);
            link1pointslist.add(point);
          }

          console.log(link1pointslist);

          // .. and from new node to "to" node
          for (i = index; i < link.pointsCount; i++)
          {
            var point = new go.Point(link.getPoint(i).x, link.getPoint(i).y);
            link2pointslist.add(point);
          }

          console.log(link2pointslist);

          var tokey = link.toNode.key;
          // add a new node
          var newnodedata = { text: "on link", location: go.Point.stringify(pt) };
          diag.model.addNodeData(newnodedata);
          // and splice it in by changing the existing link to refer to the new node
          diag.model.setToKeyForLinkData(link.data, newnodedata.key);

          // ** NEW CODE
          // Code this was based on re-used the existing link, re-purposing it to go from "from" node 
          // to new node, so do the same, but give it a new points list.
          link.points = link1pointslist;    // @@TODO find out why this doesn't work    
                                            // ... actually it does, but something ditches the points later ...
                                            // so maybe I need to move this code to after the tool has really finished operating
                                            // by saving off the info and calling it in an override of the last tool method that
                                            // gets called (perhaps not - did this and it didn't work)


          debugLastLink = link; // @@TEMP         

          // and by adding a new link from the new node to the original "toNode"
          // ** UPDATED to include the second new point list
          diag.model.addLinkData({ from: newnodedata.key, to: tokey, points: toArray(link2pointslist) });

          // optional: select the new node
          diag.select(diag.findNodeForData(newnodedata));
        }, "spliced in node on a link");
      }

      // not called at present
      function maySpliceOutNode(node) {
        return node.findLinksInto().count === 1 &&
          node.findLinksOutOf().count === 1 &&
          node.findLinksInto().first() !== node.findLinksOutOf().first();
      }

      // not called at present
      function spliceNodeOutFromLinkChain(node) {
        if (maySpliceOutNode(node)) {
          node.diagram.commit(function(diag) {
            var inlink = node.findLinksInto().first();
            var outlink = node.findLinksOutOf().first();
            // reconnect the existing incoming link
            inlink.toNode = outlink.toNode;
            // remove the node and the outgoing link
            diag.removeParts([node, outlink], false);
            // optional: select the original link
            diag.select(inlink);
          }, "spliced out node from chain of links");
        }
      }


  // Initialize modeldi 
  myModel.nodeDataArray = [
         { key: "1" , "location": "30 30" },
         { key: "2" , "location": "130 30" },
         { key: "3" , "location": "30 130" }
  ];

  myModel.linkDataArray = [ 
         { from: "1", to: "2", "points": [  30,30,  70,20, 100,40, 130,30 ] },
         { from: "2", to: "3", "points": [ 130,30, 100,80,  70,90, 30,130 ] },
         { from: "3", to: "1", "points": [ 30,130, 20,100,  40,70,  30,30 ] }
  ];

  myDiagram.model = myModel;


</script>

</body>
</html> 

1 Ответ

0 голосов
/ 02 сентября 2018

Некоторые предложения:

  • Вызовите Link.findClosestSegment , чтобы найти сегмент, на котором пользователь щелкнул, чтобы вставить узел.
  • Не вставляйте новый узел в переопределение Tool.doStop , потому что это будет вызвано, даже если пользователь нажал клавишу Escape, чтобы отменить работу инструмента. Сделайте это в doMouseDown или doMouseUp , в зависимости от желаемого поведения. Но doStop - разумное время для очистки состояния инструмента.
  • Я думаю, что это должно сработать, если вы добавите новый узел и новую ссылку, правильно соедините их вместе, убедитесь, что узел находится в нужном месте, и только тогда явно установите Link.points . Двухстороннее связывание на Link.points сохранит точки для модели.

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

...