javascript - 使用 GoJS 创建层次结构
问题描述
我正在寻找使用 GoJs 创建具有 3 级层次结构(如下所示)的树形图。
要求:
- 子节点可以是串行的(图中的子 1 和子 3)或并行的(图中的子 1 和子 2)。
- 在任何时候,一个新节点都可以作为子节点添加到级别 1 或级别 2 的任何节点,无论是串行的还是并行的。
- 缩进用于描述层次结构级别的变化。[在创建的图像中沿级别添加缩进]
- 不应允许用户自由移动任何节点。我计划在未来提供限制性的拖放功能。
问题:如何从任何父节点创建第二个分支(如上图)?
为此,我尝试使用 GoJS 的树形布局,但无法使其按预期工作。这是我使用的布局:
layout: $(go.TreeLayout, {alignment: go.TreeLayout.AlignmentStart, angle: 90});
请推荐一个 GoJs 布局以及所需的配置,它可以解决上述所有要求。
解决方案
这是一个完整的示例:
<!DOCTYPE html>
<html>
<head>
<title>Dual Tree Layout</title>
<!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
<script src="https://unpkg.com/gojs"></script>
<script id="code">
// decide whether a Node is an "assistant" node or a regular one
function isAssistant(n) { return n && n.data && !!n.data.assistant; }
// this custom TreeLayout was adapted from the Org Chart Assistants sample
function DualTreeLayout() {
go.TreeLayout.call(this);
}
go.Diagram.inherit(DualTreeLayout, go.TreeLayout);
DualTreeLayout.prototype.makeNetwork = function(coll) {
var net = go.TreeLayout.prototype.makeNetwork.call(this, coll);
// copy the collection of TreeVertexes, because we will modify the network
var vertexcoll = new go.Set(/*go.TreeVertex*/);
vertexcoll.addAll(net.vertexes);
for (var it = vertexcoll.iterator; it.next();) {
var parent = it.value;
// count the number of assistants
var acount = 0;
var ait = parent.destinationVertexes;
while (ait.next()) {
if (isAssistant(ait.value.node)) acount++;
}
// if a vertex has some number of children that should be assistants
if (acount > 0) {
parent._hasDualChildren = true;
// remember the assistant edges and the regular child edges
var asstedges = new go.Set(/*go.TreeEdge*/);
var childedges = new go.Set(/*go.TreeEdge*/);
var eit = parent.destinationEdges;
while (eit.next()) {
var e = eit.value;
if (isAssistant(e.toVertex.node)) {
asstedges.add(e);
} else {
childedges.add(e);
}
}
// first remove all edges from PARENT
eit = asstedges.iterator;
while (eit.next()) { parent.deleteDestinationEdge(eit.value); }
eit = childedges.iterator;
while (eit.next()) { parent.deleteDestinationEdge(eit.value); }
// create substitute vertex to be new parent of all regular children
var subst = net.createVertex();
subst._forRegulars = true;
net.addVertex(subst);
// reparent regular children to the new substitute vertex
eit = childedges.iterator;
while (eit.next()) {
eit.value.fromVertex = subst;
subst.addDestinationEdge(eit.value);
}
net.linkVertexes(parent, subst, null);
// create substitute vertex to be new parent of all assistant children
var subst2 = net.createVertex();
subst2._forAssistants = true;
net.addVertex(subst2);
// reparent all assistant children to the new substitute vertex
eit = asstedges.iterator;
while (eit.next()) {
eit.value.fromVertex = subst2;
subst2.addDestinationEdge(eit.value);
}
net.linkVertexes(parent, subst2, null);
}
}
return net;
};
DualTreeLayout.prototype.assignTreeVertexValues = function(v) {
if (v._hasDualChildren) {
v.nodeIndent = 0;
v.layerSpacing = 0;
v.layerSpacingParentOverlap = 1;
v.breadthLimit = 0;
} else if (v._forAssistants) {
// this is the substitute parent for the assistant(s)
v.width = v.parent.width;
v.height = v.parent.height;
v.breadthLimit = 0;
} else if (v._forRegulars) {
// found the substitute parent for non-assistant children
v.width = v.parent.width;
v.height = v.parent.height;
v.breadthLimit = 180;
} else {
v.breadthLimit = 180;
}
}; // end of DualTreeLayout
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
layout:
$(DualTreeLayout,
{
angle: 90, isRouting: true,
setsPortSpot: false, setsChildPortSpot: false,
alignment: go.TreeLayout.AlignmentStart
}),
"undoManager.isEnabled": true
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 100, height: 60 },
$(go.Shape, "RoundedRectangle", { fill: "white", portId: "" },
new go.Binding("fill", "color")),
$(go.TextBlock, { textAlign: "center" },
new go.Binding("text")),
$("TreeExpanderButton", { alignment: go.Spot.Bottom }),
$("Button", $(go.TextBlock, "S"),
{ alignment: new go.Spot(0.5, 1, -20, 0) },
{ click: (e, button) => { e.diagram.model.commit(m => m.addNodeData({ text: "S", parent: button.part.key })) } }),
$("Button", $(go.TextBlock, "P"),
{ alignment: new go.Spot(0.5, 1, 20, 0) },
{ click: (e, button) => { e.diagram.model.commit(m => m.addNodeData({ text: "P", parent: button.part.key, assistant: true })) } }),
);
myDiagram.linkTemplate =
$(go.Link,
{
layerName: "Background", routing: go.Link.Orthogonal, corner: 10,
toSpot: new go.Spot(0.001, 0, 20, 0)
},
new go.Binding("fromSpot", "toNode", function(n) {
return (n && n.data.assistant) ? new go.Spot(0.5, 1, 20, 0) : new go.Spot(0.5, 1, -20, 0);
}).ofObject(),
$(go.Shape),
$(go.Shape, { toArrow: "Standard" })
);
myDiagram.model = new go.TreeModel(
[
{ key: 1, text: "Root" },
{ key: 2, text: "Child 1 serial", parent: 1 },
{ key: 3, text: "Child 2 parallel", parent: 1, assistant: true },
{ key: 4, text: "Child 3 serial", parent: 1 },
{ key: 5, text: "Grand 1 1 parallel", parent: 2, assistant: true },
{ key: 6, text: "Grand 1 2 parallel", parent: 2, assistant: true },
{ key: 7, text: "Grand 2 1 serial", parent: 3 },
{ key: 8, text: "Grand 2 2 serial", parent: 3 }
]);
}
</script>
</head>
<body onload="init()">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
</body>
</html>
我在每个节点上添加了“S”和“P”按钮,这样用户就可以轻松地向任何节点添加“串行”或“并行”子节点,这样您就可以看到布局是如何工作的。
推荐阅读
- coldfusion - 使用 ListValueCount 的正确方法
- reactjs - 将我的 create react app typescript 转换为 ssr 以提供动态元标记
- javascript - JS 计算器,结果未按预期显示?
- jsf - JSF 的 SelectItems 的 itemLabel 即使使用不正确的 var ref 也能工作
- blazor-webassembly - 找不到 BlazorWebAssembly JS 文件。这通常表示打包错误
- sql - BigQuery - 连接谓词中的表不受支持的子查询
- python - 如何在 C++ proejct 中嵌入二进制化的 python 包
- postgresql - 错误:(repmgr)服务器处于待机模式,无法注册为主服务器
- reactjs - Storybook:如何测试基于路线的动画?
- frontend - 试图分叉 pancakeswap 前端彩票页面。获取多路通话卡盘失败