【问题标题】:How to insert node into polyline link in gojs, preserving positions of points in links either side of the new node如何在gojs中将节点插入折线链接,保留新节点两侧链接中点的位置
【发布时间】:2019-02-06 20:29:00
【问题描述】:

我在 StackOverflow (Seeking Javascript library for displaying and editing networks of nodes and edges) 上提出了一个问题,并被指向 gojs splice 示例。

这让我走了很长一段路,所以感谢您的回答,但我遇到了砖墙试图获得我想要的行为。

我正在尝试创建的应用是编辑地图上的边界:

  • 节点 = 三个或边界相交的地方
  • link = 两个节点之间的边界段。

因此,节点和链接是未标记的(节点只是小圆圈,链接只是折线)。

我已尝试适当调整拼接示例 (https://gojs.net/extras/splicing.html)。除了示例功能之外,我还需要以下主要功能:

  • 准确选择在现有节点之间的链接上放置新节点的位置
  • 保留折线的形状。

(现有示例将新节点与现有节点等距放置并使用直链。)

我试图创造的用户体验是这样的:首先,您选择链接,这样它就会得到它通常的装饰;然后在折线上的某个点上按住 shift 单击其中一个装饰,该点将成为新节点。

我试图通过使用https://gojs.net/latest/intro/extensions.html 中描述的扩展机制覆盖 LinkReshapingTool 的方法来做到这一点(而不是创建子类)。

但是,无论我尝试过什么,我都无法让折线保持不变。通过在我的代码运行后检查 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> 

【问题讨论】:

    标签: javascript polyline gojs


    【解决方案1】:

    一些建议:

    • 调用Link.findClosestSegment来查找用户点击插入节点的段。
    • 不要在 Tool.doStop 的覆盖中拼接新节点,因为即使用户按 Escape 键取消工具的操作也会调用该节点。根据您想要的行为,在 doMouseDowndoMouseUp 中执行此操作。但是 doStop 是清理工具状态的合理时间。
    • 我认为如果您添加新节点和新链接,将它们正确连接在一起,确保节点位于正确的位置,然后明确设置 Link.points,它应该可以工作。 Link.points 上的双向绑定会将点保存到模型中。

    您遇到的问题是,当您创建一个新节点时,需要时间来衡量、安排和定位。这些活动中的任何一项都会使所有连接链接的路由无效。显然,将链接与节点连接将使该链接的路由无效。所以你必须确保一切都以正确的顺序完成。

    【讨论】:

    • 谢谢。我曾尝试从 doMouseDown 和 doMouseUp 调用拼接代码,但均未成功,因此答案必须在您的第二个项目符号中。因此,请您详细说明执行此操作所需的 API 调用 - 特别是,将事物正确连接在一起意味着什么?再次感谢。
    • 我还是卡住了。我已经尝试更改代码,以便在一切完成后调用拼接操作(使用 setTimeout()),并且我已经更改了它,因此它显式删除了旧链接并添加了一个新节点和两个链接,但无论我尝试将两个新链接对象的 points 属性简单地忽略。对于它的价值,当我最初加载页面时,网络出现在节点之间的直线链接,然后突然改变为我编码的“扭结”。我想知道这是否相关。
    • 问题似乎可以通过在添加节点和添加链接之间让执行线程休眠 100 毫秒(参见 flaviocopes.com/javascript-sleep)来解决。我想某处存在竞争条件。稍微整理一下后,我会发布我更新的代码。
    猜你喜欢
    • 2016-03-30
    • 2021-02-25
    • 1970-01-01
    • 2020-02-18
    • 1970-01-01
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多