图形化编程 html,用GoJS实现图形化交互编程界面示例

这篇博客介绍了如何利用JavaScript和GoJS库创建一个具有可编辑节点和端口的图形界面。内容涵盖图元定义、上下文菜单、端口增删、链接路由等功能,展示了自定义链接类以避免平行链接重叠的方法。此外,还提供了节点模板,包括每个侧面的端口数组,以及对端口位置调整的支持。

JavaScript

语言:

JaveScriptBabelCoffeeScript

确定

function init() {

var $ = go.GraphObject.make; //for conciseness in defining node templates

myDiagram =

$(go.Diagram, "myDiagramDiv", //Diagram refers to its DIV HTML element by id

{

"undoManager.isEnabled": true

});

// when the document is modified, add a "*" to the title and enable the "Save" button

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);

}

});

// To simplify this code we define a function for creating a context menu button:

function makeButton(text, action, visiblePredicate) {

return $("ContextMenuButton",

$(go.TextBlock, text), {

click: action

},

// don't bother with binding GraphObject.visible if there's no predicate

visiblePredicate ? new go.Binding("visible", "", function(o, e) {

return o.diagram ? visiblePredicate(o, e) : false;

}).ofObject() : {});

}

var nodeMenu = // context menu for each Node

$("ContextMenu",

makeButton("Copy",

function(e, obj) {

e.diagram.commandHandler.copySelection();

}),

makeButton("Delete",

function(e, obj) {

e.diagram.commandHandler.deleteSelection();

}),

$(go.Shape, "LineH", {

strokeWidth: 2,

height: 1,

stretch: go.GraphObject.Horizontal

}),

makeButton("Add top port",

function(e, obj) {

addPort("top");

}),

makeButton("Add left port",

function(e, obj) {

addPort("left");

}),

makeButton("Add right port",

function(e, obj) {

addPort("right");

}),

makeButton("Add bottom port",

function(e, obj) {

addPort("bottom");

})

);

var portSize = new go.Size(8, 8);

var portMenu = // context menu for each port

$("ContextMenu",

makeButton("Swap order",

function(e, obj) {

swapOrder(obj.part.adornedObject);

}),

makeButton("Remove port",

// in the click event handler, the obj.part is the Adornment;

// its adornedObject is the port

function(e, obj) {

removePort(obj.part.adornedObject);

}),

makeButton("Change color",

function(e, obj) {

changeColor(obj.part.adornedObject);

}),

makeButton("Remove side ports",

function(e, obj) {

removeAll(obj.part.adornedObject);

})

);

// the node template

// includes a panel on each side with an itemArray of panels containing ports

myDiagram.nodeTemplate =

$(go.Node, "Table", {

locationObjectName: "BODY",

locationSpot: go.Spot.Center,

selectionObjectName: "BODY",

contextMenu: nodeMenu

},

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

// the body

$(go.Panel, "Auto", {

row: 1,

column: 1,

name: "BODY",

stretch: go.GraphObject.Fill

},

$(go.Shape, "Rectangle", {

fill: "#AC193D",

stroke: null,

strokeWidth: 0,

minSize: new go.Size(56, 56)

}),

$(go.TextBlock, {

margin: 10,

textAlign: "center",

font: "14px Segoe UI,sans-serif",

stroke: "white",

editable: true

},

new go.Binding("text", "name").makeTwoWay())

), // end Auto Panel body

// the Panel holding the left port elements, which are themselves Panels,

// created for each item in the itemArray, bound to data.leftArray

$(go.Panel, "Vertical",

new go.Binding("itemArray", "leftArray"), {

row: 1,

column: 0,

itemTemplate: $(go.Panel, {

_side: "left", // internal property to make it easier to tell which side it's on

fromSpot: go.Spot.Left,

toSpot: go.Spot.Left,

fromLinkable: true,

toLinkable: true,

cursor: "pointer",

contextMenu: portMenu

},

new go.Binding("portId", "portId"),

$(go.Shape, "Rectangle", {

stroke: null,

strokeWidth: 0,

desiredSize: portSize,

margin: new go.Margin(1, 0)

},

new go.Binding("fill", "portColor"))

) // end itemTemplate

}

), // end Vertical Panel

// the Panel holding the top port elements, which are themselves Panels,

// created for each item in the itemArray, bound to data.topArray

$(go.Panel, "Horizontal",

new go.Binding("itemArray", "topArray"), {

row: 0,

column: 1,

itemTemplate: $(go.Panel, {

_side: "top",

fromSpot: go.Spot.Top,

toSpot: go.Spot.Top,

fromLinkable: true,

toLinkable: true,

cursor: "pointer",

contextMenu: portMenu

},

new go.Binding("portId", "portId"),

$(go.Shape, "Rectangle", {

stroke: null,

strokeWidth: 0,

desiredSize: portSize,

margin: new go.Margin(0, 1)

},

new go.Binding("fill", "portColor"))

) // end itemTemplate

}

), // end Horizontal Panel

// the Panel holding the right port elements, which are themselves Panels,

// created for each item in the itemArray, bound to data.rightArray

$(go.Panel, "Vertical",

new go.Binding("itemArray", "rightArray"), {

row: 1,

column: 2,

itemTemplate: $(go.Panel, {

_side: "right",

fromSpot: go.Spot.Right,

toSpot: go.Spot.Right,

fromLinkable: true,

toLinkable: true,

cursor: "pointer",

contextMenu: portMenu

},

new go.Binding("portId", "portId"),

$(go.Shape, "Rectangle", {

stroke: null,

strokeWidth: 0,

desiredSize: portSize,

margin: new go.Margin(1, 0)

},

new go.Binding("fill", "portColor"))

) // end itemTemplate

}

), // end Vertical Panel

// the Panel holding the bottom port elements, which are themselves Panels,

// created for each item in the itemArray, bound to data.bottomArray

$(go.Panel, "Horizontal",

new go.Binding("itemArray", "bottomArray"), {

row: 2,

column: 1,

itemTemplate: $(go.Panel, {

_side: "bottom",

fromSpot: go.Spot.Bottom,

toSpot: go.Spot.Bottom,

fromLinkable: true,

toLinkable: true,

cursor: "pointer",

contextMenu: portMenu

},

new go.Binding("portId", "portId"),

$(go.Shape, "Rectangle", {

stroke: null,

strokeWidth: 0,

desiredSize: portSize,

margin: new go.Margin(0, 1)

},

new go.Binding("fill", "portColor"))

) // end itemTemplate

}

) // end Horizontal Panel

); // end Node

// an orthogonal link template, reshapable and relinkable

myDiagram.linkTemplate =

$(CustomLink, // defined below

{

routing: go.Link.AvoidsNodes,

corner: 4,

curve: go.Link.JumpGap,

reshapable: true,

resegmentable: true,

relinkableFrom: true,

relinkableTo: true

},

new go.Binding("points").makeTwoWay(),

$(go.Shape, {

stroke: "#2F4F4F",

strokeWidth: 2

})

);

// support double-clicking in the background to add a copy of this data as a node

myDiagram.toolManager.clickCreatingTool.archetypeNodeData = {

name: "Unit",

leftArray: [],

rightArray: [],

topArray: [],

bottomArray: []

};

myDiagram.contextMenu =

$("ContextMenu",

makeButton("Paste",

function(e, obj) {

e.diagram.commandHandler.pasteSelection(e.diagram.lastInput.documentPoint);

},

function(o) {

return o.diagram.commandHandler.canPasteSelection();

}),

makeButton("Undo",

function(e, obj) {

e.diagram.commandHandler.undo();

},

function(o) {

return o.diagram.commandHandler.canUndo();

}),

makeButton("Redo",

function(e, obj) {

e.diagram.commandHandler.redo();

},

function(o) {

return o.diagram.commandHandler.canRedo();

})

);

// load the diagram from JSON data

load();

}

// This custom-routing Link class tries to separate parallel links from each other.

// This assumes that ports are lined up in a row/column on a side of the node.

function CustomLink() {

go.Link.call(this);

};

go.Diagram.inherit(CustomLink, go.Link);

CustomLink.prototype.findSidePortIndexAndCount = function(node, port) {

var nodedata = node.data;

if (nodedata !== null) {

var portdata = port.data;

var side = port._side;

var arr = nodedata[side + "Array"];

var len = arr.length;

for (var i = 0; i < len; i++) {

if (arr[i] === portdata) return [i, len];

}

}

return [-1, len];

};

CustomLink.prototype.computeEndSegmentLength = function(node, port, spot, from) {

var esl = go.Link.prototype.computeEndSegmentLength.call(this, node, port, spot, from);

var other = this.getOtherPort(port);

if (port !== null && other !== null) {

var thispt = port.getDocumentPoint(this.computeSpot(from));

var otherpt = other.getDocumentPoint(this.computeSpot(!from));

if (Math.abs(thispt.x - otherpt.x) > 20 || Math.abs(thispt.y - otherpt.y) > 20) {

var info = this.findSidePortIndexAndCount(node, port);

var idx = info[0];

var count = info[1];

if (port._side == "top" || port._side == "bottom") {

if (otherpt.x < thispt.x) {

return esl + 4 + idx * 8;

} else {

return esl + (count - idx - 1) * 8;

}

} else { // left or right

if (otherpt.y < thispt.y) {

return esl + 4 + idx * 8;

} else {

return esl + (count - idx - 1) * 8;

}

}

}

}

return esl;

};

CustomLink.prototype.hasCurviness = function() {

if (isNaN(this.curviness)) return true;

return go.Link.prototype.hasCurviness.call(this);

};

CustomLink.prototype.computeCurviness = function() {

if (isNaN(this.curviness)) {

var fromnode = this.fromNode;

var fromport = this.fromPort;

var fromspot = this.computeSpot(true);

var frompt = fromport.getDocumentPoint(fromspot);

var tonode = this.toNode;

var toport = this.toPort;

var tospot = this.computeSpot(false);

var topt = toport.getDocumentPoint(tospot);

if (Math.abs(frompt.x - topt.x) > 20 || Math.abs(frompt.y - topt.y) > 20) {

if ((fromspot.equals(go.Spot.Left) || fromspot.equals(go.Spot.Right)) &&

(tospot.equals(go.Spot.Left) || tospot.equals(go.Spot.Right))) {

var fromseglen = this.computeEndSegmentLength(fromnode, fromport, fromspot, true);

var toseglen = this.computeEndSegmentLength(tonode, toport, tospot, false);

var c = (fromseglen - toseglen) / 2;

if (frompt.x + fromseglen >= topt.x - toseglen) {

if (frompt.y < topt.y) return c;

if (frompt.y > topt.y) return -c;

}

} else if ((fromspot.equals(go.Spot.Top) || fromspot.equals(go.Spot.Bottom)) &&

(tospot.equals(go.Spot.Top) || tospot.equals(go.Spot.Bottom))) {

var fromseglen = this.computeEndSegmentLength(fromnode, fromport, fromspot, true);

var toseglen = this.computeEndSegmentLength(tonode, toport, tospot, false);

var c = (fromseglen - toseglen) / 2;

if (frompt.x + fromseglen >= topt.x - toseglen) {

if (frompt.y < topt.y) return c;

if (frompt.y > topt.y) return -c;

}

}

}

}

return go.Link.prototype.computeCurviness.call(this);

};

// end CustomLink class

// Add a port to the specified side of the selected nodes.

function addPort(side) {

myDiagram.startTransaction("addPort");

myDiagram.selection.each(function(node) {

// skip any selected Links

if (!(node instanceof go.Node)) return;

// compute the next available index number for the side

var i = 0;

while (node.findPort(side + i.toString()) !== node) i++;

// now this new port name is unique within the whole Node because of the side prefix

var name = side + i.toString();

// get the Array of port data to be modified

var arr = node.data[side + "Array"];

if (arr) {

// create a new port data object

var newportdata = {

portId: name,

portColor: go.Brush.randomColor()

// if you add port data properties here, you should copy them in copyPortData above

};

// and add it to the Array of port data

myDiagram.model.insertArrayItem(arr, -1, newportdata);

}

});

myDiagram.commitTransaction("addPort");

}

// Exchange the position/order of the given port with the next one.

// If it's the last one, swap with the previous one.

function swapOrder(port) {

var arr = port.panel.itemArray;

if (arr.length >= 2) { // only if there are at least two ports!

for (var i = 0; i < arr.length; i++) {

if (arr[i].portId === port.portId) {

myDiagram.startTransaction("swap ports");

if (i >= arr.length - 1) i--; // now can swap I and I+1, even if it's the last port

var newarr = arr.slice(0); // copy Array

newarr[i] = arr[i + 1]; // swap items

newarr[i + 1] = arr[i];

// remember the new Array in the model

myDiagram.model.setDataProperty(port.part.data, port._side + "Array", newarr);

myDiagram.commitTransaction("swap ports");

break;

}

}

}

}

// Remove the clicked port from the node.

// Links to the port will be redrawn to the node's shape.

function removePort(port) {

myDiagram.startTransaction("removePort");

var pid = port.portId;

var arr = port.panel.itemArray;

for (var i = 0; i < arr.length; i++) {

if (arr[i].portId === pid) {

myDiagram.model.removeArrayItem(arr, i);

break;

}

}

myDiagram.commitTransaction("removePort");

}

// Remove all ports from the same side of the node as the clicked port.

function removeAll(port) {

myDiagram.startTransaction("removePorts");

var nodedata = port.part.data;

var side = port._side; // there are four property names, all ending in "Array"

myDiagram.model.setDataProperty(nodedata, side + "Array", []); // an empty Array

myDiagram.commitTransaction("removePorts");

}

// Change the color of the clicked port.

function changeColor(port) {

myDiagram.startTransaction("colorPort");

var data = port.data;

myDiagram.model.setDataProperty(data, "portColor", go.Brush.randomColor());

myDiagram.commitTransaction("colorPort");

}

// Save the model to / load it from JSON text shown on the page itself, not in a database.

function save() {

document.getElementById("mySavedModel").value = myDiagram.model.toJson();

myDiagram.isModified = false;

}

function load() {

myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);

// When copying a node, we need to copy the data that the node is bound to.

// This JavaScript object includes properties for the node as a whole, and

// four properties that are Arrays holding data for each port.

// Those arrays and port data objects need to be copied too.

// Thus Model.copiesArrays and Model.copiesArrayObjects both need to be true.

// Link data includes the names of the to- and from- ports;

// so the GraphLinksModel needs to set these property names:

// linkFromPortIdProperty and linkToPortIdProperty.

}

window.onload = function() {

init()

};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值