GoJS 使用笔记
作为商业软件,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 使用笔记的更多相关文章
- GoJS学习笔记
		
GoJS 和 GO 语言没有关系,它是一个用来创建交互式图表的 JavaScript 库. 基础概念 GraphObject 是所有图形是抽象基类,基本上 GoJS 中,万物皆 GraphObject ...
 - GoJS学习笔记 (转)
		
目录 基础概念 开始绘制图形 1. 通过代码构建图形 2. 通过 GraphObject.make 构建图形 3. 使用 Model 和 Templates 创建图形 获取图形数据 获取所有 Node ...
 - GoJS 教程新手入门(资源整理,解决方案)
		
以下几个是我在百度.谷歌 上能找到的比较全的GoJs的一些东西,希望对各位有所帮助! 如有外网网站不能访问请自行FQ GoJS官网 第一个推荐的是GoJS的一个类似于社区的问题讨论区,这里面初学者的一 ...
 - 【Javascript】js图形编辑器库介绍
		
10个JavaScript库绘制自己的图表 jopen 2015-04-06 18:18:38 • 发布 摘要:10个JavaScript库绘制自己的图表 JointJS JointJS is a J ...
 - git-简单流程(学习笔记)
		
这是阅读廖雪峰的官方网站的笔记,用于自己以后回看 1.进入项目文件夹 初始化一个Git仓库,使用git init命令. 添加文件到Git仓库,分两步: 第一步,使用命令git add <file ...
 - js学习笔记:webpack基础入门(一)
		
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
 - SQL Server技术内幕笔记合集
		
SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...
 - PHP-自定义模板-学习笔记
		
1. 开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2. 整体架构图 ...
 - PHP-会员登录与注册例子解析-学习笔记
		
1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...
 
随机推荐
- spring源码之refresh第二篇
			
大家好,我是程序员田同学 上篇文章对spring核心启动方法refresh做了整体的解读,但是只是泛泛而谈,接下来会出一系统文章对每个方法的源码进行深刻解读. 第一篇文章见 spring源码之方法概览 ...
 - 《Flink SQL任务自动生成与提交》后续:修改flink源码实现kafka connector BatchMode
			
目录 问题 思路 kafka参数问题 支持batchmode的问题 参数提交至kafkasource的问题 group by支持问题 实现 编译 测试 因为在一篇博文上看到介绍"汽车之家介绍 ...
 - Pytorch之Spatial-Shift-Operation的5种实现策略
			
Pytorch之Spatial-Shift-Operation的5种实现策略 本文已授权极市平台, 并首发于极市平台公众号. 未经允许不得二次转载. 原始文档(可能会进一步更新): https://w ...
 - 网络编程-HTTP cookie
			
目录 1.cookie的起源 2.cookie是什么? 3.创建cookie 3.1.响应首部 Set-Cookie 3.2.请求首部 Cookie 3.3.Document.cookie 4.HTT ...
 - 解决nRF Connect for PC无法连接网络的问题(非FQ)
			
各位小伙伴是不是也遇到过国内不能正常使用nRF Connect的问题,现在教大家一个十分有效的办法. 1.找到nrf connect的脚本配置文件"apps.json",默认在&q ...
 - 【计算机理论】CSAPP ch2
			
信息存储 十六进制表示法 (略) 字数据大小 大多数计算机使用8bit的块(字节)作为最小的可寻址的内存单元 字长指明了指针数据的标称大小(?) 64位系统和32位系统向后兼容 C语言中有些数据类型的 ...
 - @WebServlet注解(Servlet注解)
			
@WebServlet 注解的属性 @WebServlet 用于将一个类声明为 Servlet,该注解会在部署时被容器处理,容器根据其具体的属性配置将相应的类部署为 Servlet.该注解具有下表给出 ...
 - Java如何对一个对象进行深拷贝
			
Java如何对一个对象进行深拷贝? Posted by Wudashan on October 14, 2018 深拷贝实现代码:https://github.com/wudashan/java-de ...
 - 微信小程序入门教程之四:API 使用
			
今天是这个系列教程的最后一篇. 上一篇教程介绍了,小程序页面如何使用 JavaScript 脚本.有了脚本以后,就可以调用微信提供的各种能力(即微信 API),从而做出千变万化的页面.本篇就介绍怎么使 ...
 - linu查看系统用户与显示命令行提示符格式信息
			
目录 一:查看系统用户whoami 二:显示命令行提示符格式信息变量 一:查看系统用户whoami whoami : 当前窗口登录的用户 who : 当前用户登录系统的终端 作用: 显示当前用户登录了 ...