var $ = go.GraphObject.make; // 别名,方便使用
myDiagram =
$(go.Diagram, "myDiagramDiv", // 通过id指定画布绑定的div
{
"LinkDrawn": showLinkLabel, // 监听LinkDrawn事件,其回调函数是showLinkLabel
"LinkRelinked": showLinkLabel, // 同上,监听LinkRelinked事件
"undoManager.isEnabled": true // 允许 undo & redo
});
以上定义了图和图全局的属性、事件。最外层是一个go.Diagram对象,其结构为:每一个Diagram对象都由一些Layer对象组成,而一个Layer对象包含了数个Part对象(Part对象包括Node对象和Link对象等)。每一个Part对象由数个GraphObject(例如TextBlock,Shape,Panel对象)组成,Panel对象可以包含更多的GraphObject对象。更具体的说明:Class Diagram | GoJS
如下再给出一个Diagram对象的定义,读者品一品不同层级嵌套的参数表示的,分别是什么类:
var $ = go.GraphObject.make;
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 3 },
new go.Binding("text", "key"))
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: "Alpha", color: "lightblue" },
{ key: "Beta", color: "orange" },
{ key: "Gamma", color: "lightgreen" },
{ key: "Delta", color: "pink" }
],
[
{ from: "Alpha", to: "Beta" },
{ from: "Alpha", to: "Gamma" },
{ from: "Beta", to: "Beta" },
{ from: "Gamma", to: "Delta" },
{ from: "Delta", to: "Alpha" }
]);
接下来继续解析样例代码:
myDiagram.addDiagramListener("Modified", function(e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
这段代码作用是监听GoJS中的事件,改变页面的title,表示一下当前页面的图有没有改过,就是表达一下GoJS有自定义的事件系统。
接下来是一些辅助函数,帮助定义节点模版:
function nodeStyle() {
return [
// 将节点的location属性与数据对象的loc属性双向绑定
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
{
// location属性指定的是是节点中央的位置
locationSpot: go.Spot.Center
}
];
}
// 定义一个port
// "name"属性 被用作GraphObject.portId,
// "align"属性指定port在节点的哪个位置,
// "spot"属性控制连线如何连接到port并控制port是否沿着节点的边延伸,
// "output"和"input"参数控制用户是否能绘制出去/进来的连线
function makePort(name, align, spot, output, input) {
var horizontal = align.equals(go.Spot.Top) || align.equals(go.Spot.Bottom);
// port就是一个在节点边上延伸的小矩形
return $(go.Shape,
{
fill: "transparent", // 一开始透明
strokeWidth: 0, // 无边框
width: horizontal ? NaN : 8, // 若无水平延伸,宽度为8
height: !horizontal ? NaN : 8, // 若无垂直延伸,高度为8
alignment: align, // port在节点上的位置
stretch: (horizontal ? go.GraphObject.Horizontal : go.GraphObject.Vertical), // 拉伸方向
portId: name, // 声明这个对象是个 "port"
fromSpot: spot, // 声明连线可以连到哪里
fromLinkable: output, // 声明用户是否可以从这里连线
toSpot: spot, // 连线是否可以连接到这里
toLinkable: input, // 用户是否可以连线到这里
cursor: "pointer", // 鼠标移入会有变化,标识这儿可以连线
mouseEnter: function(e, port) { // 鼠标移入
if (!e.diagram.isReadOnly) port.fill = "rgba(255,0,255,0.5)";
},
mouseLeave: function(e, port) {
port.fill = "transparent";
}
});
}
function textStyle() {
return {
font: "bold 11pt Lato, Helvetica, Arial, sans-serif",
stroke: "#F8F8F8"
}
}
- go.Binding():给图中节点和连线绑定数据,go.Point.parse是定义的一个转换数据格式的函数,将字符串location解析为节点的loc属性,也就是定位属性可以用的数据格式。makeTwoWay()则实现了双向绑定。具体参考:Class Binding | GoJS
- 上面三个都是辅助函数,在定义节点模版时会用到
接下来定义了节点模版,可以定义多种节点:
myDiagram.nodeTemplateMap.add("", // 默认类别
$(go.Node, "Table", nodeStyle(),
// 主对象是panel,包括一个包裹了文本内容的矩形
$(go.Panel, "Auto",
$(go.Shape, "Rectangle",
{ fill: "#282c34", stroke: "#00A9C9", strokeWidth: 3.5 },
new go.Binding("figure", "figure")),
$(go.TextBlock, textStyle(),
{
margin: 8,
maxSize: new go.Size(160, NaN),
wrap: go.TextBlock.WrapFit,
editable: true
},
new go.Binding("text").makeTwoWay())
),
// 定义了四个port,分布在四条边
makePort("T", go.Spot.Top, go.Spot.TopSide, false, true),
makePort("L", go.Spot.Left, go.Spot.LeftSide, true, true),
makePort("R", go.Spot.Right, go.Spot.RightSide, true, true),
makePort("B", go.Spot.Bottom, go.Spot.BottomSide, true, false)
));
// 同上,定义了几种不同节点类型
myDiagram.nodeTemplateMap.add("Conditional",
$(go.Node, "Table", nodeStyle(),
$(go.Panel, "Auto",
$(go.Shape, "Diamond",
{ fill: "#282c34", stroke: "#00A9C9", strokeWidth: 3.5 },
new go.Binding("figure", "figure")),
$(go.TextBlock, textStyle(),
{
margin: 8,
maxSize: new go.Size(160, NaN),
wrap: go.TextBlock.WrapFit,
editable: true
},
new go.Binding("text").makeTwoWay())
),
makePort("T", go.Spot.Top, go.Spot.Top, false, true),
makePort("L", go.Spot.Left, go.Spot.Left, true, true),
makePort("R", go.Spot.Right, go.Spot.Right, true, true),
makePort("B", go.Spot.Bottom, go.Spot.Bottom, true, false)
));
myDiagram.nodeTemplateMap.add("Start",
$(go.Node, "Table", nodeStyle(),
$(go.Panel, "Spot",
$(go.Shape, "Circle",
{ desiredSize: new go.Size(70, 70), fill: "#282c34", stroke: "#09d3ac", strokeWidth: 3.5 }),
$(go.TextBlock, "Start", textStyle(),
new go.Binding("text"))
),
makePort("L", go.Spot.Left, go.Spot.Left, true, false),
makePort("R", go.Spot.Right, go.Spot.Right, true, false),
makePort("B", go.Spot.Bottom, go.Spot.Bottom, true, false)
));
myDiagram.nodeTemplateMap.add("End",
$(go.Node, "Table", nodeStyle(),
$(go.Panel, "Spot",
$(go.Shape, "Circle",
{ desiredSize: new go.Size(60, 60), fill: "#282c34", stroke: "#DC3C00", strokeWidth: 3.5 }),
$(go.TextBlock, "End", textStyle(),
new go.Binding("text"))
),
makePort("T", go.Spot.Top, go.Spot.Top, false, true),
makePort("L", go.Spot.Left, go.Spot.Left, false, true),
makePort("R", go.Spot.Right, go.Spot.Right, false, true)
));
// 在 ../extensions/Figures.js 中,自定义了一种图形:
go.Shape.defineFigureGenerator("File", function(shape, w, h) {
var geo = new go.Geometry();
var fig = new go.PathFigure(0, 0, true); // starting point
geo.add(fig);
fig.add(new go.PathSegment(go.PathSegment.Line, .75 * w, 0));
fig.add(new go.PathSegment(go.PathSegment.Line, w, .25 * h));
fig.add(new go.PathSegment(go.PathSegment.Line, w, h));
fig.add(new go.PathSegment(go.PathSegment.Line, 0, h).close());
var fig2 = new go.PathFigure(.75 * w, 0, false);
geo.add(fig2);
// The Fold
fig2.add(new go.PathSegment(go.PathSegment.Line, .75 * w, .25 * h));
fig2.add(new go.PathSegment(go.PathSegment.Line, w, .25 * h));
geo.spot1 = new go.Spot(0, .25);
geo.spot2 = go.Spot.BottomRight;
return geo;
});
myDiagram.nodeTemplateMap.add("Comment",
$(go.Node, "Auto", nodeStyle(),
$(go.Shape, "File",
{ fill: "#282c34", stroke: "#DEE0A3", strokeWidth: 3 }),
$(go.TextBlock, textStyle(),
{
margin: 8,
maxSize: new go.Size(200, NaN),
wrap: go.TextBlock.WrapFit,
textAlign: "center",
editable: true
},
new go.Binding("text").makeTwoWay())
));
其中,defineFigureGenerator()自定义了一种图形,具体可参考:图形形状 | GoJS
myDiagram.linkTemplate =
$(go.Link, // 整个link面板
{
routing: go.Link.AvoidsNodes,
curve: go.Link.JumpOver,
corner: 5, toShortLength: 4,
relinkableFrom: true,
relinkableTo: true,
reshapable: true,
resegmentable: true,
// 鼠标移入移出效果
mouseEnter: function(e, link) { link.findObject("HIGHLIGHT").stroke = "rgba(30,144,255,0.2)"; },
mouseLeave: function(e, link) { link.findObject("HIGHLIGHT").stroke = "transparent"; },
selectionAdorned: false
},
new go.Binding("points").makeTwoWay(),
$(go.Shape, // 高亮的Shape样式
{ isPanelMain: true, strokeWidth: 8, stroke: "transparent", name: "HIGHLIGHT" }),
$(go.Shape,
{ isPanelMain: true, stroke: "gray", strokeWidth: 2 },
new go.Binding("stroke", "isSelected", function(sel) { return sel ? "dodgerblue" : "gray"; }).ofObject()),
$(go.Shape, // 箭头
{ toArrow: "standard", strokeWidth: 0, fill: "gray" }),
$(go.Panel, "Auto", // 连线标签
{ visible: false, name: "LABEL", segmentIndex: 2, segmentFraction: 0.5 },
new go.Binding("visible", "visible").makeTwoWay(),
$(go.Shape, "RoundedRectangle", // 标签形状
{ fill: "#F8F8F8", strokeWidth: 0 }),
$(go.TextBlock, "Yes", // 标签
{
textAlign: "center",
font: "10pt helvetica, arial, sans-serif",
stroke: "#333333",
editable: true
},
new go.Binding("text").makeTwoWay())
)
);
以上定义了连线模版,实际上,在本图中有两种连线:带标签和不带标签的,从菱形组件出发的连线是带标签的。这两种线被统一定义在一个连线模版中,由visible属性控制是否显示标签。
接下来就是一些常规的辅助函数和输入输出了,不做赘述:
// 根据出发节点类型控制标签隐藏
function showLinkLabel(e) {
var label = e.subject.findObject("LABEL");
if (label !== null) label.visible = (e.subject.fromNode.data.category === "Conditional");
}
// 定义用户连线的排列方式
myDiagram.toolManager.linkingTool.temporaryLink.routing = go.Link.Orthogonal;
myDiagram.toolManager.relinkingTool.temporaryLink.routing = go.Link.Orthogonal;
load(); // 加载数据到图中
// 初始化左侧“调色板”,即一些备选图形
myPalette =
$(go.Palette, "myPaletteDiv", // 制定一个div的id
{
// 自定义简单动画
"animationManager.initialAnimationStyle": go.AnimationManager.None,
"InitialAnimationStarting": animateFadeDown,
nodeTemplateMap: myDiagram.nodeTemplateMap, // 共享在主图中定义好的集中节点模版
model: new go.GraphLinksModel([ // 指定备选图形
{ category: "Start", text: "Start" },
{ text: "Step" },
{ category: "Conditional", text: "???" },
{ category: "End", text: "End" },
{ category: "Comment", text: "Comment" }
])
});
// 自定义的简单动画效果
function animateFadeDown(e) {
var diagram = e.diagram;
var animation = new go.Animation();
animation.isViewportUnconstrained = true;
animation.easing = go.Animation.EaseOutExpo;
animation.duration = 900;
animation.add(diagram, 'position', diagram.position.copy().offset(0, 200), diagram.position);
animation.add(diagram, 'opacity', 0, 1);
animation.start();
}
}
// 图的数据就是一个json串,保存这个json串即可
function save() {
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
myDiagram.isModified = false;
}
function load() {
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
}