目录
GoJS 和 GO 语言没有关系,它是一个用来创建交互式图表的 JavaScript 库。
基础概念
GraphObject 是所有图形是抽象基类,基本上 GoJS 中,万物皆 GraphObject。
Panel 有不同的类型,每个类型表示一种布局,通过不同的坐标系统排列。
注意,GoJS 中的 x 轴水平向右,y 轴垂直向下。
包括:
- Panel.Position 建立坐标系,通过指定坐标对绝对位置排序。
- Panel.Vertical & Panel.Horizontal 顾名思义,分别在垂直和水平的线性排列
- Panel.Auto 调整主元素的大小以适应 Panel 中的其他元素。
- Panel.Spot 通过 GraphObject.alignment 属性指定其他元素相对于主元素的位置。
- Panel.Table 以表格的方式排列元素,通过指定 row 和 col 以及相关信息指定元素位置。
- Panel.TableRow & Panel.TableColumn 只能在 Panel.Table 中使用,以将元素集合组织为表格中的行或列。
- Panel.Viewbox 用于自动调整 ** 单个元素 ** 的大小以适合面板的可用区域。
- Panel.Grid 仅用于绘制常规的线条图案。元素必须是用于描述重复行的形状。
- Panel.Link 仅供 Link 部件和 Link Adornments 使用。
- Panel.Graduated 用于沿主 Shape 元素绘制常规刻度线和文本。
Part 以及他的继承元素 Node 和 Link 等可以直接被加到 diagram 中(通过 Diagram.add
)。其他的元素要作为 Part 的子元素。
Node 可以被 Link 连接起来。每一个 Node 都有一个 key,用来唯一标识该 Node。link 有 from 和 to 属性,用来表示该 Link 连接了哪两个边。
Group 是一个 Node,可以包含一组 Nodes 和 Links。
Shape 表示一个几何图形。可以使用 GoJS 中定义好的一些图形,如 “Rectangle” 也可以自定义图形的形状。通过 fill 和 stroke 等属性决定图形的显示。
TextBlock 用来显示文本。字体属性和 CSS 相同 font-style font-variant font-weight font-size font-family
。也可以定义 stroke 等属性。
开始绘制图形
1. 通过代码构建图形
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="https://unpkg.com/gojs/release/go.js"></script> </head> <body> <div id="myDiagramDiv" style="border: solid 1px blue; width:400px; height:150px"></div> <script> // 创建图 var diagram = new go.Diagram("myDiagramDiv"); // 创建节点 节点中元素的排列方式是 Panel.Auto var node = new go.Node(go.Panel.Auto); // 创建图形 var shape = new go.Shape(); // 定义图形属性 shape.figure = "RoundedRectangle"; shape.fill = "lightblue"; // 将图形加到节点 node.add(shape); // 创建一个文本 var textblock = new go.TextBlock(); // 定义文本属性 textblock.text = "Hello!"; textblock.margin = 5; // 文本加到节点 node.add(textblock); // 将节点加到图 diagram.add(node); </script> </body> </html>
2. 通过 GraphObject.make 构建图形
GraphObject.make
是一个神奇的函数,详见 GraphObject.make,可以用来创建各种 GraphObject
元素,同时定义元素的属性。可以嵌套定义多个元素。
上面代码也可以写为下面的方式(因为 go.GraphObject.make
使用较多,所以官网建议命名为 $
方便使用,你当然也可以起其他的名字)
var diagram = new go.Diagram("myDiagramDiv"); var $ = go.GraphObject.make; diagram.add( $(go.Node, go.Panel.Auto, $(go.Shape, { figure: "RoundedRectangle", fill: "lightblue" }), $(go.TextBlock, { text: "Hello!", margin: 5 }) ));
好处一方面是代码量减少了,另一方面如果给元素赋值了错误的属性,make
函数会报出错误。
例如,如果 text 写错为 test,会在控制台看到报错:
Uncaught Error: Trying to set undefined property "test" on object: TextBlock("")
3. 使用 Model 和 Templates 创建图形
虽然第二种方式很简单,但是如果要加很多的 Node,而不同的 Node 之间结构相同,通过上面的方式,就需要 add 好多次。如果能定义一个 Node 的模板,然后通过指定参数来定义节点,就会方便很多。
这就类似定义一个类,以后只需要通过 new Something(p1, p2...) 来将创建一类对象。
举例,
var diagram = new go.Diagram("myDiagramDiv"); var $ = go.GraphObject.make; diagram.nodeTemplate = // 定义模板 $(go.Node, "Auto", $(go.Shape, { figure: "RoundedRectangle", fill: "white" }), $(go.TextBlock, { text: "hello!", margin: 5 }) ); var nodeDataArray = [ { key: "Alpha" }, { key: "Beta" } ]; var linkDataArray = [ { from: "Alpha", to: "Beta" } ]; diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
通过 go.GraphLinksModel
来创建一个图需要分别指定 Node 和 Link 的集合。定义好模板(diagram.nodeTemplate
)后,只需要为 diagram.model
传入 nodeDataArray,数组中每一个元素都在图中对应一个节点和 linkDataArray,对应每一条边。
如果 Node 的结构相同,只是某几个属性不同,也可以在模板中定义变量。变量通过 go.Binding 来定义。
var diagram = new go.Diagram("myDiagramDiv"); var $ = go.GraphObject.make; diagram.nodeTemplate = // 定义模板 $(go.Node, "Auto", $(go.Shape, { figure: "RoundedRectangle", fill: "white" }, // white 作为 fill 的默认属性 new go.Binding('fill', 'myFill') // 在 model 的 nodes 中通过 myFill 属性来指定 Node 的 fill 属性 ), $(go.TextBlock, { text: "hello!", margin: 5 }) ); var nodeDataArray = [ { key: "Alpha" }, // 默认白色节点 { key: "Beta", myFill: 'green' } // 绿色节点 ]; var linkDataArray = [ { from: "Alpha", to: "Beta" } ]; diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
获取图形数据
获取所有 Node、Link
通过 diagram.model.nodeDataArray
会获得所有节点的集合。
[{"key":"Alpha","__gohashid":408},{"key":"Beta","myFill":"green","__gohashid":409}]
会发现节点的属性除了用户赋值的以外,还有一个 __gohashid
,这个是 GoJS 维护的一个属性,不需要去管。
通过 diagram.model.linkDataArray
会获得所有边的集合。
通过 diagram.nodes
获得的是所有 Nodes 和 Groups 的迭代器 Iterator<Node>
。遍历迭代器:
for (var it = diagram.nodes; it.next(); ) { var node = it.value; console.log(node); }
diagram.links
同理。
注意,通过 diagram 获得的节点和 model 中的不同,这样获得的节点包含很多图中的其他信息,比如对于边,包含相连的节点信息等。
diagram.model.toJSON()
可以通过 diagram.model.toJSON() 获取图形相关数据,其中下划线开头的属性会被忽略,自然也会忽略 __gohashid
,所以起自己的属性名时,不要以下划线开头。
> diagram.model.toJSON()
< "{ "class": "GraphLinksModel", "nodeDataArray": [ {"key":"Alpha"}, {"key":"Beta", "myFill":"green"} ], "linkDataArray": [ {"from":"Alpha", "to":"Beta"} ] }"
使用 diagram.model = go.Model.fromJson(model);
可以使用 model 字符串加载图。
获取指定的 Node,Link
获取指定 key 的 Node: findNodeDataForKey
> diagram.model.findNodeDataForKey('Alpha') < {key: "Alpha", __gohashid: 408}
获取指定 key 的 Link findLinkDataForKey
,默认 GoJS 中的 Link 是不会被赋值一个唯一的 key 的,除非设置 linkKeyProperty
。
也可以通过 diagram 查找节点。
> diagram.findNodeForKey('Alpha') < V {__gohashid: 457, G: 4194307, lb: 1, bg: null, Ua: "", …} > diagram.findNodeForKey('Alpha').data === diagram.model.findNodeDataForKey('Alpha') < true
在 diagram 中查找边 diagram.findLinkForData
,不过查找的 data 要指定所有的属性,包括 __gohashid
。
> diagram.findLinkForData({from: "Alpha", to: "Beta",__gohashid: 411}) < S {__gohashid: 469, G: 4194307, lb: 1, bg: null, Ua: "", …} > link = diagram.findLinkForData(diagram.model.linkDataArray[0]) < S {__gohashid: 469, G: 4194307, lb: 1, bg: null, Ua: "", …} > link.data < {from: "Alpha", to: "Beta", __gohashid: 411} > link.fromNode < V {__gohashid: 457, G: 4194307, lb: 1, bg: null, Ua: "", …}
获取选择元素信息
diagram.selection
是一个只读属性,返回被选中元素(包括节点和边)集合。
获得选中个数 diagram.selection.size
获取第一个选中的元素,diagram.selection.first()
修改图形
修改节点属性
直接修改 model
中的属性值不会使图形发生改变的,要通过 setDataProperty
进行修改。
var data = myDiagram.model.findNodeDataForKey("Alpha"); // 直接修改不生效 if (data !== null) data.color = "red"; // 通过 diagram.model.setDataProperty 修改才可以 diagram.model.setDataProperty(data, 'myFill', 'purple');
选中元素
通过代码指定选中元素。
var node = diagram.findNodeForKey('Alpha'); // 会清除当前选中并选中 node diagram.select(node);
删除、添加节点和边
var node = { key: 'addNode', myFill: 'tomato' }; // 添加节点 diagram.model.addNodeData(node); // 添加边 Alpha -> addNode diagram.model.addLinkData({from:'Alpha', to: 'addNode'}); // 删除节点 diagram.model.removeNodeData(node);
事件
Model 和 Diagram 都会产生事件。所以可以通过 Model.addChangedListener
和 Diagram.addChangedListener
添加事件监听器。通过 removeChangedListener
删除事件监听。
let listener = function(changedEvent) { // do something... } // 添加监听器 diagram.addModelChangedListener(listener); // 移除监听器 diagram.removeModelChangedListener(listener);
通过 Model.setDataProperty
修改元素的属性,图形的结构不会发生变化 changedEvent.modelChange
会是一个空字符串。
diagram.addModelChangedListener(function(changedEvent) { // 修改的元素对象(修改后) 修改属性 修改之前的值 修改之后的值 if (changedEvent.modelChange === '') { console.log(changedEvent.object, changedEvent.propertyName, changedEvent.oldValue, changedEvent.newValue); } });
其他情况下 changedEvent.modelChange
会是下面的字符串之一:
- "nodeDataArray", 修改
Model.nodeDataArray
包括diagram.model.nodeDataArray = [...]
,Model.addNodeData
或Model.removeNodeData
- "nodeCategory", 调用
Model.setCategoryForNodeData
- "nodeGroupKey",
GraphLinksModel.setGroupKeyForNodeData
- "linkDataArray", 修改
GraphLinksModel.linkDataArray
,包括diagram.model.nodeDataArray = [...]
,GraphLinksModel.addLinkData
或GraphLinksModel.removeLinkData
- "linkFromKey", 调用
GraphLinksModel.setFromKeyForLinkData
- "linkToKey", 调用
GraphLinksModel.setToKeyForLinkData
- "linkFromPortId", 调用
GraphLinksModel.setFromPortIdForLinkData
- "linkToPortId", 调用
GraphLinksModel.setToPortIdForLinkData
- "linkLabelKeys", 调用
GraphLinksModel.setLabelKeysForLinkData
,GraphLinksModel.addLabelKeyForLinkData
或GraphLinksModel.removeLabelKeyForLinkData
- "linkCategory", 调用
GraphLinksModel.setCategoryForLinkData
- "nodeParentKey", 调用
TreeModel.setParentKeyForNodeData
- "parentLinkCategory", 调用
TreeModel.setParentLinkCategoryForNodeData
事务相关的事务, ChangedEvent.propertyName
会是以下的值。它们的 changedEvent.modelChange
也是一个空字符串。
- "StartingFirstTransaction"
- "StartedTransaction"
- "CommittingTransaction"
- "CommittedTransaction"
- "RolledBackTransaction"
- "StartingUndo"
- "FinishedUndo"
- "StartingRedo"
- "FinishedRedo"
可以通过 isTransactionFinished
判断事件是否是结束。
diagram.addModelChangedListener(function(evt) { if (evt.isTransactionFinished) saveModel(evt.model); });
Transactions
因为 GoJS 有 undo 和 redo 的功能,所以在撤销和恢复时,要保证单步操作一系列修改的完整性。
但是原则上,每次使用代码来对图形进行修改,都应该用事务包裹起来,即使不开启 undo/redo 功能。
开启 undo & redo 功能 Ctrl-Z(撤销)/ Ctrl-Y(恢复)
diagram.model.undoManager.isEnabled = true; // 或 diagram = $(go.Diagram, "myDiagramDiv", { "undoManager.isEnabled": true // undo & redo });
使用事物:
diagram.commit(function(d) { // .... }, "do a transaction");
Template Maps
上面的节点模板都只有一种,通过 diagram.nodeTemplate
来定义。当一个图形中希望有多种表现形式的节点时变不够用了。这时可以使用 myDiagram.nodeTemplateMap
来定义多种模板,并在使用时通过 category
指定。
var template1 = $(go.Node, "Auto", $(go.Shape, { figure: "RoundedRectangle", fill: "white" }), $(go.TextBlock, { text: "hello!", margin: 5 }, new go.Binding('text', 'text') )); var template2 = $(go.Node, "Auto", $(go.Shape, "RoundedRectangle", new go.Binding("fill", "color")), $(go.Panel, "Table", { defaultAlignment: go.Spot.Left }, $(go.TextBlock, { row: 0, column: 0, columnSpan: 2, font: "bold 12pt sans-serif" }, new go.Binding("text", "key")), $(go.TextBlock, { row: 1, column: 0 }, "Description:"), $(go.TextBlock, { row: 1, column: 1 }, new go.Binding("text", "desc")), $(go.TextBlock, { row: 2, column: 0 }, "Color:"), $(go.TextBlock, { row: 2, column: 1 }, new go.Binding("text", "color")) ) ); // 设置 category 为空字符串 则为不指定 category 时的默认图形 diagram.nodeTemplateMap.add('', template1); // category: detailtemplate diagram.nodeTemplateMap.add('detailtemplate', template2); /* 或者定义一个 go.Map 并赋值给 diagram.nodeTemplateMap var templmap = new go.Map(); templmap.add("", template1); templmap.add("detailtemplate", template2); diagram.nodeTemplateMap = templmap; */ var nodeDataArray = [ { key: "Alpha", text: 'Alpha' }, { key: "Beta", text: 'Beta', color: 'pink', desc: 'desc', category: 'detailtemplate' } ]; var linkDataArray = [ { from: "Alpha", to: "Beta" } ]; diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
Palette Diagrams
创建 go.Palette 来创建图形模板,可以通过拖拽的方式快速生成一系列相同的图案。