作为商业软件,GoJs很容易使用,文档也很完备,不过项目中没有时间系统地按照文档学习,总是希望快速入门使用,所以将项目中遇到的问题精简一下,希望对后来者有些帮助。

开始使用

这里先展示一个最简单的例子,说明GoJs的使用。

<!DOCTYPE html> <!-- HTML5 document type -->
<html> <head>
<script src="https://unpkg.com/gojs/release/go-debug.js"></script>
</head> <body>
<div id="myDiagramDiv" style="width:1200px; height:850px; background-color: #DAE4E4;"></div>
<script>
var $ = go.GraphObject.make; myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true
}); myDiagram.add(
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{
fill: $(go.Brush, "Linear",
{ 0.0: "Violet", 1.0: "Lavender" })
}),
$(go.TextBlock, "测试文本!",
{ margin: 5 }),
$("Button", {
alignment: go.Spot.Right,
alignmentFocus: go.Spot.Left,
click: function(e,obj){ console.log(e); console.log(obj);alert(obj);}
},
$(go.TextBlock, "+", // the Button content
{ font: "bold 8pt sans-serif" }))
)); </script>
</body> </html>

首先是引用GoJs库,可以有多个途径下载,可以通过npm,nuget等包管理工具,也可以直接下载。我们这里使用unpkg的引用。

然后就是使用 go.GraphObject.make创建图形和图形中的元素。这里先将 go.GraphObject.make简化定义为$,方便代码的编写与阅读,注意这不是必须的,也可以使用$$或者其它简化方式。结果如下:



这里的关键是go.GraphObject.make的使用,下面重点介绍这个函数。

使用go.GraphObject.make创建对象

一个图形可以看做由节点和连线组成,在GoJs中,图形元素是GraphObject,我们可以使用代码创建节点:

  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 = "你好!";
textblock.margin = 5;
node.add(textblock);
diagram.add(node);

这种办法属于常规的编程方法,容易理解,但是需要定义大量的中间变量,如果需要创建的元素很多,就会感觉有些冗余。因此GoJs使用创建函数go.GraphObject.make简化创建过程。上面的代码写为:

  var $ = go.GraphObject.make;
diagram.add(
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle", { fill: "lightblue" }),
$(go.TextBlock, "你好!", { margin: 5 })
));

可读性好多了。 go.GraphObject.make的第一个参数是需要创建的类型,通常是GraphObject的子类,后续的参数可以有多个,可以是以下类型:

  • 属性/值对的JS简单对象,说明被创建对象的属性。
  • GraphObject,添加到被创建对象中的子对象,比如,上面的代码中在Node中增加Shape和TextBlock。
  • 字符串,针对特定对象的属性,比如对于TextBlock,就是设置文本值
  • 其它可能的Js对象,针对创建对象的不同。

Binding

基本用法

Binding顾名思义是绑定,将模型的属性与GraphObject对象的属性进行绑定。比如,有如下模型:

{ key: 23, say: "你好!" }

我们需要将say属性绑定到文本对象的text属性,可以使用下面的代码:

 var $ = go.GraphObject.make;
myDiagram.nodeTemplate =
$(go.Node, "Auto",
. . .
$(go.TextBlock, new go.Binding("text", "say"))
)

转换函数

如果我们希望绑定的属性进行转换,可以使用转换函数,比如:

  new go.Binding("text", "say", function(v) { return "我说: " + v; })

单向绑定和双向绑定

单向绑定时只能是模型的属性改变GraphObject对象的属性,而双向绑定时,GraphObject对象的属性的改变可以改变模型的属性。双向绑定的写法是这样的:

new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify)

当loc转换为location时,使用go.Point.parse函数,当location转换为loc时,使用go.Point.stringify函数。

GraphObject

GraphObject是抽象类,不能直接创建,GraphObject具有下面的一些特性。

尺寸

GraphObject有关尺寸的属性如下:

  • desiredSize,minSize和maxSize。width和height会转换为desiredSize。
  • angle和scale
  • stretch

GraphObject在Panel中绘制完成后,会有如下的只读属性:

  • nuturalBounds:表示基本尺寸,不受转换的影响
  • measureBounds: 表示在包含它的Panel中的尺寸
  • actualBounds:表示在包含它的Panel中的实际尺寸

顶层GraphObject一定是Part

Part是GraphObject的子类,表示顶层元素。顶层元素一定是Part,包括Node,Linke,Group以及Adornment,下面的属性用于获取相关的GraphObject:

  • panel:获取包含这个GraphObject的Panel
  • part: 获取这个GraphObject所在的Part。
  • layer: 获取这个GraphObject所在的Layer。
  • diagram:获取所在的Diagram。

Model

模型中保存了图形显示的数据,描述基本实体、它们的属性以及之间的关系。模型中的数据要尽量简单,可以很容易地序列化为JSON或者XML格式。有两种模型TreeModel和GraphLinksModel,前者保存树状结构的数据。模型中key值用来标识对象,必须是唯一的,下面是Model的使用实例:

myDiagram.model = new go.TreeModel();
myDiagram.model.nodeDataArray = [{ "key": 0, "text": "Mind Map", "loc": "0 0" },
{ "key": 1, "parent": 0, "text": "Getting more time", "brush": "skyblue", "dir": "right", "loc": "77 -22" },
{ "key": 11, "parent": 1, "text": "Wake up early", "brush": "skyblue", "dir": "right", "loc": "200 -48" },
{ "key": 12, "parent": 1, "text": "Delegate", "brush": "skyblue", "dir": "right", "loc": "200 -22" }];

这是树形结构的数据。如果保存一般的图结构,需要使用GraphLinksModel。

自定义Node模板

个人认为方便的自定义模板是GoJs的强大功能之一,使用nodeTemplateMap可以很方便地定义各种类型的模板,只要在数据模型中指定模板的名称(使用category),就可以显示相应的图形。nodeTemplateMap的使用方法如下:

myDiagram.nodeTemplateMap.add("End",
part
);

这里,part就是显示的模板,比如,下面是一个part的定义,显示状态图的开始节点:

var partStart=    $(go.Node, "Spot", { desiredSize: new go.Size(75, 75) },
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Circle",
{
fill: "#52ce60", /* green */
stroke: null,
portId: "",
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock, "开始",
{
font: "bold 16pt helvetica, bold arial, sans-serif",
stroke: "whitesmoke"
})
);

对应的数据如下:

 {"id":-1, "loc":"155 -138", "category":"Start"}

数据中,category指定了模板类型,loc绑定到图元的位置,这里是双向绑定,也就是图元位置的变化,会改变数据模型中的数据。

如果只定义通用的模板,可以使用:

myDiagram.nodeTemplate=part;

这种情况下,没有指定category的数据都采用缺省模板显示。

自定义选中模板

当一个节点被选中时,我们希望使用不同的模板显示,比如,状态图中,一个被选中的节点中会出现添加链接的按钮,选中前:



选中后:



如果为使用缺省模板的节点定义选中模板,可以直接定义:

 myDiagram.nodeTemplate.selectionAdornmentTemplate = adornmentTemplate;

如果需要为使用nodeTemplateMap添加的自定义模板定义选中模板,可以使用如下方法:

var partStart=    $(go.Node, "Spot", { desiredSize: new go.Size(75, 75) },
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Circle",
{
fill: "#52ce60", /* green */
stroke: null,
portId: "",
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock, "开始",
{
font: "bold 16pt helvetica, bold arial, sans-serif",
stroke: "whitesmoke"
})
);
partStart.selectionAdornmentTemplate =$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle", roundedRectangleParams,
{ fill: null, stroke: "#7986cb", strokeWidth: 3 }),
$(go.Placeholder) // a Placeholder sizes itself to the selected Node
),
// the button to create a "next" node, at the top-right corner
$("Button",
{
alignment: go.Spot.TopRight,
click: addNodeAndLink // this function is defined below
},
$(go.Shape, "PlusLine", { width: 6, height: 6 })
)
); myDiagram.nodeTemplateMap.add("Start",
partStart
);

上面的代码中,需要先定义模板的part,然后增加选中模板,最后,使用nodeTemplateMap.add方法进行添加。

节点和连接的上下文菜单

对于节点和菜单的缺省模板,可以直接使用contextMenu定义上下文菜单,比如:

        myDiagram.nodeTemplate.contextMenu =
$("ContextMenu",
$("ContextMenuButton",
$(go.TextBlock, "显示属性"),
{ click: function (e, obj) {
var data = myDiagram.model.findNodeDataForKey(obj.part.key);
alert(data.complex.p1);
} })
);

对于使用nodeTemplateMap定义的自定义模板,需要在模板上先定义上下文菜单,然后再将模板添加到nodeTemplateMap中,下面的代码定义了状态图中结束节点的上下文菜单:

var partEnd=$(go.Node, "Spot", { desiredSize: new go.Size(75, 75) },
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Circle",
{
fill: "maroon",
stroke: null,
portId: "",
fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.Shape, "Circle", { fill: null, desiredSize: new go.Size(65, 65), strokeWidth: 2, stroke: "whitesmoke" }),
$(go.TextBlock, "结束",
{
font: "bold 16pt helvetica, bold arial, sans-serif",
stroke: "whitesmoke"
})
);
partEnd.contextMenu=
$("ContextMenu",
$("ContextMenuButton",
$(go.TextBlock, "显示属性"),
{ click: function (e, obj) {
var data = myDiagram.model.findNodeDataForKey(obj.part.key);
alert(data.complex.p1);
} })
);
myDiagram.nodeTemplateMap.add("End",
partEnd
);

连接的上下文菜单定义与节点相同,示例代码如下:

        myDiagram.linkTemplate.contextMenu =
$("ContextMenu",
$("ContextMenuButton",
$(go.TextBlock, "显示属性"),
{ click: function (e, obj) {
alert(obj.part.data.expression);
} })
);

节点和连接关联数据的访问

很多图形编辑器不容易使用的一个原因是编辑器的数据模型与业务的数据模型很难匹配。业务数据模型经常比较复杂,不仅仅是键值对能够完全表示的,很多情况下需要使用复杂的对象描述。GoJs在这一点上做得非常好,图形相关的数据模型可以和图形进行绑定,并且数据模型中可以包括复杂的数据对象,比如下面的节点中包括了复合的对象:

{"id":-1, "loc":"155 -138", "category":"Start","complex":{"p1":"自定义属性"}}

对象的读取也不复杂,在访问节点数据的示例代码如下:

        myDiagram.nodeTemplate.contextMenu =
$("ContextMenu",
$("ContextMenuButton",
$(go.TextBlock, "显示属性"),
{ click: function (e, obj) {
var data = myDiagram.model.findNodeDataForKey(obj.part.key);
alert(data.complex.p1);
} })
);

访问连接数据的示例代码如下:


myDiagram.linkTemplate.contextMenu =
$("ContextMenu",
$("ContextMenuButton",
$(go.TextBlock, "显示属性"),
{ click: function (e, obj) {
console.log(e);
console.log(obj.part);
alert(obj.part.data.expression);
} })
);

GoJs的命令

GoJs的命令,比如删除、重做、取消等等通过类CommandHandler实现。命令可以通过代码执行,也可以通过快捷键执行。下面的代码执行undo操作:

 myDiagram.commandHandler.undo();

下面是GoJs常用的命令和对应的快捷键:

  • Del 或者 Backspace 激活 CommandHandler.deleteSelection,删除
  • Ctrl-X 或者 Shift-Del 激活 CommandHandler.cutSelection,剪切
  • Ctrl-C 或者 Ctrl-Insert 激活 CommandHandler.copySelection,拷贝
  • Ctrl-V 或者 Shift-Insert 激活 CommandHandler.pasteSelection,粘贴
  • Ctrl-A 激活 CommandHandler.selectAll,全选
  • Ctrl-Z 或者 Alt-Backspace 激活 CommandHandler.undo,取消
  • Ctrl-Y 或者 Alt-Shift-Backspace 激活 CommandHandler.redo,重做
  • 空格键 激活 CommandHandler.scrollToPart,滚动到部件
    • (减号)激活CommandHandler.decreaseZoom,缩小zoom
    • (加号)激活 CommandHandler.increaseZoom,放大zoom
  • Ctrl-0 激活 CommandHandler.resetZoom ,重置zoom
  • Shift-Z 激活 CommandHandler.zoomToFit,设置zoom到适合图形大小
  • Ctrl-G 激活 CommandHandler.groupSelection , 组合
  • Ctrl-Shift-G 激活 CommandHandler.ungroupSelection,取消组合
  • F2 激活 CommandHandler.editTextBlock,编辑
  • Esc 激活 CommandHandler.stopCommand,取消命令

GoJs 上下文菜单

前面介绍了节点和链接的上下文菜单,在图形的背景上也可以设置上下文菜单,设置方法很简单,直接在背景的contextMenu上设置就可以了,示例代码如下:

    myDiagram.contextMenu =
GO("ContextMenu",
GO("ContextMenuButton",
GO(go.TextBlock, "撤销"),
{
click: function (e, obj) {
myDiagram.commandHandler.undo();
}
})
);

可以对ContextMenuButton设置尺寸,比如,增加宽和高的属性:

GO("ContextMenuButton",
GO(go.TextBlock, "撤销"),
{
width: 160, height: 120,
click: function (e, obj) {
myDiagram.commandHandler.undo();
}
}),

也可以为ContextMenu设置属性,添加完菜单按钮后面,增加属性设置:

myDiagram.contextMenu =
GO("ContextMenu", GO("ContextMenuButton",
GO(go.TextBlock, "撤销"),
{
click: function (e, obj) {
myDiagram.commandHandler.undo();
}
}),
GO("ContextMenuButton",
GO(go.TextBlock, "重做"),
{
click: function (e, obj) {
myDiagram.commandHandler.redo();
}
}),
{width:200}
);

GoJs 生成图片并回传服务器

GoJs提供在客户端生成流程图的blob数据,然后通过浏览器进行下载,这种方式不需要服务端的支持,示例代码如下:

myDiagram.makeImageData({ returnType: "blob", scale: 3, detail: 0.9, callback: saveBlobToServer});

这里生成的blob数据会由自定义的回调函数处理,在回调函数中,可以编写通过浏览器的下载代码,或者将流程图数据回传到服务器的代码。这里,我们希望将图片回传服务器进行保存:

            function saveBlobToServer(blob) {
var fd = new FormData();
fd.append('fname', 'myBlobFile.png');
fd.append('data', blob);
$.ajax({
type: 'POST',
url: root + 'Upload/SaveImage',
data: fd,
processData: false,
contentType: false
}).done(function (data) {
if (!data) alert("保存完成");
else alert(data);
});
}

服务器端使用Asp.Net Core:

       [HttpPost]
public IActionResult SaveImage()
{
var files = Request.Form.Files;
var fn = Request.Form["fname"];
if (files.Count > 0)
{
var pic = files[0];
var fileName = fn;// Path.Combine(rootpath, pic.FileName);
if (System.IO.File.Exists(fileName)) System.IO.File.Delete(fileName);
using (var stream = new FileStream(fileName, FileMode.CreateNew))
{
pic.CopyTo(stream);
}
} return Content("");
}

GoJS 使用笔记的更多相关文章

  1. GoJS学习笔记

    GoJS 和 GO 语言没有关系,它是一个用来创建交互式图表的 JavaScript 库. 基础概念 GraphObject 是所有图形是抽象基类,基本上 GoJS 中,万物皆 GraphObject ...

  2. GoJS学习笔记 (转)

    目录 基础概念 开始绘制图形 1. 通过代码构建图形 2. 通过 GraphObject.make 构建图形 3. 使用 Model 和 Templates 创建图形 获取图形数据 获取所有 Node ...

  3. GoJS 教程新手入门(资源整理,解决方案)

    以下几个是我在百度.谷歌 上能找到的比较全的GoJs的一些东西,希望对各位有所帮助! 如有外网网站不能访问请自行FQ GoJS官网 第一个推荐的是GoJS的一个类似于社区的问题讨论区,这里面初学者的一 ...

  4. 【Javascript】js图形编辑器库介绍

    10个JavaScript库绘制自己的图表 jopen 2015-04-06 18:18:38 • 发布 摘要:10个JavaScript库绘制自己的图表 JointJS JointJS is a J ...

  5. git-简单流程(学习笔记)

    这是阅读廖雪峰的官方网站的笔记,用于自己以后回看 1.进入项目文件夹 初始化一个Git仓库,使用git init命令. 添加文件到Git仓库,分两步: 第一步,使用命令git add <file ...

  6. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  7. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  8. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  9. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

随机推荐

  1. Python多环境管理神器(pyenv)

    前面我们已经介绍了,python中两种最基础的虚拟环境管理工具,venv和virtualenv,其中virtualenv可以和virtualenvwrapper配合使用.详情请参考:https://w ...

  2. 《剑指offer》面试题03. 数组中重复的数字

    问题描述 找出数组中重复的数字. 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次.请找出数组中 ...

  3. [USB波形分析] 全速USB波形数据分析(三)

    前面的两篇文章介绍和分析了USB的一些基本知识,结合前面的介绍,今天用实例介绍USB的枚举过程. 1 | 概况 硬件基于EK-TMC123GXL开发板,软件是TI提供的USB批量传输的简单例子,在PC ...

  4. 【C++】STL算法

    STL算法 标签:c++ 目录 STL算法 一.不变序列算法 1.熟悉的min(), max() 2.找最值还自己动手么?不了不了 3.熟悉的find()和新学会的count() 二.变值算法 1.f ...

  5. Docker 与 K8S学习笔记(十八)—— Pod的使用

    Pod 是一组紧密关联的容器集合,它们共享IPC.Network和UTS namespace,是 Kubernetes 调度的基本单元.Pod 的设计理念是支持多个容器在一个 Pod 中共享网络和文件 ...

  6. 记一次redis 基于spring实现类对同一个KEY序列化内容不同导致一次事故

    我们的场景是这样的 我们对一个key:比如list.point.card:1 @Resourceprivate RedisTemplate<String, Long> redisTempl ...

  7. java继承子父类构造函数-子类的实例化过程

    1 /* 2 * 子父类中的构造函数的特点. 3 * 在子类构造对象时,发现,访问子类构造函数时,父类也运行了. 4 * 为什么呢? 5 * 原因是:在子类的构造函数中第一行有一个默认的隐式语句.su ...

  8. 什么是VPC

    1 什么是私有网络(VPC) 私有网络是一块可用户自定义的网络空间,您可以在私有网络内部署云主机.负载均衡.数据库.Nosql快存储等云服务资源.您可自由划分网段.制定路由策略.私有网络可以配置公网网 ...

  9. 数据库查询语句遇到:Unknown column 'XXXX' in 'where clause'解决方法

    数据库查询语句遇到:Unknown colunm 'XXX' in 'where clause'解决方法 根本原因:可能是sql语句所用到的数据类型错误(int与String)弄错- 我的情况: 在网 ...

  10. Python 使用 Windows10 桌面通知

    前言 Win10 没有提供简单命令行方式来触发桌面通知,所以使用 Python 来写通知脚本. 一番搜索,找到 win10toast .但这开源仓库已无人维护,通过 github fork 的关系图, ...