Is it possible to make groups non-intersecting in GoJS? - javascript

I have several groups with nodes and I'd like to make these groups non-intersecting on moving. What I need to do for that? There is an example of my group template.
$(go.Group, "Auto",
{
layout: $(go.LayeredDigraphLayout, {
direction: 0,
columnSpacing: 10,
initializeOption: go.LayeredDigraphLayout.InitDepthFirstOut,
aggressiveOption: go.LayeredDigraphLayout.AggressiveMore
}),
minSize: new go.Size(800, 30),
computesBoundsIncludingLocation: true,
computesBoundsIncludingLinks: true,
computesBoundsAfterDrag: true,
isSubGraphExpanded: true
},
$(go.Shape, "Rectangle", [
{
fill: null,
stroke: "gray",
strokeWidth: 2
},
new go.Binding('fill', '', function (group) {
return group.data.isEditable ? '#eee' : '#F7EAEC';
}).ofObject('')
]),
$(go.Panel, "Vertical",
{ defaultAlignment: go.Spot.Left },
$(go.Panel, "Horizontal",
{ defaultAlignment: go.Spot.Top },
$(go.TextBlock,
{ font: "Bold 18px Sans-Serif", textAlign: "left" },
new go.Binding("text", "name"))
),
$(go.Placeholder,
{ padding: new go.Margin(10, 10), margin: 0 })
)
);

The optimization that is needed is in treating groups as atomic objects. There is no need to test whether any of the member nodes of a group also overlap any nodes when one has already checked the whole group.
Implementing that is just adding two lines to the navig function in that sample, https://gojs.net/latest/samples/dragUnoccupied.html.
function isUnoccupied(r, node) {
var diagram = node.diagram;
// nested function used by Layer.findObjectsIn, below
// only consider Parts, and ignore the given Node and any Links
function navig(obj) {
var part = obj.part;
if (part === node) return null;
if (part instanceof go.Link) return null;
// add these two checks:
if (part.isMemberOf(node)) return null;
if (node.isMemberOf(part)) return null;
return part;
}
// only consider non-temporary Layers
var lit = diagram.layers;
while (lit.next()) {
var lay = lit.value;
if (lay.isTemporary) continue;
if (lay.findObjectsIn(r, navig, null, true).count > 0) return false;
}
return true;
}
// a Part.dragComputation function that prevents a Part from being dragged to overlap another Part
function avoidNodeOverlap(node, pt, gridpt) {
if (node.diagram instanceof go.Palette) return gridpt;
// this assumes each node is fully rectangular
var bnds = node.actualBounds;
var loc = node.location;
// use PT instead of GRIDPT if you want to ignore any grid snapping behavior
// see if the area at the proposed location is unoccupied
var r = new go.Rect(gridpt.x - (loc.x - bnds.x), gridpt.y - (loc.y - bnds.y), bnds.width, bnds.height);
// maybe inflate R if you want some space between the node and any other nodes
r.inflate(-0.5, -0.5); // by default, deflate to avoid edge overlaps with "exact" fits
// when dragging a node from another Diagram, choose an unoccupied area
if (!(node.diagram.currentTool instanceof go.DraggingTool) &&
(!node._temp || !node.layer.isTemporary)) { // in Temporary Layer during external drag-and-drop
node._temp = true; // flag to avoid repeated searches during external drag-and-drop
while (!isUnoccupied(r, node)) {
r.x += 10; // note that this is an unimaginative search algorithm --
r.y += 10; // you can improve the search here to be more appropriate for your app
}
r.inflate(0.5, 0.5); // restore to actual size
// return the proposed new location point
return new go.Point(r.x - (loc.x - bnds.x), r.y - (loc.y - bnds.y));
}
if (isUnoccupied(r, node)) return gridpt; // OK
return loc; // give up -- don't allow the node to be moved to the new location
}
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true,
// support creating groups with Ctrl-G
"commandHandler.archetypeGroupData": { isGroup: true, text: "NEW GROUP" }
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
{ // avoid overlapping other nodes
dragComputation: avoidNodeOverlap
},
$(go.Shape,
{ fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8, editable: true },
new go.Binding("text").makeTwoWay())
);
myDiagram.groupTemplate =
$(go.Group, "Vertical",
{ // avoid overlapping other nodes
dragComputation: avoidNodeOverlap,
// support ungrouping by Ctrl-Shift-G
ungroupable: true
},
$(go.TextBlock,
{ font: "bold 14pt sans-serif", editable: true },
new go.Binding("text").makeTwoWay()),
$(go.Panel, "Auto",
$(go.Shape, { fill: "lightgray" }),
$(go.Placeholder, { padding: 5 })
)
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue" },
{ key: 2, text: "Beta", color: "orange" },
{ key: 3, text: "Gamma", color: "lightgreen" },
{ key: 4, text: "Delta", color: "pink" }
],
[
{ from: 1, to: 2 },
{ from: 1, to: 3 },
{ from: 2, to: 2 },
{ from: 3, to: 4 },
{ from: 4, to: 1 }
]);
}
To create a Group, select some nodes and type Control-G.

Set this in your nodeTemplate:
myDiagram.nodeTemplate =
$(go.Node, "Auto",
{ dragComputation: avoidNodeOverlap });
You can check a solution here:
https://gojs.net/latest/samples/dragUnoccupied.html

Related

JsPlumb, How to save connector labels?

Could someone help me out with what I am missing?
The scenario is: there are several nodes. Among them, one node can connect with 3 other nodes and those connections should be labeled.
The codes are connecting multiple nodes with labels, but on save it doesn't save the labels. On page load, it shows the connections but not the labels.
Here are my codes.
let connector = [
"EditableBezier",
{
cornerRadius: 0,
curviness: 30,
},
];
let paintStyle = {
strokeWidth: 2,
stroke: "rgb(217,217,217)",
outlineWidth: 3,
outlineStroke: "transparent",
};
let paintStyle = {
strokeWidth: 2,
stroke: "rgb(217,217,217)",
outlineWidth: 3,
outlineStroke: "transparent",
};
let hoverStyle = {
strokeWidth: 2,
stroke: "rgb(67,67,67)",
};
let abLabelCounter = 1;
function abSplitConnections(params) {
var source = params.connection.source;
var target = params.connection.target;
if (source.id === target.id) return false;
var conn = jsPlumb.getInstance();
let c = conn.connect({
source: source,
target: target,
editable: true,
anchor: "AutoDefault",
endpoint: "Blank",
connector: connector,
paintStyle: paintStyle,
hoverPaintStyle: hoverStyle,
overlays: [
[
"Arrow",
{
location: 1,
width: 10,
length: 10,
},
],
[
"Label",
{
location: 0.5,
label: () => {
if (abLabelCounter === 1) {
abLabelCounter++;
return "A";
}
if (abLabelCounter === 2) {
abLabelCounter++;
return "B";
}
if (abLabelCounter === 3) {
abLabelCounter = 1;
return "C";
}
},
id: "partition",
},
],
],
});
}

Gojs making part of header always visible and changing background colors individually

Bands are currently running dynamically in the Banded Tree Layout.When there is no data on the nodes in the band, the band is completely invisible.
Q 1 : How can I make the header part always visible even if the related band is empty?
Q 2:
how do i change the background colors of the headers individually?
myDiagram.nodeTemplateMap.add("VerticalBands",
$(go.Part, "Position",
{
isLayoutPositioned: false, // but still in document bounds
locationSpot: new go.Spot(0, 0, 0, 30), // account for header height
layerName: "Background",
pickable: false,
selectable: false,
itemTemplate:
$(go.Panel, "Vertical",
new go.Binding("opacity", "visible", function (v) { return v ? 1 : 0; }),
new go.Binding("position", "bounds", function (b) { return b.position; }),
$(go.TextBlock,
{
verticalAlignment: go.Spot.Center,
textAlign: "center",
background: headerColor,
wrap: go.TextBlock.None,
font: "12pt 'Open Sans', Arial, sans-serif",
stroke: "transparent",
height: 30,
},
new go.Binding("text"),
new go.Binding("width", "bounds", function (r) { return r.width; })),
// for rectangular bands:
$(go.Shape,
{ stroke: null, strokeWidth: 0 },
new go.Binding("desiredSize", "bounds", function (r) { return r.size; }),
new go.Binding("fill", "itemIndex", function (i) { return i % 2 == 0 ? "#ececec" : "whitesmoke" ; }).ofObject()),
)
},
new go.Binding("itemArray")
));
myDiagram.model = new go.TreeModel(nodearray);
}
// update the background object holding the visual "bands"
var bands = this.diagram.findPartForKey("_BANDS");
if (bands) {
var model = this.diagram.model;
bands.location = this.arrangementOrigin.copy().add(offset);
// make each band visible or not, depending on whether there is a layer for it
for (var it = bands.elements; it.next();) {
var idx = it.key;
var elt = it.value; // the item panel representing a band
elt.visible = idx < layerRects.length;
}
// set the bounds of each band via data binding of the "bounds" property
var arr = bands.data.itemArray;
for (var i = 0; i < layerRects.length; i++) {
var itemdata = arr[i];
if (itemdata) {
model.setDataProperty(itemdata, "bounds", layerRects[i]);
}
}
}

GraphObject.make requires a class function or GoJS class name or name of an object builder, not: ToolTip

My version is gojs-1.7.22 and I am trying to load the sample productionProcess but its retrieving the following error:
"GraphObject.make requires a class function or GoJS class name or name of an object builder, not: ToolTip".
I am doing something wrong?
// Load libs
var $ = go.GraphObject.make; // for conciseness in defining templates
myDiagram = $(go.Diagram, "myDiagramDiv", // create a Diagram for the DIV HTML element
{
maxSelectionCount: 1, // users can select only one part at a time
"toolManager.hoverDelay": 10, // how quickly tooltips are shown
initialAutoScale: go.Diagram.Uniform, // scale to show all of the contents
"ChangedSelection": onSelectionChanged, // view additional information
});
function infoString(obj) {
var part = obj.part;
if (part instanceof go.Adornment) part = part.adornedPart;
var msg = "";
if (part instanceof go.Link) {
msg = "";
} else if (part instanceof go.Node) {
msg = part.data.text + ":\n\n" + part.data.description;
}
return msg;
}
var colors = {
"red": "#be4b15",
"green": "#52ce60",
"blue": "#6ea5f8",
"lightred": "#fd8852",
"lightblue": "#afd4fe",
"lightgreen": "#b9e986",
"pink": "#faadc1",
"purple": "#d689ff",
"orange": "#fdb400",
};
// A data binding conversion function. Given an name, return the Geometry.
// If there is only a string, replace it with a Geometry object, which can be shared by multiple Shapes.
function geoFunc(geoname) {
var geo = icons[geoname];
if (typeof geo === "string") {
geo = icons[geoname] = go.Geometry.parse(geo, true);
}
return geo;
}
myDiagram.nodeTemplate =
$(go.Node, "Spot",
{
locationObjectName: 'main',
locationSpot: go.Spot.Center,
toolTip:
$("ToolTip",
$(go.TextBlock, { margin: 4, width: 140 },
new go.Binding("text", "", infoString).ofObject())
)
},
new go.Binding("location", "pos", go.Point.parse).makeTwoWay(go.Point.stringify),
// The main element of the Spot panel is a vertical panel housing an optional icon,
// plus a rectangle that acts as the port
$(go.Panel, "Vertical",
$(go.Shape, {
name: 'icon',
width: 1, height: 1,
stroke: null, strokeWidth: 0,
fill: colors.blue
},
new go.Binding("fill", "color", function (c) { return colors[c]; }),
new go.Binding("width", "iconWidth"),
new go.Binding("height", "iconHeight"),
new go.Binding("geometry", "icon", geoFunc)),
$(go.Shape, {
name: 'main',
width: 40, height: 40,
margin: new go.Margin(-1, 0, 0, 0),
portId: "",
stroke: null, strokeWidth: 0,
fill: colors.blue
},
new go.Binding("fill", "color", function (c) { return colors[c]; }),
new go.Binding("width", "portWidth"),
new go.Binding("height", "portHeight"))
),
$(go.TextBlock, {
font: "Bold 14px Lato, sans-serif",
textAlign: "center",
margin: 3,
maxSize: new go.Size(100, NaN),
alignment: go.Spot.TopCenter,
alignmentFocus: go.Spot.BottomCenter
},
new go.Binding("text"))
);
// Some links need a custom to or from spot
function spotConverter(dir) {
if (dir === "left") return go.Spot.LeftSide;
if (dir === "right") return go.Spot.RightSide;
if (dir === "top") return go.Spot.TopSide;
if (dir === "bottom") return go.Spot.BottomSide;
if (dir === "rightsingle") return go.Spot.Right;
}
myDiagram.linkTemplate =
$(go.Link, {
toShortLength: -2,
fromShortLength: -2,
layerName: "Background",
routing: go.Link.Orthogonal,
corner: 15,
fromSpot: go.Spot.RightSide,
toSpot: go.Spot.LeftSide
},
// make sure links come in from the proper direction and go out appropriately
new go.Binding("fromSpot", "fromSpot", function (d) { return spotConverter(d); }),
new go.Binding("toSpot", "toSpot", function (d) { return spotConverter(d); }),
new go.Binding("points").makeTwoWay(),
// mark each Shape to get the link geometry with isPanelMain: true
$(go.Shape, { isPanelMain: true, stroke: colors.lightblue, strokeWidth: 10 },
new go.Binding("stroke", "color", function (c) { return colors[c]; })),
$(go.Shape, { isPanelMain: true, stroke: "white", strokeWidth: 3, name: "PIPE", strokeDashArray: [20, 40] })
);
myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
loop(); // animate some flow through the pipes
I am following the sample described in the following link
https://github.com/NorthwoodsSoftware/GoJS/blob/master/samples/productionProcess.html
The "ToolTip" builder was added in version 2.0. Try using the sample from the 1.7 version.

GOJS, how do you bind data to the group property?

I have a GOJS application up and running and i'm trying to bind a parameter in my data to the group property.
If I manually set the group property in the data then it works exactly as I would expect and the nodes appear as part of the group, but if the group is set using binding then no group connection seems to be made.
What am I missing?
example showing the group being set within the data
var nodes = []
var nodeObj ={
key:"groupObject",
text:"group",
isGroup:true
}
nodes.push(nodeObj)
nodeObj = {
key:"node1",
text:"node1",
group:"groupObject"
}
nodes.push(nodeObj)
nodeObj = {
key:"node2",
text:"node2",
group:"groupObject"
}
nodes.push(nodeObj)
}
const initDiagram = () => {
const $ = go.GraphObject.make;
const diagram =
$(go.Diagram,
{
'undoManager.isEnabled': true,
'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' },
model: $(go.GraphLinksModel,
{
linkKeyProperty: 'key'
})
});
diagram.nodeTemplate =
$(go.Node, 'Auto',
$(go.Shape, 'RoundedRectangle',
{ name: 'SHAPE', fill: 'white', strokeWidth: 0 },
new go.Binding('fill', 'color')),
$(go.TextBlock,
{ margin: 8, editable: true, stroke:"black" },
new go.Binding('text').makeTwoWay()
)
);
diagram.groupTemplate =
$(go.Group, "Vertical", $(go.GridLayout,{wrappingColumn:1}),
$(go.TextBlock, // group title
{ alignment: go.Spot.Center, font: "Bold 15pt Sans-Serif" },
new go.Binding("text")),
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle", // surrounds the Placeholder
{fill: "lightblue" }),
$(go.Placeholder,
{ padding: 5}),
)
);
return diagram;
}
This works ^^^
Now if I set the group parameter in the data to "groupName" instead of group, then bind group to groupName in the init function, the nodes no longer appear as part of the group
var nodes = []
var nodeObj ={
key:"groupObject",
text:"group",
isGroup:true
}
nodes.push(nodeObj)
nodeObj = {
key:"node1",
text:"node1",
groupName:"groupObject" //this line has changed
}
nodes.push(nodeObj)
nodeObj = {
key:"node2",
text:"node2",
groupName:"groupObject" //This line has changed
}
nodes.push(nodeObj)
}
const initDiagram = () => {
const $ = go.GraphObject.make;
const diagram =
$(go.Diagram,
{
'undoManager.isEnabled': true,
'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' },
model: $(go.GraphLinksModel,
{
linkKeyProperty: 'key'
})
});
diagram.nodeTemplate =
$(go.Node, 'Auto',
new go.Binding('group','groupName'), //this line has changed
$(go.Shape, 'RoundedRectangle',
{ name: 'SHAPE', fill: 'white', strokeWidth: 0 },
new go.Binding('fill', 'color')),
$(go.TextBlock,
{ margin: 8, editable: true, stroke:"black" },
new go.Binding('text').makeTwoWay()
)
);
diagram.groupTemplate =
$(go.Group, "Vertical", $(go.GridLayout,{wrappingColumn:1}),
$(go.TextBlock, // group title
{ alignment: go.Spot.Center, font: "Bold 15pt Sans-Serif" },
new go.Binding("text")),
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle",
{fill: "lightblue" }),
$(go.Placeholder,
{ padding: 5}),
)
);
return diagram;
}
Bindings are used to keep the properties of the GraphObjects of one Part up-to-date with properties on that Part's model data. Bindings are not used for maintaining relationships between Parts. Only models know how to interpret and maintain relationships.
If you want to use the data property "groupName" instead of "group" to refer to the node's containing group, set GraphLinksModel.nodeGroupKeyProperty to "groupName". Preferably before you set Model.nodeDataArray.
Also, please read https://gojs.net/latest/intro/dataBinding.html#ChangingGraphStructure.

Nested elements in jsPlumb

I'm trying to put together a chart with nested elements that connect to each others' endpoints. As you can see in this jsFiddle, it's not going very well. The JS is reproduced below in full:
jsPlumb.ready(function () {
jsPlumb.importDefaults({ ConnectionOverlays: [["Arrow", { location: 1 }]] });
var pinCount = 0;
var pin = {
endpoint: "Dot",
isSource: true,
isTarget: true,
maxConnections: -1,
paintStyle: {
strokeStyle: "#1e8151",
fillStyle: "transparent",
radius: 7,
lineWidth: 2
},
connectorStyle: {
lineWidth: 3,
strokeStyle: "#deea18",
joinstyle: "round",
outlineColor: "#eaedef",
outlineWidth: 0,
overlays: [
["Arrow", {
location: 1
}]
]
},
connectorHoverStyle: {
lineWidth: 4,
strokeStyle: "#5C96BC",
outlineWidth: 2,
outlineColor: "white"
},
connector: ["StateMachine", {
stub: [5, 5],
gap: 10,
cornerRadius: 5,
alwaysRespectStubs: true
}]
}
jsPlumb.draggable($(".container"));
jsPlumb.draggable($(".reactor"), { containment: "parent" });
function rebalance(part, side) {
var ends = $.grep(jsPlumb.getEndpoints(part), function (elem, i) {
return elem.anchor.type == side
});
var len = ends.length + 1;
$.each(ends, function (i, elem) {
elem.anchor.y = (1 / len) * (i + 1);
});
jsPlumb.repaintEverything();
}
function addPin(part, side) {
jsPlumb.addEndpoint(part, pin, { anchor: side, uuid: "pin" + pinCount });
pinCount++;
rebalance(part, side);
}
var $c = $("#container");
var $r1 = $("#child1"), $r2 = $("#child2");
addPin($c, "Left");
addPin($c, "Right");
addPin($r1, "Left");
addPin($r1, "Right");
addPin($r2, "Left");
addPin($r2, "Right");
jsPlumb.connect({uuids: ["pin0", "pin2"]});
jsPlumb.connect({uuids: ["pin3", "pin4"]});
jsPlumb.connect({uuids: ["pin5", "pin1"]});
});
Peer node connections, as in ["pin3", "pin4"], look sane until you move any part of the chart. Then they explode. Connections between parent/child endpoints never really do what they're supposed to. Removing position: absolute; and draggable from all elements involved does something half-way passable, but a) still has some placement issues and b) removing those properties defeats the purpose.
General jsPlumb tips welcome, but specifically: Am I doing something wrong that prevents this from lining up properly? Am I misusing some property of connectors/endpoints? Is there a workaround for the placement issue I'm running into here?

Categories