流程图控件GoJS教程:扩展GoJS
GoJS是一款功能强大,快速且轻量级的流程图控件,可帮助你在JavaScript 和HTML5 Canvas程序中创建流程图,且极大地简化您的JavaScript / Canvas 程序。
GoJS可以通过多种方式进行扩展。更改标准行为的最常见方法是在GraphObject,Diagram,CommandHandler,Tool或Layout上设置属性。但是,当不存在此类属性时,您可能需要覆盖CommandHandler,Tool,Layout,Link或Node的方法。API参考中记录了可以覆盖的方法。
- 不要修改GoJS类的原型。
- 仅使用API中记录的属性和方法。
请注意,扩展类的API可能会随任何版本(甚至是点发行)而更改。如果打算在生产环境中使用扩展名,则应将代码复制到自己的源目录中。
除了我们的示例外,GoJS还提供了一个扩展库,展示了自定义工具和布局的创建。这些类和示例已被翻译成TypeScript,可从此处获得../extensionsTS/。这些扩展类是UMD模块。他们使用../release/go.js图书馆。扩展类还可以作为ES6模块在以下位置获得../extensionsJSM/:这些使用../release/go-module.js库。我们建议您将所需的文件复制到项目中,以便可以调整它们对“ go.js”库的引用方式,并可以将它们包含在自己的构建和打包过程中。
命令处理程序
通过覆盖CommandHandler,您可以更改默认功能并创建自己的键绑定。有关更多信息,请参见“ 命令 ” 简介页。但是,下面显示的用于覆盖“工具和布局”上的方法的技术也适用于CommandHandler。
工具和布局
GoJS使用许多工具和布局在节点和链接上进行操作,所有这些工具和布局都是Tool和Layout类的子类。有关工具的更多信息,请参见工具的简介页面;有关布局的更多信息,请参见布局的简介页面。
可以修改工具,也可以将其替换或添加到Diagram.toolManager中。所有工具必须继承自Tool类或继承自Tool的类。
我们的一些示例(例如Pipes示例)包含自定义工具的示例。扩展库中提供了更多自定义工具示例。这些类和示例的TypeScript版本可以在../extensionsTS/中找到。这些自定义工具类也可以作为ES6模块在../extensionsJSM/中使用。布局可以被修改,或者它们可以通过设置使用Diagram.layout或Group.layout。所有布局都必须从Layout类或从Layout继承的类继承。
我们的一些示例(例如“ 解析树”示例)包含自定义布局的示例。扩展库中提供了更多自定义布局示例。这些类的TypeScript版本可以在../extensionsTS/中找到。这些自定义布局类也可以在../extensionsJSM/中的ES6模块中使用。
在不定义子类的情况下覆盖方法
通常,我们可以避免完整地继承Tool的子类,而仅重写单个方法。当我们想对方法的行为做些小改动时,这是很常见的。这里我们展示了如何覆盖ClickSelectingTool ,standardMouseSelect方法通过修改特定图表的工具实例。
也可以用这种方式覆盖Layout方法,但很少这样做——布局几乎总是被子类化。对于Group.layout值的布局,无法完成此操作,因为这些布局可能会被复制且无法共享。因为我们不是在创造一个新的(子)类,所以我们直接在图的ClickSelectingTool上设置这个方法,这个工具是通过它的工具管理器引用的。以这种方式覆盖方法的典型脚手架如下:
var tool = diagram.toolManager.clickSelectingTool; tool.standardMouseSelect = function() { //可能在 // ... 之前做其他事情 //注意在此类函数中使用“ this”! //如果您想要正常的行为,请调用基本功能。 //注意对原型的引用, //以及对“ call”的调用,将其传递为“ this”。 go.ClickSelectingTool.prototype.standardMouseSelect.call(tool); //也许在 // 之后做其他事情... }作为一个具体的例子,我们将覆盖工具standardMouseSelect只选择宽度和高度小于50图单元的节点和链接。这意味着我们必须使用图表找到要选择的对象。findPartAt,检查它的边界,如果边界太大就退出。否则,我们调用基本功能来进行选择。
diagram.nodeTemplate = $(go.Node,“ Auto”, $(go.Shape,“ Rectangle”, { fill:“ white” }, 新建 go.Binding(“ fill”,“ color”)), $(go.TextBlock, { margin:5 }, 新的 go.Binding(“ text”,“ key”)) ); var tool = diagram.toolManager.clickSelectingTool; tool.standardMouseSelect = function() { var diagram = tool.diagram; var e = diagram.lastInput; //以选择包含组如果Part.canSelect()为假 VAR curobj = diagram.findPartAt(e.documentPoint,false); 如果(curobj!== null){ var bounds = curobj.actualBounds; //如果边界大于50宽度或高度, 则结束选择过程,如果(bounds.width> 50 || bounds.height> 50){ //如果这是没有修饰符的左键单击,则我们希望执行相同的操作仿佛 // //点击“图表”背景,因此 如果((e.left &&!e.control &&!e.shift){ diagram.clearSelection(); } //然后返回,这样就不会调用基本功能 return; } } //否则,调用基本功能 go.ClickSelectingTool.prototype.standardMouseSelect.call(tool); } diagram.model = new go.Model([ { 键:“ Alpha”,颜色:“浅蓝色” }, { 键:“ Epsilon”,颜色:“蓟” }, { 键:“ Psi”,颜色:“ lightcoral” }, { 键:“伽玛”,颜色:“ 浅绿色” } ]);
运行此代码,我们看到“ Epsilon”和“ Gamma”节点都不可选,因为它们的宽度都大于50。请注意,此自定义工具不会更改其他可能选择的工具的行为,例如DraggingTool或该DragSelectingTool。
通过子类化布局覆盖方法
可以将布局子类化以创建自定义布局,这些自定义布局继承现有布局的属性和方法。GoJS的传统JavaScript中的子类化需要几个关键步骤:
- 创建一个新类(函数),然后调用基类构造函数。
- 使用新类和基类调用Diagram.inherit。
- 修改派生类的原型以添加新功能。
function CascadeLayout() { //注意直接调用构造函数,不使用“原型” go.Layout.call(this); //在“ this”上添加新属性 } go.Diagram.inherit(CascadeLayout,go.Layout); // //注意在原型 CascadeLayout.prototype.doLayout = function(coll)上设置方法 { //布局逻辑在这里。 //您可以可靠地使用“ this”来引用 //调用此方法的布局实例。 }请注意,如果您使用现代JavaScript(ECMAScript 6)或TypeScript进行编写,则可以使用更新的语法来定义类:
导出 类 CascadeLayout 扩展 go。布局 { //在此声明和初始化新的数据属性(字段) constuctor(){ super(); //其他初始化可以在这里完成 } //(可选)在此处定义属性获取器和设置器 //覆盖或定义方法 公共doLayout(coll){ //布局逻辑在这里。 } }布局通常需要用作布局选项的其他属性。要向中添加“ offset”属性CascadeLayout,我们将使用下划线成员为私有的约定,并在构造函数中设置默认值:
function CascadeLayout() { go.Layout.call(this); 此 ._offset = 新 go.Size(12,12); }然后,我们使用Object.defineProperty“公开”获取器和设置器。Getter和Setters使我们能够进行类型检查并具有副作用。此设置器确保偏移值是一个go.Size对象,并且仅在更改值后才使布局无效。
Object .defineProperty(CascadeLayout.prototype,“ offset”,{ get:function() { 返回 此 ._offset; }, get:function(val) { if(!(val instanceof go.Size)){ 抛出 新 错误(“ CascadeLayout.offset的新值必须是Size,而不是:” + val); } if(!this ._offset.equals(val)){ this ._offset = val; 这个 .invalidateLayout(); } } });如果您使用ECMAScript 6或TypeScript进行编写,则可以定义属性getter和setter。
get offset(){ 返回 此 ._offset; } set offset(val){ if(!(val instanceof go.Size)){ 抛出 新 错误(“ CascadeLayout.offset的新值必须为Size,而不是:” + val); } if(!this ._offset.equals(val)){ this ._offset = val; 这个 .invalidateLayout(); } }如果可以将布局用作Group.layout的值,则需要确保可以正确复制在Group模板中设置的实例。
CascadeLayout.prototype.cloneProtected = function(复制) { go.Layout.prototype.cloneProtected.call(this,copy); copy._offset = 此 ._offset; }最后,我们将定义一个Layout.doLayout,确保查看文档并适应所有可能的输入,因为doLayout具有一个参数,可以是Diagram,Group或Iterable集合。
综上,我们可以看到级联布局的实际效果:
/ ** * @构造函数 * @扩展布局 * @类 *此布局按offset属性指定的级联排列节点 * / function CascadeLayout() { go.Layout.call(this); 此 ._offset = new go.Size(12,12); } go.Diagram.inherit(CascadeLayout,go.Layout); CascadeLayout.prototype.cloneProtected = function(复制) { go.Layout.prototype.cloneProtected.call(this,copy); copy._offset = 此 ._offset; } Object .defineProperty(CascadeLayout.prototype,“ offset”,{ get:function() { 返回 此 ._offset; }, get:function(val) { if(!(val instanceof go.Size)){ 抛出 新 错误(“ CascadeLayout.offset的新值必须是Size,而不是:” + val); } if(!this ._offset.equals(val)){ this ._offset = val; this .invalidateLayout(); } } }); / ** *此方法放置所有节点,并忽略所有链接。 * @this {CascadeLayout} * @param {Diagram | Group | Iterable}将零件集合收集到布局中。 * / CascadeLayout.prototype.doLayout = function(coll) { //获取要布置的节点和链接 var parts = this .collectParts(coll); //从布局的起点开始布局,这是从Layout继承的属性 var x = this .arrangementOrigin.x; var y = 此 .arrangementOrigin.y; var offset = this .offset; var it = parts.iterator; while(it.next()){ var node = it.value; if((node instanceof go.Node))continue ; //忽略链接 node.move(new go.Point(x,y)); x + = offset.width; y + = offset.height; } } // CascadeLayout的结尾 //常规图设置: diagram.layout = new CascadeLayout(); diagram.nodeTemplate = $(go.Node,“ Auto”, $(go.Shape,“ Rectangle”, { fill:“ white” }, new go.Binding(“ fill”,“ color”)), $(go.TextBlock, { margin:5 }, new go.Binding(“ text”,“ key”)) ); diagram.model = new go.Model([ { 键:“ Alpha”,颜色:“浅蓝色” }, { 键:“贝塔”,颜色:“蓟” }, { 键:“ Delta”,颜色:“ lightcoral” }, { 键:“伽玛”,颜色:“ 浅绿色” } ]);