首页 > 解决方案 > 寻找用于显示和编辑节点和边网络的 Javascript 库

问题描述

这是我的第一个问题,所以我希望我已经问清楚了。如果它已经得到回答并且我还没有设法找到正确的搜索内容,请提前道歉。

我正在寻找一个 Javascript 库,可用于创建用于交互式编辑节点和边网络的应用程序。

这里的“节点”是至少三个边相交的点,并且“边”连接两个节点,但可能是“折线”,因为它有多个线段而不是直线。画布中元素(节点和边段)的位置很重要(即不仅仅是哪些节点与哪些边相接)。

我需要能够使用鼠标和键盘执行的操作是:

我还需要能够放大和缩小。

我需要能够访问描述网络的数据(即点的位置,以及线和点之间的关系),并对其进行序列化和反序列化。

总的来说,我希望有几百个节点和沿它们之间的边缘的几千个点。

我预计必须编写相当多的代码,但最好在合适的“自然”框架之上这样做。

我发现了一些我需要的东西:

任何指针不胜感激。

谢谢。

标签: javascriptnetworking

解决方案


这是您的大部分应用程序:https ://gojs.net/extras/splicing.html 。完整的实现代码在页面上——只需执行“查看页面源代码”浏览器命令。但无论如何我都会在这里复制它:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Simple Splicing Sample for GoJS</title>
  <meta name="description" content="Splicing a new node into a selected link, and splicing out an existing node in the path of a link chain." />
  <!-- Copyright 1998-2018 by Northwoods Software Corporation. -->
  <meta charset="UTF-8">
  <script src="https://unpkg.com/gojs"></script>
  <script src="../assets/js/goSamples.js"></script>  <!-- this is only for the GoJS Samples framework -->
  <script id="code">
    function init() {
      if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this

      var $ = go.GraphObject.make;

      myDiagram =
        $(go.Diagram, "myDiagramDiv",
          {
            initialContentAlignment: go.Spot.Center,
            // create a node upon double-click in background:
            "clickCreatingTool.archetypeNodeData": { text: "a new node" },
            "undoManager.isEnabled": true
          });

      myDiagram.nodeTemplate =
        $(go.Node, "Auto",
          { locationSpot: go.Spot.Center },
          new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
          {
            selectionAdornmentTemplate:
              $(go.Adornment, "Spot",
                $(go.Panel, "Auto",
                  $(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 2 }),
                  $(go.Placeholder, { margin: 1 })
                ),
                $(go.Shape, "XLine",
                  {
                    isActionable: true,  // so that click works in an Adornment
                    width: 14, height: 14, stroke: "orange", strokeWidth: 4, background: "transparent",
                    alignment: go.Spot.TopRight,
                    click: function(e, shape) {
                      var node = shape.part.adornedPart;
                      spliceNodeOutFromLinkChain(node);
                    },
                    cursor: "pointer"
                  },
                  new go.Binding("visible", "", function(ad) {
                    var node = ad.adornedPart;
                    return maySpliceOutNode(node);
                  }).ofObject())
              )
          },
          $(go.Shape,
            {
              fill: "lightgray", strokeWidth: 0,
              portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer"
            },
            new go.Binding("fill", "color")),
          $(go.TextBlock, "some text",
            { margin: 8, editable: true },
            new go.Binding("text").makeTwoWay())
        );

      myDiagram.linkTemplate =
        $(go.Link,
          {
            relinkableFrom: true, relinkableTo: true,
            reshapable: true, resegmentable: true,
            selectionAdornmentTemplate:
              $(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) {
                      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);
                      spliceNewNodeIntoLink(link, pt);
                    },
                    cursor: "pointer"
                  })
              ),
            toShortLength: 1
          },
          new go.Binding("points").makeTwoWay(),
          $(go.Shape, { strokeWidth: 2 }),
          $(go.Shape, { toArrow: "OpenTriangle" })
        );

      function spliceNewNodeIntoLink(link, pt) {
        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");
      }

      function maySpliceOutNode(node) {
        return node.findLinksInto().count === 1 &&
          node.findLinksOutOf().count === 1 &&
          node.findLinksInto().first() !== node.findLinksOutOf().first();
      }

      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");
        }
      }

      load();
    }

    // Show the diagram's model in JSON format that the user may edit
    function save() {
      document.getElementById("mySavedModel").value = myDiagram.model.toJson();
      myDiagram.isModified = false;
    }
    function load() {
      myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
    }
  </script>
</head>
<body onload="init()">
  <div id="sample">
    <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
    <div>
      <button id="SaveButton" onclick="save()">Save</button>
      <button onclick="load()">Load</button>
      Diagram Model saved in JSON format:
    </div>
    <textarea id="mySavedModel" style="width:100%;height:300px">
{ "class": "GraphLinksModel",
  "nodeDataArray": [ 
{"key":1, "text":"Alpha", "color":"lightblue", "location":"-40 -10"},
{"key":2, "text":"Beta", "color":"orange", "location":"106 -58"},
{"key":3, "text":"Gamma", "color":"lightgreen", "location":"-8 105"},
{"key":4, "text":"Delta", "color":"pink", "location":"110 51"}
 ],
  "linkDataArray": [ 
{"from":1, "to":2, "points":[-15.5,-18.0,84.5,-50.9]},
{"from":1, "to":3, "points":[-35.7,5.1,-12.2,89.85]},
{"from":2, "to":2, "points":[116.7,-42.8,150.3,4.3,59.3,0.3,93.8,-42.8]},
{"from":3, "to":4, "points":[23,90.8,87,61.5]},
{"from":4, "to":1, "points":[87,41.6,-15.5,0.0]}
 ]}
  </textarea>
    <p>
      When a Link is selected it shows a green "+" button that when clicked creates a new Node and splices it into the link.
      It does that by reconnecting the existing link to the new node and creating a new link to go from the new node to the original
      destination of the selected link.
    </p>
    <p>
      When a Node is selected, if it has exactly one Link coming into it and exactly one Link going out of it,
      then it shows an orange "X" button.  When clicked it removes the node and the outgoing link and reconnects the incoming
      link to connect with the original destination of the outgoing link.
    </p>
    <p>
      All links are relinkable and resegmentable.
    </p>
  </div>
</body>
</html>

在后台双击创建一个新节点。这是通过以下方式实现的:

"clickCreatingTool.archetypeNodeData": { text: "a new node" },

或 Control-拖动或 Control-C/Control-V 复制选择。

从节点边缘内部拖动以绘制到节点的新链接。这是通过以下方式实现的:

portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer"

Node中的Shape上。

单击选定节点的文本以开始编辑它。这是通过以下方式实现的:

editable: true

Node中的TextBlock上。

每个链接,当被选中时,允许用户拖动一端以重新连接它,或者通过拖动中间手柄来添加或删除点。这是通过以下方式实现的:

      relinkableFrom: true, relinkableTo: true,
      reshapable: true, resegmentable: true

链接上。

每个链接还有一个自定义选择装饰,显示一个绿色的“+”按钮,创建一个新节点并将其拼接到链接中:

            selectionAdornmentTemplate:
              $(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) {
                      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);
                      spliceNewNodeIntoLink(link, pt);
                    },
                    cursor: "pointer"
                  })
              ),

每个节点只有一个链接进入它,一个链接离开它(不是同一个链接)有一个选择装饰,包括一个橙色的“X”按钮,允许该节点被拼接到它们的路径之外。这被实现为:

            selectionAdornmentTemplate:
              $(go.Adornment, "Spot",
                $(go.Panel, "Auto",
                  $(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 2 }),
                  $(go.Placeholder, { margin: 1 })
                ),
                $(go.Shape, "XLine",
                  {
                    isActionable: true,  // so that click works in an Adornment
                    width: 14, height: 14, stroke: "orange", strokeWidth: 4, background: "transparent",
                    alignment: go.Spot.TopRight,
                    click: function(e, shape) {
                      var node = shape.part.adornedPart;
                      spliceNodeOutFromLinkChain(node);
                    },
                    cursor: "pointer"
                  },
                  new go.Binding("visible", "", function(ad) {
                    var node = ad.adornedPart;
                    return maySpliceOutNode(node);
                  }).ofObject())
              )

选择一个节点并使用Delete键删除它不仅会删除该节点,还会删除与其连接的所有链接。

当然,完全支持撤消和重做。这是通过以下方式实现的:

"undoManager.isEnabled": true

一切都应该在触摸设备上运行,虽然我没有用这个示例尝试过。

而序列化和反序列化是通过在and函数中调用Model.toJsonModel.fromJson来完成的:saveload

document.getElementById("mySavedModel").value = myDiagram.model.toJson();

myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);

当然,您会希望向服务器发送和接收 JSON 格式的文本,而不是在 <textarea> 中的页面上显示它。


推荐阅读