GoJS 是什么
GoJS是一个功能丰富的js库,用于在浏览器上实现自定义交互式图表和复杂的可视化图表。 GoJS通过可自定义的模板和布局构建复杂节点,链接和组,绘制js图表。
GoJS为用户交互提供了许多高级功能,如拖放,复制和粘贴,文本编辑,工具提示,上下文菜单,自动布局,数据绑定和模型,事务状态和撤销管理,事件处理程序,命令以及用于自定义操作的可扩展工具系统等等。
为什么使用GoJS
为了更直观地表达信息,我们常常需要用图形来展示数据以及逻辑关系,例如最常见的架构图、ER图、流程图、BPMN图等等,都是用来解决实际应用所遇到的问题。前端可使用canvas,svg,html+css技术来绘制图形。主流的前端库有mxGraph,Joint等这篇文章介绍的比较清楚了。
我们对用户交互有很高的需求,并且有深入定制模板的需求,因此我们选择GoJS
如何使用GoJS
官网上介绍:GoJS支持图形模板 以及 图形对象属性与模型数据的数据绑定。
GoJS supports graphical templates and data-binding of graphical object properties to model data.
我们通过下面两个部分,来理解这句话。
GoJS 的概念
图表(Diagram)
GoJS图表即最后看到的可视化视图,它是由这些部分构成的:一个或多个可能有连接关系的、可能成组的节点。所有这些节点和链路聚集在相同或不同的层中,并呈现出一定的布局(开发者预定好的或GoJS自动布局)。
模型(Modal)
每个图表都有一个数据模型,用于保存和解释开发者程序的数据。
模型描述了节点之间的连接关系和组成员关系。图表自动为模型 Model.nodeDataArray 中的每个数据项创建一个节点或组, 为模型 GraphLinksModel.linkDataArray 中的每个数据项创建一个链接。而且,我们可以为每个数据对象添加所需的任何属性。
模板(Template)
模板声明了每个节点或链路的外观、位置和行为。
面板(Panel)
每个模板由GoJS中的面板Panel构成,面板本身作为一个图形对象GraphObject,保存其他图形对象作为它的元素,同时,面板需要负责图形对象的尺寸、位置。
每个面板建立自己的坐标系,面板中的元素按顺序绘制,从而确定了这些元素的z坐标。
面板有很多种类,比如 Panel.Position,Panel.Auto,Panel.Vertical,Panel.Horizontal 等等。
所有部件(这里指构成面板的图形对象,比如 Shapes、Pictures、TextBlocls)都有默认模板。
图形对象属性与模型数据属性的数据绑定使得数据的每个节点或链接都是唯一的。
下面这张图会更直观地解释上面的概念:
HTML
js外链地址
//引入的内容js如有需求请私聊我
//gojs
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta content="a1z51.23600852" name="spm-id" />
<title>
demo
</title>
<script src="../../lib/zepto.min.js"></script>
<script src="../../lib/vue.min.js"></script>
<script src="../../lib/veryless.js"></script>
<script src="../../lib/go.js"></script>
<style>
html,
body {
position: relative;
height: 100%;
}
body {
background: #eee;
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
font-size: 1rem;
color: #000;
margin: 0;
padding: 0;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="app">
<div style="width: 100%; display: flex; justify-content: space-between">
<div id="myPaletteDiv" style="width: 100px; margin-right: 2px; background-color: whitesmoke; border: 1px solid black; position: relative; -webkit-tap-highlight-color: rgba(255, 255, 255, 0.2); cursor: auto;"></div>
<div id="myDiagramDiv" style="flex-grow: 1; height: 500px; border: 1px solid black; position: relative; -webkit-tap-highlight-color: rgba(255, 255, 255, 0); cursor: auto;"></div>
<div v-if="showSet" style="width: 2rem;height: 500px;">00000</div>
</div>
<button class="test_01" @click="getData">获取数据</button>
<button class="test_02" @click="updata">修改数据</button>
</div>
<script>
//vue
$(document).ready(() => {
var app = new Vue({
el: "#app",
data() {
return {
showSet:true,
}
},
methods: {
init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make;
myDiagram = $(go.Diagram, "myDiagramDiv", {
initialContentAlignment: go.Spot.Left,
initialAutoScale: go.Diagram.UniformToFill,
layout: $(go.LayeredDigraphLayout, {
direction: 0
}),
"undoManager.isEnabled": true,
ChangedSelection: this.ChangedSelection
});
this.myDiagram = myDiagram;
// 添加监听节点生成事件
myDiagram.addDiagramListener("externalobjectsdropped", function(e) {
console.log("添加监听节点生成事件");
e.subject.each(function(n) {
console.log(n.data.key);
});
});
myDiagram.addDiagramListener("BackgroundSingleClicked", e=> {
console.log("添加背景点击事件");
this.showSet = false;
});
function makePort(name, leftside) {
var port = $(go.Shape, "Rectangle", {
fill: "gray",
stroke: null,
desiredSize: new go.Size(8, 8),
portId: name, // declare this object to be a "port"
toMaxLinks: 1, // don't allow more than one link into a port
cursor: "pointer" // show a different cursor to indicate potential link point
});
var lab = $(go.TextBlock, name, {
font: "4pt sans-serif"
});
var panel = $(go.Panel, "Horizontal", { margin: new go.Margin(2, 0) });
// set up the port/panel based on which side of the node it will be on
if (leftside) {
port.toSpot = go.Spot.Left;
port.toLinkable = true;
lab.margin = new go.Margin(1, 0, 0, 1);
panel.alignment = go.Spot.TopLeft;
panel.add(port);
panel.add(lab);
} else {
port.fromSpot = go.Spot.Right;
port.fromLinkable = true;
lab.margin = new go.Margin(1, 1, 0, 0);
panel.alignment = go.Spot.TopRight;
panel.add(lab);
panel.add(port);
}
return panel;
}
function makeTemplate(typename, inports, outports) {
var node = $(go.Node, "Spot",
$(go.Panel, "Auto", {
width: 80,
height: 80
},
$(go.Shape, "Rectangle", {
fill: "white",
stroke: null,
strokeWidth: 0,
spot1: go.Spot.TopLeft,
spot2: go.Spot.BottomRight
}, new go.Binding("fill", "color")),
$(go.Panel, "Table",
$(go.TextBlock, {
text: "",
row: 0,
margin: 5,
maxSize: new go.Size(80, NaN),
stroke: "#000",
font: "8pt sans-serif"
}, new go.Binding("text", "name")),
$(go.Picture, {
row: 1,
source: "https://morefun-active.oss-cn-beijing.aliyuncs.com/morefun2021/gojs/test/1.png",
width: 30,
height: 30
}, new go.Binding("source", "icon")),
$(go.TextBlock, {
text: '',
row: 2,
margin: 5,
maxSize: new go.Size(80, NaN),
stroke: "#000",
font: "8pt sans-serif"
}, new go.Binding("text", "number")),
)
),
$(go.Panel, "Vertical", {
alignment: go.Spot.Left,
alignmentFocus: new go.Spot(0, 0.5, 8, 0)
},
inports),
$(go.Panel, "Vertical", {
alignment: go.Spot.Right,
alignmentFocus: new go.Spot(1, 0.5, -8, 0)
},
outports)
);
myDiagram.nodeTemplateMap.set(typename, node);
}
makeTemplate("Gender", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
makeTemplate("Age", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
makeTemplate("Cross", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
makeTemplate("And", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
makeTemplate("Difference", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
myDiagram.linkTemplate = $(go.Link, {
routing: go.Link.Orthogonal,
corner: 5,
relinkableFrom: true,
relinkableTo: true
},
$(go.Shape, { stroke: "gray", strokeWidth: 2 }),
$(go.Shape, { stroke: "gray", fill: "gray", toArrow: "Standard" })
);
this.load();
this.init2222();
},
init2222() {
let that = this;
var $ = go.GraphObject.make;
myPalette = $(go.Palette, "myPaletteDiv", {
maxSelectionCount: 1,
});
myPalette.nodeTemplate = $(go.Node, "Spot",
$(go.Panel, "Auto", {
width: 80,
height: 80
},
$(go.Shape, "Rectangle", {
fill: "white",
stroke: null,
strokeWidth: 0,
spot1: go.Spot.TopLeft,
spot2: go.Spot.BottomRight
}, new go.Binding("fill", "color")),
$(go.Panel, "Table",
$(go.TextBlock, {
text: "",
row: 0,
margin: 5,
maxSize: new go.Size(80, NaN),
stroke: "#000",
font: "8pt "
}, new go.Binding("text", "name")),
$(go.Picture, {
row: 1,
source: "https://morefun-active.oss-cn-beijing.aliyuncs.com/morefun2021/gojs/test/1.png",
width: 30,
height: 30
}, new go.Binding("source", "icon")),
$(go.TextBlock, {
text: '',
row: 2,
margin: 5,
maxSize: new go.Size(80, NaN),
stroke: "#000",
font: "8pt"
}, new go.Binding("text", "number")),
)
)
// $(go.Shape, { width: 14, height: 14, fill: "white" }, new go.Binding("fill", "color")),
// $(go.TextBlock, new go.Binding("text", "name"))
);
myPalette.model.nodeDataArray = [
{ type: "Gender", color: "#0ee73c", name: "性别" },
{ type: "Age", color: "#d5e70e", name: "年龄" },
{ type: "Cross", color: "#d5e70e", name: "交" },
{ type: "And", color: "#d5e70e", name: "并" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
];
},
load() {
let a = {
"class": "go.GraphLinksModel",
"nodeCategoryProperty": "type",
"linkFromPortIdProperty": "frompid",
"linkToPortIdProperty": "topid",
"nodeDataArray": [
{ type: "Gender", color: "#0ee73c", name: "00000",number:334 }
],
"linkDataArray": []
}
this.myDiagram.model = go.Model.fromJson(a);
},
ChangedSelection(e) {
let that = this;
console.log(this.showSet);
var data = null;
e.diagram.selection.each(nodeOrLink => {
if (nodeOrLink instanceof go.Node) { //选择节点
data = nodeOrLink.data;
console.log(data)
console.log('-----');
that.showSet = true;
}
});
},
getData() {
console.log("----------------块");
console.log(this.myDiagram.model.nodeDataArray);
console.log("----------------线");
console.log(this.myDiagram.model.linkDataArray);
console.log(this.myDiagram.model.toJson())
},
updata() {
// 获取数据
let data = JSON.parse(this.myDiagram.model.toJson());
var node = this.myDiagram.model.findNodeDataForKey('-1');
console.log(node);
this.myDiagram.model.setDataProperty(node, 'name', "女");
}
},
mounted() {
this.init();
// this.init2222();
},
created() {}
})
})
</script>
<style>
#myDiagramDiv {
background-color: #F8F8F8;
border: 1px solid #aaa;
}
.test_01 {
position: absolute;
width: 1rem;
height: 0.4rem;
bottom: 0rem;
left: 0rem;
text-align: center;
font-size: 0.15rem;
line-height: 0.4rem;
background-color: rgba(255, 0, 0, .8);
}
.test_02 {
position: absolute;
width: 1rem;
height: 0.4rem;
bottom: 0rem;
left: 2rem;
text-align: center;
font-size: 0.15rem;
background-color: rgba(0, 255, 0, .8);
}
</style>
</body>
</html>
vue中的使用 其实代码都一样就是挪到了框架 关于引入的话直接干到入口html文件下 建议最好外链引入不要放在本地资源下
<template>
<div class="page_go">
<el-card>
<div class="btnBox">
<el-button type="primary" @click="getData" class="btn_go">获取数据</el-button>
<el-button type="primary" @click="updateData" class="btn_go_2">修改数据</el-button>
</div>
<div style="width: 100%; display: flex; justify-content: space-between">
<div id="myPaletteDiv" style="width: 100px; margin-right: 2px; background-color: whitesmoke; border: 1px solid black; position: relative; -webkit-tap-highlight-color: rgba(255, 255, 255, 0.2); cursor: auto;"></div>
<div id="myDiagramDiv" style="flex-grow: 1; height: 500px; border: 1px solid black; position: relative; -webkit-tap-highlight-color: rgba(255, 255, 255, 0); cursor: auto;"></div>
<!-- <div v-if="showSet" style="width: 2rem;height: 500px;">00000</div> -->
</div>
</el-card>
</div>
</template>
<script>
export default {
mounted() {
this.init();
},
computed: {},
data() {
return {
showSet:false,
};
},
methods: {
//初始化
init(){
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var gomake = go.GraphObject.make;
var myself = this;
//初始化流程图构建gojs对象
myself.myDiagram = gomake(go.Diagram, "myDiagramDiv", {
initialContentAlignment: go.Spot.Left, //居左显示
initialAutoScale: go.Diagram.UniformToFill,
layout: gomake(go.LayeredDigraphLayout, {
direction: 0
}),
"undoManager.isEnabled": true, //支持 Ctrl-Z 和 Ctrl-Y 操作
ChangedSelection: this.ChangedSelection
});
// 添加监听节点生成事件
myself.myDiagram.addDiagramListener("externalobjectsdropped", function(e) {
console.log("添加监听节点生成事件");
e.subject.each(function(n) {
console.log(n.data.key);
});
});
myself.myDiagram.addDiagramListener("BackgroundSingleClicked", e=> {
console.log("添加背景点击事件");
this.showSet = false;
});
//构建节点
//可以设置描边大小及其颜色
// { fill: "#44CCFF",stroke: 'green',strokeWidth:2,angle:15 }
function makePort(name, leftside) {
var port = gomake(go.Shape, "Rectangle", {
fill: "gray", //填充为灰色
stroke: null, //不描边
desiredSize: new go.Size(8, 8), //大小
portId: name, // declare this object to be a "port"
toMaxLinks: 1, // don't allow more than one link into a port
cursor: "pointer" // show a different cursor to indicate potential link point
});
var lab = gomake(go.TextBlock, name, {
font: "4pt sans-serif"
});
//创建输入点和输出点
var panel = gomake(go.Panel, "Horizontal", { margin: new go.Margin(2, 0) });
if (leftside) {
port.toSpot = go.Spot.Left;
port.toLinkable = true;
lab.margin = new go.Margin(1, 0, 0, 1);
panel.alignment = go.Spot.TopLeft;
panel.add(port);
panel.add(lab);
} else {
port.fromSpot = go.Spot.Right;
port.fromLinkable = true;
lab.margin = new go.Margin(1, 1, 0, 0);
panel.alignment = go.Spot.TopRight;
panel.add(lab);
panel.add(port);
}
return panel;
}
//定义节点模板
function makeTemplate(typename, inports, outports) {
var node = gomake(go.Node, "Spot",
gomake(go.Panel, "Auto", {
width: 80,
height: 80
},
gomake(go.Shape, "Rectangle", {
fill: "white",
stroke: null,
strokeWidth: 0,
spot1: go.Spot.TopLeft,
spot2: go.Spot.BottomRight
}, new go.Binding("fill", "color")),
gomake(go.Panel, "Table",
gomake(go.TextBlock, {
text: "",
row: 0,
margin: 5,
maxSize: new go.Size(80, NaN),
stroke: "#000",
font: "8pt sans-serif"
}, new go.Binding("text", "name")),
gomake(go.Picture, {
row: 1,
source: "https://morefun-active.oss-cn-beijing.aliyuncs.com/morefun2021/gojs/test/1.png",
width: 30,
height: 30
}, new go.Binding("source", "icon")),
gomake(go.TextBlock, {
text: '',
row: 2,
margin: 5,
maxSize: new go.Size(80, NaN),
stroke: "#000",
font: "8pt sans-serif"
}, new go.Binding("text", "number")),
)
),
gomake(go.Panel, "Vertical", {
alignment: go.Spot.Left,
alignmentFocus: new go.Spot(0, 0.5, 8, 0)
},
inports),
gomake(go.Panel, "Vertical", {
alignment: go.Spot.Right,
alignmentFocus: new go.Spot(1, 0.5, -8, 0)
},
outports)
);
myself.myDiagram.nodeTemplateMap.set(typename, node);
}
makeTemplate("Gender", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
makeTemplate("Age", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
makeTemplate("Cross", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
makeTemplate("And", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
makeTemplate("Difference", [makePort("输入L", true), makePort("输入R", true)], [makePort("输出O", false)]);
//页面数据初始化
let a = {
"class": "go.GraphLinksModel",
"nodeCategoryProperty": "type",
"linkFromPortIdProperty": "frompid",
"linkToPortIdProperty": "topid",
"nodeDataArray": [],
"linkDataArray": []
}
this.myDiagram.model = go.Model.fromJson(a);
this.init2();
},
init2(){
var gomake = go.GraphObject.make;
this.myPalette = gomake(go.Palette, "myPaletteDiv", {
maxSelectionCount: 1,
});
this.myPalette.nodeTemplate = gomake(go.Node, "Spot",
gomake(go.Panel, "Auto", {
width: 80,
height: 80
},
gomake(go.Shape, "Rectangle", {
fill: "white",
stroke: null,
strokeWidth: 0,
spot1: go.Spot.TopLeft,
spot2: go.Spot.BottomRight
}, new go.Binding("fill", "color")),
gomake(go.Panel, "Table",
gomake(go.TextBlock, {
text: "",
row: 0,
margin: 5,
maxSize: new go.Size(80, NaN),
stroke: "#000",
font: "8pt "
}, new go.Binding("text", "name")),
gomake(go.Picture, {
row: 1,
source: "https://morefun-active.oss-cn-beijing.aliyuncs.com/morefun2021/gojs/test/1.png",
width: 30,
height: 30
}, new go.Binding("source", "icon")),
gomake(go.TextBlock, {
text: '',
row: 2,
margin: 5,
maxSize: new go.Size(80, NaN),
stroke: "#000",
font: "8pt"
}, new go.Binding("text", "number")),
)
)
);
this.myPalette.model.nodeDataArray = [
{ type: "Gender", color: "#0ee73c", name: "性别" },
{ type: "Age", color: "#d5e70e", name: "年龄" },
{ type: "Cross", color: "#d5e70e", name: "交" },
{ type: "And", color: "#d5e70e", name: "并" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
{ type: "Difference", color: "#d5e70e", name: "差" },
];
},
ChangedSelection(e) {
let that = this;
console.log(this.showSet,'???????');
this.showSet = false;
var data = null;
e.diagram.selection.each(nodeOrLink => {
if (nodeOrLink instanceof go.Node) { //选择节点
data = nodeOrLink.data;
console.log(data)
console.log('-----');
that.showSet = true;
}
});
},
//获取数据
getData(){
console.log("----------------块");
console.log(this.myDiagram.model.nodeDataArray);
console.log("----------------线");
console.log(this.myDiagram.model.linkDataArray);
console.log(this.myDiagram.model.toJson(),"xxxxxxxxxxxxxx");
},
updateData(){
let data = JSON.parse(this.myDiagram.model.toJson());
var node = this.myDiagram.model.findNodeDataForKey('-1');
console.log(node);
this.myDiagram.model.setDataProperty(node, 'name', "女");
},
load() {
},
}
};
</script>
<style type="text/css" scoped>
.page_go{
position: relative;
overflow: hidden;
}
.btn_go{
position: absolute;
top:25px;
left: 20px;
}
.btn_go_2{
position: absolute;
top:25px;
left: 180px;
}
.card-block-welcome {
width: 100%;
}
.card-block-welcome img {
width: 100%;
border: 1rem solid #fff;
padding: 0 !important;
}
.welcome_bg {
background-color: #fff;
}
canvas {
width: 100%;
height: 100%;
}
.btnBox{
width: 100%;
height: 80px;
/* background: red; */
}
</style>