如何在gojs中将节点插入折线链接中,并保留新节点两侧的链接中的点位置

时间:2018-08-31 19:30:44

标签: javascript polyline gojs

我问了一个关于StackOverflow(Seeking Javascript library for displaying and editing networks of nodes and edges)的问题,并指出了gojs拼接示例。

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

我要创建的应用程序是在地图上编辑边框:

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

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

我已尝试适当修改接头样本(https://gojs.net/temp/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> 

1 个答案:

答案 0 :(得分:0)

一些建议:

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

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