javascript - 寻找用于显示和编辑节点和边网络的 Javascript 库
问题描述
这是我的第一个问题,所以我希望我已经问清楚了。如果它已经得到回答并且我还没有设法找到正确的搜索内容,请提前道歉。
我正在寻找一个 Javascript 库,可用于创建用于交互式编辑节点和边网络的应用程序。
这里的“节点”是至少三个边相交的点,并且“边”连接两个节点,但可能是“折线”,因为它有多个线段而不是直线。画布中元素(节点和边段)的位置很重要(即不仅仅是哪些节点与哪些边相接)。
我需要能够使用鼠标和键盘执行的操作是:
- 创建一个新节点
- 删除节点
- 在节点之间创建新边
- 删除一条边 通过添加和删除线段来操作一条边(相当于沿边添加和删除点)
- 将边缘上的点更改为新节点 将节点更改回仅沿边缘的点
- 拖动一个点或一个节点,以便连接到它的线随之移动。
我还需要能够放大和缩小。
我需要能够访问描述网络的数据(即点的位置,以及线和点之间的关系),并对其进行序列化和反序列化。
总的来说,我希望有几百个节点和沿它们之间的边缘的几千个点。
我预计必须编写相当多的代码,但最好在合适的“自然”框架之上这样做。
我发现了一些我需要的东西:
- 谷歌我的地图允许编辑单个多边形
- fabric.js 有许多有用的操作功能,但(据我所知)不允许编辑沿折线的点
- GoJS 将可编辑的折线作为其示例的一部分
- D3js 看起来很有希望,并且实际上有数百个示例,但似乎没有一个与我正在尝试做的事情相近 - 特别是它似乎对可视化比操作更有用。
任何指针不胜感激。
谢谢。
解决方案
这是您的大部分应用程序: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.toJson和Model.fromJson来完成的:save
load
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
当然,您会希望向服务器发送和接收 JSON 格式的文本,而不是在 <textarea> 中的页面上显示它。
推荐阅读
- javascript - 提交使用 React 组件制作的登录表单
- sql - 每天注册/验证的员工人数
- c# - 在 MainPage.xaml.cs -xamarin-C# 中看不到对象
- php - 防止在 MySQL/PHP 中构建索引时创建索引
- amazon-web-services - DynamoDB 触发器 Lambda 函数问题:函数调用失败
- scala - 如何在数据框中创建一个新的列列,条件是当值在列表中时使用每个标签?
- objective-c - Swift print() 打印类型的名称而不是枚举关联值
- php - Laravel belongsToMany 检索多于一行
- c# - 仅使用 c# winform 输入表单后如何检查用户是否登录,没有 asp.netc#
- haskell - 应该如何使用forever函数?