Swimlane - GoJS library - javascript

I would like to ask, if anyone knows how to convert the orientation of the diagram of swimlanes in GOjs library from being top-level to a single row orientation, it is because I want to have a diagram that connects a node to another from a different group.
And also is it possible to add attributes to each node, like onmouseover, class, id, name, and etc.?
Currently I have this copied and paste code:
<!DOCTYPE html>
<html>
<head>
<title>Swimlane</title>
<!-- Copyright 1998-2014 by Northwoods Software Corporation. -->
<link href="goSamples.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="go.js"></script>
<script id="code">
// These parameters need to be set before defining the templates.
// this controls whether the swimlanes are horizontal stacked vertically, or the other way:
var HORIZONTAL = true;
// this controls the minimum length of any swimlane
var MINLENGTH = 200;
// this controls the minimum breadth of any swimlane
var MINBREADTH = 100;
// compute the minimum length needed to hold all of the subgraphs
function computeMinPlaceholderSize(diagram) {
var len = MINLENGTH;
for (var it = diagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var holder = group.placeholder;
if (holder !== null) {
var sz = holder.actualBounds;
len = Math.max(len, (HORIZONTAL ? sz.width : sz.height));
}
}
return (HORIZONTAL ? new go.Size(len, NaN) : new go.Size(NaN, len));
}
// get the minimum placeholder size for a particular Group;
// when group is null, return the minimum size
function computePlaceholderSize(group) {
if (group instanceof go.Group) {
var holder = group.placeholder;
if (holder !== null) {
return holder.actualBounds.size;
}
}
return (HORIZONTAL ? new go.Size(MINLENGTH, MINBREADTH) : new go.Size(MINBREADTH, MINLENGTH));
}
// define a custom ResizingTool to limit how far one can shrink a Group
function GroupResizingTool() {
go.ResizingTool.call(this);
}
go.Diagram.inherit(GroupResizingTool, go.ResizingTool);
GroupResizingTool.prototype.isLengthening = function() {
return (this.handle.alignment === (HORIZONTAL ? go.Spot.Right : go.Spot.Bottom));
};
GroupResizingTool.prototype.computeMinSize = function() {
var msz = computePlaceholderSize(null); // get the minimum size
if (this.isLengthening()) { // compute the minimum length of all lanes
var sz = computeMinPlaceholderSize(this.diagram);
if (HORIZONTAL) {
msz.width = Math.max(msz.width, sz.width);
} else {
msz.height = Math.max(msz.height, sz.height);
}
} else { // find the minimum size of this single lane
var sz = computePlaceholderSize(this.adornedObject.part);
msz.width = Math.max(msz.width, sz.width);
msz.height = Math.max(msz.height, sz.height);
}
return msz;
};
GroupResizingTool.prototype.resize = function(newr) {
if (this.isLengthening()) { // changing the length of all of the lanes
for (var it = myDiagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var shape = group.findObject("SHAPE");
if (shape !== null) { // set its desiredSize, but leave the other direction alone
if (HORIZONTAL) {
shape.width = newr.width;
} else {
shape.height = newr.height;
}
}
}
} else { // changing the breadth and length of a single lane
go.ResizingTool.prototype.resize.call(this, newr);
}
};
// end GroupResizingTool class
// define a custom grid layout that makes sure the length of each lane is the same
// and that each lane is broad enough to hold its subgraph
function StackLayout() {
go.GridLayout.call(this);
}
go.Diagram.inherit(StackLayout, go.GridLayout);
StackLayout.prototype.doLayout = function(coll) {
var diagram = this.diagram;
if (diagram === null) return;
diagram.startTransaction("StackLayout");
// make sure all of the Group Shapes are big enough
var minsize = computeMinPlaceholderSize(diagram);
for (var it = diagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var shape = group.findObject("SHAPE");
if (shape !== null) { // change the desiredSize to be big enough in both directions
var sz = computePlaceholderSize(group);
if (HORIZONTAL) {
shape.width = (isNaN(shape.width) ? minsize.width : Math.max(shape.width, minsize.width));
if (!isNaN(shape.height)) shape.height = Math.max(shape.height, sz.height);
} else {
if (!isNaN(shape.width)) shape.width = Math.max(shape.width, sz.width);
shape.height = (isNaN(shape.height) ? minsize.height : Math.max(shape.height, minsize.height));
}
var cell = group.resizeCellSize;
if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) shape.width = Math.ceil(shape.width / cell.width) * cell.width;
if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) shape.height = Math.ceil(shape.height / cell.height) * cell.height;
}
}
// now do all of the usual stuff, according to whatever properties have been set on this GridLayout
go.GridLayout.prototype.doLayout.call(this, coll);
diagram.commitTransaction("StackLayout");
};
// end StackLayout class
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagram",
{
// use a custom ResizingTool (along with a custom ResizeAdornment on each Group)
resizingTool: new GroupResizingTool(),
// use a simple layout that ignores links to stack the top-level Groups on top of each other
layout:
$(StackLayout,
{
cellSize: new go.Size(1, 1),
spacing: new go.Size(0, 0),
wrappingColumn: (HORIZONTAL ? 1 : Infinity),
wrappingWidth: Infinity,
isViewportSized: false
}),
// don't allow dropping onto the diagram's background
mouseDrop: function(e) { e.diagram.currentTool.doCancel(); },
// a clipboard copied node is pasted into the original node's group (i.e. lane).
"commandHandler.copiesGroupKey": true
});
// When a Node has been moved, make sure all of the top-level Groups get laid out again in a stack
function relayoutDiagramStack(e) {
myDiagram.layout.invalidateLayout(); // but don't invalidate all Layouts that are in Groups
myDiagram.layoutDiagram();
}
myDiagram.addDiagramListener("SelectionMoved", relayoutDiagramStack);
myDiagram.addDiagramListener("SelectionCopied", relayoutDiagramStack);
// this is a Part.dragComputation function for limiting where a Node may be dragged
function stayInGroup(part, pt, gridpt) {
// don't constrain top-level nodes
var grp = part.containingGroup;
if (grp === null) return pt;
// try to stay within the background Shape of the Group
var back = grp.findObject("SHAPE");
if (back === null) return pt;
// allow dragging a Node out of a Group if the Shift key is down
if (part.diagram.lastInput.shift) return pt;
var b = part.actualBounds;
var p1 = back.getDocumentPoint(go.Spot.TopLeft);
var p2 = back.getDocumentPoint(go.Spot.BottomRight);
// find the padding inside the group's placeholder that is around the member parts
var m = grp.placeholder.padding;
// now limit the location appropriately
var x = Math.max(p1.x + m.left, Math.min(pt.x, p2.x - m.right - b.width - 1));
var y = Math.max(p1.y + m.top, Math.min(pt.y, p2.y - m.bottom - b.height - 1));
return new go.Point(x, y);
}
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
{ fill: "white", portId: "", cursor: "pointer", fromLinkable: true, toLinkable: true }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key")),
// limit dragging of Nodes to stay within the containing Group, defined above
{
dragComputation: stayInGroup,
mouseDrop: function (e, node) { // dropping a copy of some Nodes and Links onto this Node adds them to this Node's Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var grp = node.containingGroup;
if (grp !== null) {
var ok = grp.addMembers(node.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
layoutConditions: go.Part.LayoutAdded | go.Part.LayoutNodeSized
}
);
// each Group is a "swimlane" with a header on the left and a resizable lane on the right
myDiagram.groupTemplate =
$(go.Group, HORIZONTAL ? "Horizontal" : "Vertical",
{
movable: false, copyable: false, deletable: false, // can't move or copy or delete lanes
avoidable: false,
selectionObjectName: "SHAPE", // selecting a lane causes the body of the lane to be highlit, not the label
resizable: true, resizeObjectName: "SHAPE", // allow lanes to be resized, but the custom resizeAdornmentTemplate only permits one kind of resizing
layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{ direction: HORIZONTAL ? 0 : 90, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }),
computesBoundsAfterDrag: true, // needed to prevent recomputing Group.placeholder bounds too soon
computesBoundsIncludingLinks: false,
computesBoundsIncludingLocation: true,
mouseDrop: function (e, grp) { // dropping a copy of some Nodes and Links onto this Group adds them to this Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var ok = grp.addMembers(grp.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
// the lane header consisting of a Shape and a TextBlock
$(go.Panel, "Horizontal",
{ angle: HORIZONTAL ? 270 : 0, // maybe rotate the header to read sideways going up
alignment: go.Spot.Center },
$(go.Shape, "Diamond",
{ width: 8, height: 8 },
new go.Binding("fill", "color")),
$(go.TextBlock, // the lane label
{ font: "bold 16pt sans-serif" },
new go.Binding("text", "key"))
), // end Horizontal Panel
$(go.Panel, "Auto", // the lane consisting of a background Shape and a Placeholder representing the subgraph
$(go.Shape, "Rectangle",
{ name: "SHAPE", fill: "white", minSize: computePlaceholderSize(null) },
new go.Binding("fill", "color")),
$(go.Placeholder,
{ padding: 10, alignment: go.Spot.TopLeft })
) // end Auto Panel
); // end Group
// define a custom resize adornment that only has a single resize handle
myDiagram.groupTemplate.resizeAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Placeholder),
$(go.Shape, // for changing the length of a lane
{
alignment: HORIZONTAL ? go.Spot.Right: go.Spot.Bottom,
desiredSize: HORIZONTAL ? new go.Size(7, 50) : new go.Size(50, 7),
fill: "lightblue", stroke: "dodgerblue",
cursor: HORIZONTAL ? "col-resize" : "row-resize"
}),
$(go.Shape, // for changing the breadth of a lane
{
alignment: HORIZONTAL ? go.Spot.Bottom : go.Spot.Right,
desiredSize: HORIZONTAL ? new go.Size(50, 7) : new go.Size(7, 50),
fill: "lightblue", stroke: "dodgerblue",
cursor: HORIZONTAL ? "row-resize" : "col-resize"
})
);
myDiagram.linkTemplate =
$(go.Link,
{ routing: go.Link.AvoidsNodes, corner: 5 },
{ relinkableFrom: true, relinkableTo: true },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
{
mouseDrop: function (e, link) { // dropping a copy of some Nodes and Links onto this Link adds them to this Link's Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var grp = link.containingGroup;
if (grp !== null) {
var ok = grp.addMembers(link.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
layoutConditions: go.Part.LayoutAdded
}
);
// define some sample graphs in some of the lanes
myDiagram.model = new go.GraphLinksModel(
[ // node data
{ key: "Lane1", isGroup: true, color: "lightblue" },
{ key: "Lane2", isGroup: true, color: "lightgreen" },
{ key: "Lane3", isGroup: true, color: "lightyellow" },
{ key: "Lane4", isGroup: true, color: "orange" },
{ key: "oneA", group: "Lane1" },
{ key: "oneB", group: "Lane1" },
{ key: "oneC", group: "Lane1" },
{ key: "oneD", group: "Lane1" },
{ key: "twoA", group: "Lane2" },
{ key: "twoB", group: "Lane2" },
{ key: "twoC", group: "Lane2" },
{ key: "twoD", group: "Lane2" },
{ key: "twoE", group: "Lane2" },
{ key: "twoF", group: "Lane2" },
{ key: "twoG", group: "Lane2" },
{ key: "fourA", group: "Lane4" },
{ key: "fourB", group: "Lane4" },
{ key: "fourC", group: "Lane4" },
{ key: "fourD", group: "Lane4" },
],
[ // link data
{ from: "oneA", to: "oneB" },
{ from: "oneA", to: "oneC" },
{ from: "oneB", to: "oneD" },
{ from: "oneC", to: "oneD" },
{ from: "twoA", to: "twoB" },
{ from: "twoA", to: "twoC" },
{ from: "twoA", to: "twoF" },
{ from: "twoB", to: "twoD" },
{ from: "twoC", to: "twoD" },
{ from: "twoD", to: "twoG" },
{ from: "twoE", to: "twoG" },
{ from: "twoF", to: "twoG" },
{ from: "fourA", to: "fourB" },
{ from: "fourB", to: "fourC" },
{ from: "fourC", to: "fourD" }
]);
myDiagram.model.undoManager.isEnabled = true;
}
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagram" style="border: solid 1px blue; width:100%; height:600px;"></div>
</div>
</body>
</html>
I've tried setting
var HORIZONTAL = true;
to
var HORIZONTAL = false;
I may have the correct orientation though, but the flow is changed. I wonder if the column could just be horizontal and the flow runs like the top-level view.

The "flow" is changed because setting HORIZONTAL = false also changes the Group's layout that it users for its members:
myDiagram.groupTemplate =
$(go.Group, HORIZONTAL ? "Horizontal" : "Vertical",
{
...
layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{ direction: HORIZONTAL ? 0 : 90, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }),
So you're going to have to modify that direction: HORIZONTAL ? 0 : 90 to your liking if you don't want it to change when HORIZONTAL does.
It is not possible to add HTML attributes to each node, but you can store any arbitrary data for each node in the node data itself.

Related

Cesium polygon callback using javascript

I am trying to edit the change the edit or redraw the polygon. this is my code.
Error
An error occurred while rendering. Rendering has stopped.
TypeError: this._callback is not a function
TypeError: this._callback is not a function
using pickedObject.id i got the exect polygon i want to reposition, but call back issue.
var points = [-95.8079865631313, 30.24038650541154, -
60.10509002138564, 23.526593580490083, -59.06372427570612, 2.245934026097194, -
117.00668212362282, 3.938434130034481
];
function loadPoly(points) {
redPolygon = viewer.entities.add({
id: "myArray",
name: "myArray",
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray(points),
material: Cesium.Color.fromBytes(221, 240, 235, 160)
}
});
polygonCollection.push(redPolygon);
adding_billboard(-95.8079865631313, 30.24038650541154, "A", "-95.8079865631313, 30.24038650541154");
adding_billboard(-60.10509002138564, 23.526593580490083, "A", "-60.10509002138564, 23.526593580490083");
adding_billboard(-59.06372427570612, 2.245934026097194, "A", "-59.06372427570612, 2.245934026097194");
adding_billboard(-117.00668212362282, 3.938434130034481, "A", "-117.00668212362282, 3.938434130034481");
viewer.flyTo(redPolygon);
}
function adding_billboard(lon, lat, name, popup) {
var entity = viewer.entities.add({
name: name,
position: Cesium.Cartesian3.fromDegrees(lon, lat, 2000),
billboard: {
image: 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-512.png',
show: true, // default
pixelOffset: new Cesium.Cartesian2(0, -20), // default: (0, 0)
eyeOffset: new Cesium.Cartesian3(0.0, 0.0, 0.0), // default
horizontalOrigin: Cesium.HorizontalOrigin.bottom, // default
alignedAxis: Cesium.Cartesian3.ZERO, // default
width: 20, // default: undefined
height: 25, // default: undefined
//disableDepthTestDistance: Number.POSITIVE_INFINITY, // draws the label in front of terrain
// on ground show
},
label: {
text: popup,
font: "7pt sans-serif",
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
verticalOrigin: Cesium.VerticalOrigin.BASELINE,
fillColor: Cesium.Color.BLACK,
showBackground: true,
backgroundColor: new Cesium.Color(1, 1, 1, 0.7),
backgroundPadding: new Cesium.Cartesian2(8, 4),
disableDepthTestDistance: Number
.POSITIVE_INFINITY, // draws the label in front of terrain
},
});
pointsCollection.push(entity);
}
var coordinates = [76.82071632075994, 33.4134542888633, 77.83750798568438, 33.39276536442791, 77.32892923803021,
32.93547457354476
];
var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction(function(click) {
var pickedObject = scene.pick(click.position);
if (Cesium.defined(pickedObject)) {
console.log("Second ");
console.log("pickedObject.id.id ", pickedObject.id.id);
console.log("pickedObject.id.name ", pickedObject.id.name);
console.log("pickedObject.id..polygon.hierarchy ", pickedObject.id.polygon.hierarchy.valueOf());
var data = pickedObject.id.polygon.hierarchy.valueOf();
console.log("data ", data.positions.valueOf());
pickedObject.id.polygon = {
hierarchy: new Cesium.CallbackProperty(new Cesium.Cartesian3.fromDegreesArray(coordinates),
false),
material: Cesium.Color.fromBytes(221, 240, 235, 160)
//pickedObject.id = redPolygon;// tried this but dailed due to same id then i removed it.
if (pickedObject.id.name == 'C') {
// $('#modal-activity').modal('show');
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
i want to shift polygon to some other coordiante but unable to use call back properly can some one guild me how can I do ?
i did some google searches which are given below but my issues not solved.
here i was trying to make polygon points dynamic but line disappers in tarrains. fist time it was ok after moving point it disappers.
https://sandcastle.cesium.com/?#c=zVTRbpswFP2VK14gEjMhWTaJ0Whd0odpnVpl0vZQ+uDCTWvN2Mg2yVjVf5/BkKRZ1VV9mnjhHp9zfH2uYUMVbBhuUcEJCNzCAjWrS/K9wwI/78qFFIYygcoffchE+2ysDoVhpomt0DmQDmCoCS2K4D4TAKxIwP+8jP2wrQQtESywwgLOOGeVRpACdK3WNEfHqaS2HlIkMLSyoMrYNyqmZK1kucRbhaiDN3E8I+MQpu/JeNRLmTAJdBvbiv1C/o39xgSm49BhueRSJTvjtiKrs2W/KmvD7SEXT5A+nZ8uvjym/WCFuUsgJrMWfsjEg8tmn8zkBclMXpPMP4N52wXz7v8Lpl1/LpVK8qbl7JvtD62Tw9t52aOXSlaoTHOqFG2CKyeBQ+YK16hQ5DhQg6O9w/5+whX4w2Y+XMMofLXb5Am3wex677t1Oc12QEkNKkb50VFdIhcu3K89Z9fA/aB+foQvHuLxGCdujHaQo8NxeqGXatNwnLvVj6yspDJQKx4QEhksK25b1dFNnf9EQ3KtW1kaDaK0YBv7EZxk3tEvJvMg51Rru7KueXdRM2+eRpb/SMYlLZi4vdig4rRpKXfx/NyBhJA0suXfKiMlv6HqwPEP
second :Dynamically change polygon position in cesium
it also gives error because i don't know where to use callback.
please guild me. thank you.
Here's Sandcastle link.
const {
Cartesian3,
CallbackProperty,
Color,
defined,
ScreenSpaceEventHandler,
ScreenSpaceEventType,
Viewer
} = window.Cesium;
const viewer = new Viewer("cesiumContainer");
let redPolygon;
const polygonCollection = [];
const pointsCollection = [];
const polygonId = "myArray";
let polygonPoints = [
-95.8079865631313, 30.24038650541154, -60.10509002138564, 23.526593580490083, -59.06372427570612,
2.245934026097194, -117.00668212362282, 3.938434130034481
];
function loadPoly() {
redPolygon = viewer.entities.add({
id: polygonId,
name: "myArray",
polygon: {
hierarchy: new CallbackProperty(() => {
return {
positions: Cartesian3.fromDegreesArray(polygonPoints)
};
}, false),
material: Color.fromBytes(221, 240, 235, 160)
}
});
polygonCollection.push(redPolygon);
adding_billboard(-95.8079865631313, 30.24038650541154, "A", "-95.8079865631313, 30.24038650541154");
adding_billboard(-60.10509002138564, 23.526593580490083, "A", "-60.10509002138564, 23.526593580490083");
adding_billboard(-59.06372427570612, 2.245934026097194, "A", "-59.06372427570612, 2.245934026097194");
adding_billboard(-117.00668212362282, 3.938434130034481, "A", "-117.00668212362282, 3.938434130034481");
viewer.flyTo(redPolygon);
}
function adding_billboard(lon, lat, name, popup) {
const entity = viewer.entities.add({
name: name,
position: Cartesian3.fromDegrees(lon, lat, 2000),
billboard: {
image: "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-512.png",
show: true, // default
pixelOffset: new Cesium.Cartesian2(0, -20), // default: (0, 0)
eyeOffset: new Cartesian3(0.0, 0.0, 0.0), // default
horizontalOrigin: Cesium.HorizontalOrigin.bottom, // default
alignedAxis: Cesium.Cartesian3.ZERO, // default
width: 20, // default: undefined
height: 25 // default: undefined
//disableDepthTestDistance: Number.POSITIVE_INFINITY, // draws the label in front of terrain
// on ground show
},
label: {
text: popup,
font: "7pt sans-serif",
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
verticalOrigin: Cesium.VerticalOrigin.BASELINE,
fillColor: Color.BLACK,
showBackground: true,
backgroundColor: new Color(1, 1, 1, 0.7),
backgroundPadding: new Cesium.Cartesian2(8, 4),
disableDepthTestDistance: Number.POSITIVE_INFINITY // draws the label in front of terrain
}
});
pointsCollection.push(entity);
}
loadPoly();
const scene = viewer.scene;
const handler = new ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction(function (click) {
const pickedObject = scene.pick(click.position);
if (defined(pickedObject) && pickedObject.id && pickedObject.id.id === polygonId) {
const newPolygonPoints = [
76.82071632075994, 33.4134542888633, 77.83750798568438, 33.39276536442791, 77.32892923803021,
32.93547457354476
];
polygonPoints = newPolygonPoints;
viewer.camera.flyTo({
destination: Cartesian3.fromDegrees(76.82071632075994, 33.4134542888633, 1000)
});
}
}, ScreenSpaceEventType.LEFT_CLICK);

GoJS Pipe Example Add TextBlock

It is not apparent to me how to edit the GoJS Pipes example here so that I can display text within the "pipes" without messing up the layout. I am trying to apply the answer to the same question given here but it's so old that it could be out of date, or maybe it's just not explicit enough for me to make sense of it properly with my limited knowledge of this lib.
I have started a hardcoded pipeline that looks like so:
The pipeline code:
const base = 15;
const minLen = base * 2;
const maxLen = base * 6;
const minInc1 = minLen + base;
const minInc2 = minLen * 2;
const minInc3 = minLen + (base * 3);
interface IShapeParams {
angle?: number
text?: string
key: number
}
const createShapeI = (params: IShapeParams): ObjectData => ({
geo: `F1 M0 0 L${minLen} 0 ${minLen} ${maxLen} 0 ${maxLen}z`,
ports: [
{ id: "U1", spot: "0.5 0 0 0.5" },
{ id: "U2", spot: "0.5 1 0 -0.5" }
],
...params
});
const startHorz = 0;
const startVert = 0;
export const pipeline = {
"class": "go.GraphLinksModel",
"copiesArrays": true,
"copiesArrayObjects": true,
"linkFromPortIdProperty": "fid",
"linkToPortIdProperty": "tid",
"nodeDataArray": [
{
...createShapeI({ key: 1, text: "Pipe 1", angle: 90 }),
"loc": `${startHorz} ${startVert}`
},
{
...createShapeI({ key: 2, text: "Pipe 2", angle: 90 }),
"loc": `${startHorz - maxLen} ${startVert}`
}
],
"linkDataArray": [
{ "from": 1, "to": 2, "fid": "U2", "tid": "U1" }
]
};
The original nodeTemplate from the Pipe source code:
myDiagram.nodeTemplate =
$(go.Node, "Spot",
{
locationObjectName: "SHAPE",
locationSpot: go.Spot.Center,
selectionAdorned: false, // use a Binding on the Shape.stroke to show selection
itemTemplate:
// each port is an "X" shape whose alignment spot and port ID are given by the item data
$(go.Panel,
new go.Binding("portId", "id"),
new go.Binding("alignment", "spot", go.Spot.parse),
$(go.Shape, "XLine",
{ width: 6, height: 6, background: "transparent", fill: null, stroke: "gray" },
new go.Binding("figure", "id", portFigure), // portFigure converter is defined below
new go.Binding("angle", "angle"))
),
// hide a port when it is connected
linkConnected: (node, link, port) => {
if (link.category === "") {
port.visible = false;
}
},
linkDisconnected: (node, link, port) => {
if (link.category === "") {
port.visible = true;
}
}
},
// this creates the variable number of ports for this Spot Panel, based on the data
new go.Binding("itemArray", "ports"),
// remember the location of this Node
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// remember the angle of this Node
new go.Binding("angle", "angle").makeTwoWay(),
// move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts
new go.Binding("layerName", "isSelected", function(s) {
return s ? "Foreground" : "";
}).ofObject(),
$(go.Shape,
{
name: "SHAPE",
// the following are default values;
// actual values may come from the node data object via data binding
geometryString: "F1 M0 0 L20 0 20 20 0 20 z",
fill: "rgba(128, 128, 128, 0.5)"
},
// this determines the actual shape of the Shape
new go.Binding("geometryString", "geo"),
// selection causes the stroke to be blue instead of black
new go.Binding("stroke", "isSelected", (s) => {
return s ? "dodgerblue" : "black";
}).ofObject())
);
Now when I try to apply the solution given on the referenced thread I end up with the nodeTemplate like so:
myDiagram.nodeTemplate =
$(go.Node, "Spot",
{
locationObjectName: "SHAPE",
locationSpot: go.Spot.Center,
selectionAdorned: false, // use a Binding on the Shape.stroke to show selection
// hide a port when it is connected
linkConnected: (node, link, port) => {
if (link.category === "") {
port.visible = false;
}
},
linkDisconnected: (node, link, port) => {
if (link.category === "") {
port.visible = true;
}
}
},
$(go.Panel, "Spot",
{
itemTemplate:
// each port is an "X" shape whose alignment spot and port ID are given by the item data
$(go.Panel,
new go.Binding("portId", "id"),
new go.Binding("alignment", "spot", go.Spot.parse),
$(go.Shape, "XLine",
{ width: 6, height: 6, background: "transparent", fill: null, stroke: "gray" },
new go.Binding("figure", "id", portFigure), // portFigure converter is defined below
new go.Binding("angle", "angle"))
)
},
// this creates the variable number of ports for this Spot Panel, based on the data
new go.Binding("itemArray", "ports"),
// remember the location of this Node
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// remember the angle of this Node
new go.Binding("angle", "angle").makeTwoWay(),
// move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts
new go.Binding("layerName", "isSelected", function(s) {
return s ? "Foreground" : "";
}).ofObject(),
$(go.Shape,
{
name: "SHAPE",
// the following are default values;
// actual values may come from the node data object via data binding
geometryString: "F1 M0 0 L20 0 20 20 0 20 z",
fill: "rgba(128, 128, 128, 0.5)"
},
// this determines the actual shape of the Shape
new go.Binding("geometryString", "geo"),
// selection causes the stroke to be blue instead of black
new go.Binding("stroke", "isSelected", (s) => {
return s ? "dodgerblue" : "black";
}).ofObject())
),
$(go.TextBlock, { margin: 5 }, new go.Binding("text", "text"))
);
This renders the text correctly, but it completely breaks the layout as the ports have switched sides, I have no idea why. On top of that, the space seen in between the pipes now I cannot reconcile at all by trying to alter the loc strings on the pipeline, in fact nothing I do to the loc properties does anything at all now. See below image:
How can I just add text blocks to these shapes while keeping the same layout and functionality?
So the pipe template is currently structured like this:
Node (that is a Spot Panel)
- 1st child: Shape
- 2nd-nth child: Item array of x's
Spot panels have one main element (usually the first child unless you specify it), and then N other elements that are positioned based on it. So at the very minimum you want a structure like this:
Node (that is a Spot Panel)
- 1st child: Panel (another Spot panel)
- Shape
- TextBlock
- 2nd-nth child: Item array of x's
So you are swapping out the Shape for a Panel (a container with lots of stuff) that will contain that shape. This will work as long as that Panel is exactly the same size as the shape it would be replacing. If its larger, for instance if the text is larger than the shape, then you're in trouble.
How to solve that problem really depends on what you want the result to look like. The simplest way would be to force the TextBlock to stretch: go.GraphObject.Fill, so that it always sizes itself identical to the shape, so that the Panel (Shape+TextBlock) is always identical in size to the Shape it replaced.
Separately you probably want the TextBlock, which will assume the entire area of the gray shape, to be centered vertically, so you'd want to add verticalAlignment: go.Spot.Center
Like this:
myDiagram.nodeTemplate =
$(go.Node, "Spot",
{
locationObjectName: "SHAPE",
locationSpot: go.Spot.Center,
selectionAdorned: false, // use a Binding on the Shape.stroke to show selection
itemTemplate:
// each port is an "X" shape whose alignment spot and port ID are given by the item data
$(go.Panel,
new go.Binding("portId", "id"),
new go.Binding("alignment", "spot", go.Spot.parse),
$(go.Shape, "XLine",
{ width: 6, height: 6, background: "transparent", fill: null, stroke: "gray" },
new go.Binding("figure", "id", portFigure), // portFigure converter is defined below
new go.Binding("angle", "angle"))
),
// hide a port when it is connected
linkConnected: function(node, link, port) {
if (link.category === "") port.visible = false;
},
linkDisconnected: function(node, link, port) {
if (link.category === "") port.visible = true;
}
},
// this creates the variable number of ports for this Spot Panel, based on the data
new go.Binding("itemArray", "ports"),
// remember the location of this Node
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// remember the angle of this Node
new go.Binding("angle", "angle").makeTwoWay(),
// move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts
new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
// Everything except the ports: (the pipe body, and pipe text)
$(go.Panel, "Spot",
$(go.Shape,
{
name: "SHAPE",
// the following are default values;
// actual values may come from the node data object via data binding
geometryString: "F1 M0 0 L20 0 20 20 0 20 z",
fill: "rgba(128, 128, 128, 0.5)"
},
// this determines the actual shape of the Shape
new go.Binding("geometryString", "geo"),
// selection causes the stroke to be blue instead of black
new go.Binding("stroke", "isSelected", function(s) { return s ? "dodgerblue" : "black"; }).ofObject()),
$(go.TextBlock, "Some text")
)
);
Here is that modification live: https://codepen.io/simonsarris/pen/RwrKqrq?editors=1010

Issue in creating ports with GoJS

I am testing the GoJS library for Javascript.
I'm looking to create ports on the sides of a group.
I attach an executable extract from the program.
When I create a new object from the palette, right-click on the object, and select "Add left port", the debugging message shows that the variable "arr" is declared "undefined" at execution. (program lines 111 and 112).
Thank you for your help.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dynamic Ports</title>
<meta name="description" content="Nodes with varying lists of ports on each of four sides." />
<!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="../release/go-debug.js"></script>
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
<!-- LL <script>goCode("diagramEvents", 600, 200)</script>-->
<script src="/Users/leonlevy/gojs/site/extensionsTS/ResizeMultipleScript.js"></script>
<span id="diagramEventsMsg" style="color: red"></span>
<span id="changeMethodsMsg" style="color: red"></span>
<span id="BackgroundDoubleClicked" style="color: red"></span>
<p>
<script id="code">
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make; //for conciseness in defining node templates
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
allowDrop: true, // from Palette
// what to do when a drag-drop occurs in the Diagram's background
mouseDrop: function(e) { finishDrop(e, null); },
// layout: null, // Diagram has no layout
/* $(go.GridLayout,
{ wrappingWidth: Infinity, alignment: go.GridLayout.Position, cellSize: new go.Size(1, 1) }),*/
initialContentAlignment: go.Spot.Center,
"commandHandler.archetypeGroupData": { isGroup: true, category: "OfGroups" },
"undoManager.isEnabled": true
});
// LL
function showMessage(s) {
document.getElementById("diagramEventsMsg").textContent = s;
}
// 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);
}
});
myDiagram.addDiagramListener("ObjectSingleClicked",
function(e) {
var part = e.subject.part;
var heightPart = part.actualBounds.height;
var widthPart = part.actualBounds.width;
var xPart = part.actualBounds.position.x;
var yPart = part.actualBounds.position.y;
var xMouseClic = myDiagram.lastInput.documentPoint.x;
var yMouseClic = myDiagram.lastInput.documentPoint.y;
var xPartMiddle = xPart+ widthPart/2;
var side = xMouseClic<= xPartMiddle ? "Left" : "Right";
if (!(part instanceof go.Link)) {console.log("hello leon");}
});
myDiagram.addDiagramListener("BackgroundSingleClicked",
function(e) {
showMessage("Clicked on background " + myDiagram.lastInput.documentPoint);
//console.log("hello leon");}
});
myDiagram.addDiagramListener("BackgroundDoubleClicked", // LL KO
function(e) {
showMessage("Double-clicked at " );//+ e.myDiagram.lastInput.documentPoint);
//showMessage("Clicked on " + myDiagram.lastInput.documentPoint);
});
myDiagram.addDiagramListener("ClipboardPasted",
function(e) { showMessage("Pasted " + e.myDiagram.selection.count + " parts"); });
// LL end
// 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 },
//{showMessage( "button: " + myDiagram.lastInput.button);
// don't bother with binding GraphObject.visible if there's no predicate
visiblePredicate ? new go.Binding("visible", "", function(o, e) { return o.myDiagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
//}
}
var nodeMenu = // context menu for each Node
$(go.Adornment, "Horizontal",
makeButton("Add left port",
function (e, obj) { addPort("left"); }),
makeButton("Add right port",
function (e, obj) { addPort("right"); }),
); //Leon end nodeM
// 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"];
showMessage ("node: " + node + "; name: " + name + ";arr: " + arr + ";node.data: " + node.data);
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");
}
var portSize = new go.Size(8, 8);
// this function is used to highlight a Group that the selection may be dropped into
function highlightGroup(e, grp, show) {
if (!grp) return; showMessage("ok")
e.handled = true;
if (show) {
// cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
// instead depend on the DraggingTool.draggedParts or .copiedParts
var tool = grp.diagram.toolManager.draggingTool;
var map = tool.draggedParts || tool.copiedParts; // this is a Map
// now we can check to see if the Group will accept membership of the dragged Parts
if (grp.canAddMembers(map.toKeySet())) {
grp.isHighlighted = true;
return;
}
}
grp.isHighlighted = false;
}
// Upon a drop onto a Group, we try to add the selection as members of the Group.
// Upon a drop onto the background, or onto a top-level Node, make selection top-level.
// If this is OK, we're done; otherwise we cancel the operation to rollback everything.
function finishDrop(e, grp) {
var ok = (grp !== null
? grp.addMembers(grp.diagram.selection, true)
: e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
if (!ok) e.diagram.currentTool.doCancel();
}
var portMenu = // context menu for each port
$(go.Adornment, "Vertical",
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); })
);
// 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.groupTemplateMap.add("OfGroups",
$(go.Group, "Table",
{ locationObjectName: "BODY",
locationSpot: go.Spot.Center,
selectionObjectName: "BODY",
contextMenu: nodeMenu
},
{
background: "transparent",
resizable: true,
// highlight when dragging into the Group
mouseDragEnter: function(e, grp, prev) { highlightGroup(e, grp, true); },
mouseDragLeave: function(e, grp, next) { highlightGroup(e, grp, false); },
//computesBoundsAfterDrag: true,
mouseDrop: finishDrop,
resizable: true, resizeObjectName: "SHAPE"
//, minSize: new go.Size(36, 46)
},
new go.Binding("background", "isHighlighted", function(h) { return h ? "rgba(0,0,255,0.)" : "transparent"; }).ofObject(),
// save the modified size in the model node data
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Rectangle",
{ fill: null, stroke: "rgba(0,50,255,1)", strokeWidth: 3, name: "SHAPE"}),
$(go.Panel, "Vertical", {alignment: go.Spot.Top},// title above Placeholder
$(go.Panel, "Horizontal", // button next to TextBlock
{ stretch: go.GraphObject.Horizontal, background: "rgba(0,0,255,0.1)" },
$("SubGraphExpanderButton",
{ alignment: go.Spot.Right, margin: 5 }),
$(go.TextBlock,
{ //text: "verticalAlignment: Top", verticalAlignment: go.Spot.Top,
alignment: go.Spot.Left,
editable: true,
margin: 5,
font: "bold 18px sans-serif",
opacity: 0.75,
stroke: "#404040"
},
new go.Binding("text", "text").makeTwoWay())
), // end Horizontal Panel
$(go.Placeholder,
{ padding: 5, alignment: go.Spot.TopLeft })
), // end Vertical Panel
// 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 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
)
); // end groupTemplateMap.add("OfGroups"
// initialize the Palette and its contents
myPalette =
$(go.Palette, "myPaletteDiv",
{
groupTemplateMap: myDiagram.groupTemplateMap,
layout: $(go.GridLayout, { wrappingColumn: 1, alignment: go.GridLayout.Position })
});
myPalette.nodeTemplate =
$(go.Node, "Horizontal",
$(go.Shape,
{ width: 10, height: 10, fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
new go.Binding("text", "color"))
);
myPalette.model = new go.GraphLinksModel([
{ key: 101, text: 'UNIT', isGroup: true, isSubProcess: true,
category: "OfGroups", isAdHoc: true, loc: '0 0' },
]);
}
</script>
</head>
<body onload="init()">
<!-- LL -->
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<div id="sample">
<div style="width: 100%; display: flex; justify-content: space-between">
<div id="myPaletteDiv" style="width: 100px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div>
<div id="myDiagramDiv" style="flex-grow: 1; height: 480px; border: solid 1px black"></div>
</div>
<p>
See the Ports Intro page for an explanation of GoJS ports.
</p>
<textarea id="mySavedModel" style="width:100%;height:250px">
</textarea>
</div>
</div>
</body>
</html>
You need to make sure that the node is bound to the correct data. In particular, the data object should have a leftArray property that is an Array holding port descriptor objects. (And it seems to expect a rightArray property too.) You can determine the expected properties for each Node by looking at the source property names of each of the Bindings that are in the node template.
You can determine the expected properties for each of the port descriptors in either data Array by looking at the properties that the Panel.itemTemplate uses in its data Bindings.

I build synchronized charts (highcharts) that I want to export in one PDF

Ok so I build sync charts with test data. Everything works perfectly. The only problem I have is that I want to export all of them in one PDF but I only have the option to do it seperatly. So I build in a button that gets the charts in an array and exports it to PDF but it does not want to work.
This is the JS i have that builds the charts
function clickModal(test) {
document.getElementById("myModal").style.display = "block";
// Call the AJAX function that start the chart procedures
getAjaxData(test);
}
// HIGHCHART FUNCTIONALITY STARTS HERE
['mousemove', 'touchmove', 'touchstart'].forEach(function (eventType) {
document.getElementById('container').addEventListener(
eventType,
function (e) {
var chart,
point,
i,
event;
for (i = 0; i < Highcharts.charts.length; i = i + 1) {
chart = Highcharts.charts[i];
// Find coordinates within the chart
event = chart.pointer.normalize(e);
// Get the hovered point
point = chart.series[0].searchPoint(event, true);
if (point) {
point.highlight(e);
}
}
}
);
});
/**
* Override the reset function, we don't need to hide the tooltips and
* crosshairs.
*/
Highcharts.Pointer.prototype.reset = function () {
return undefined;
};
/**
* Highlight a point by showing tooltip, setting hover state and draw crosshair
*/
Highcharts.Point.prototype.highlight = function (event) {
event = this.series.chart.pointer.normalize(event);
this.onMouseOver(); // Show the hover marker
this.series.chart.tooltip.refresh(this); // Show the tooltip
this.series.chart.xAxis[0].drawCrosshair(event, this); // Show the crosshair
};
/**
* Synchronize zooming through the setExtremes event handler.
*/
function syncExtremes(e) {
var thisChart = this.chart;
if (e.trigger !== 'syncExtremes') { // Prevent feedback loop
Highcharts.each(Highcharts.charts, function (chart) {
if (chart !== thisChart) {
if (chart.xAxis[0].setExtremes) { // It is null while updating
chart.xAxis[0].setExtremes(
e.min,
e.max,
undefined,
false,
{ trigger: 'syncExtremes' }
);
}
}
});
}
}
function getAjaxData(test){
$.getJSON('datasync.php', function(chartData, tstSuccess) {
// Display the results of the getJSON call to data.php
document.getElementById("json1").innerHTML = JSON.stringify(tstSuccess, undefined, 2);
document.getElementById("json").innerHTML = JSON.stringify(chartData, undefined, 2);
var charts1 = {}; // global variable
var index = 0;
// Loop through each dataset in the returned JSON
chartData.datasets.forEach(function (dataset, i) {
// Add X values
dataset.data = Highcharts.map(dataset.data, function (val, j) {
return [chartData.xData[j], val];
});
// Create a child div for each dataset that is returned
var chartDiv = document.createElement('div');
chartDiv.className = 'chart';
document.getElementById('container').appendChild(chartDiv);
charts1[index] = Highcharts.chart(chartDiv, {
chart: {
marginLeft: 40, // Keep all charts left aligned
spacingTop: 20,
spacingBottom: 20
},
exporting: {
buttons: {
contextButton: {
menuItems: [
'downloadPDF',
'viewData'
]
}
}
},
title: {
text: dataset.name,
align: 'left',
margin: 0,
x: 30
},
credits: {
enabled: false
},
legend: {
enabled: false
},
xAxis: {
crosshair: true,
events: {
setExtremes: syncExtremes
},
labels: {
//format: '{value} km'
format: ' '
}
},
yAxis: {
title: {
text: null
}
},
tooltip: {
positioner: function () {
return {
// right aligned
x: this.chart.chartWidth - this.label.width - 40,
y: 10 // align to title
};
},
borderWidth: 0,
backgroundColor: 'none',
pointFormat: '{point.y}',
headerFormat: '',
shadow: false,
style: {
fontSize: '18px'
},
valueDecimals: dataset.valueDecimals
},
series: [{
data: dataset.data,
name: dataset.name,
type: dataset.type,
color: Highcharts.getOptions().colors[i],
fillOpacity: 0.3,
tooltip: {
valueSuffix: ' ' + dataset.unit
}
}]
});
});
index++;
});
}
$('#export-pdf').click(function() {
Highcharts.exportCharts([charts1[0], charts1[1]], {
type: 'application/pdf'
});
});
And this is my HTML
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<div id="container">
<button id="export-png">Export to PNG</button>
</div>
</div>
</div>
So im using PHP to build up JSON from an array ( will use SQL to build from database later just want to get the functionality working )
Then I use the highcharts library along with JS to display the charts
Ok so I figured out the problem. I brought in this piece of code
Highcharts.getSVG = function(charts) {
var svgArr = [],
top = 0,
width = 0;
$.each(charts, function(i, chart) {
var svg = chart.getSVG();
svg = svg.replace('<svg', '<g transform="translate(0,' + top + ')" ');
svg = svg.replace('</svg>', '</g>');
svg = svg.replace('-9000000000', '-999'); // Bug in v4.2.6
top += chart.chartHeight;
width = Math.max(width, chart.chartWidth);
svgArr.push(svg);
});
return '<svg height="'+ top +'" width="' + width + '" version="1.1" xmlns="http://www.w3.org/2000/svg">' + svgArr.join('') + '</svg>';
};
/**
* Create a global exportCharts method that takes an array of charts as an argument,
* and exporting options as the second argument
*/
Highcharts.exportCharts = function(charts, options) {
// Merge the options
options = Highcharts.merge(Highcharts.getOptions().exporting, options);
// Post to export server
Highcharts.post(options.url, {
filename: options.filename || 'chart',
type: options.type,
width: options.width,
svg: Highcharts.getSVG(charts)
});
};
I changed the charts so that it does not created an array of chart objects
Highcharts.chart(chartDiv, {
chart: {
marginLeft: 40, // Keep all charts left aligned
spacingTop: 20,
spacingBottom: 20
},
instead of
charts1[index] = Highcharts.chart(chartDiv, {
chart: {
marginLeft: 40, // Keep all charts left aligned
spacingTop: 20,
spacingBottom: 20
},
And in the buttons function I just used the highcharts.charts instead of the array
$('#export-pdf').click(function() {
Highcharts.exportCharts(Highcharts.charts, {
type: 'application/pdf'
});
});

goJS disable adding new nodes when no options in dropdown

I have simple python flask app where I send JSON data to my HTML and with goJS I display my graph which looks like this:
I made custom choices dropdown for users to edit node and link text. Those choice options are read from .txt file and sent to html via flask. Options in dropdown lists are made so that when option is selected once, it can not be selected again, until user delete node or link with that used option, and then he can use that option again. So far, I used this code to make nodes text selectable in dropdown list:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Chart</title>
<meta name="description" content="A finite state machine chart with editable and interactive features." />
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="{{url_for('static', filename='go.js')}}"></script>
<!-- custom text editors -->
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorRadioButtons.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='DataInspector.js')}}"></script>
<link href="https://gojs.net/latest/extensions/DataInspector.css" rel="stylesheet">
<link href="{{url_for('static', filename='DataInspector.css')}}" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script id="code">
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
// start everything in the middle of the viewport
initialContentAlignment: go.Spot.Center,
// have mouse wheel events zoom in and out instead of scroll up and down
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
// support double-click in background creating a new node
"clickCreatingTool.archetypeNodeData": { text: "new node" },
// enable undo & redo
"textEditingTool.defaultTextEditor": window.TextEditorSelectBox,
"undoManager.isEnabled": true,
"layout": new go.ForceDirectedLayout(),
"ModelChanged": function(e) {
console.log("Diagram model changed!");
if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
console.log("eee");
console.log(e);
var linkdata = e.oldValue;
console.log("linkdata");
console.log(linkdata);
var oldstr = linkdata.text;
console.log("oldstr");
console.log(oldstr);
if (!oldstr) return;
var choices = e.model.modelData.choices;
console.log("choices");
console.log(choices);
var idx = choices.indexOf(oldstr);
if (idx < 0) {
console.log("adding choice: " + oldstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.push(oldstr);
e.model.set(e.model.modelData, "choices", newchoices);
}
}
}
});
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log("myDiagram.model.modelData.choices");
console.log(myDiagram.model.modelData.choices);
// 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);
}
});
myDiagram.addDiagramListener("textEdited", function(e) {
console.log("Text is edited");
console.log(e);
//CHECK IF LINK,
//IF YES REMOVE THAT OPTION FROM LIST
});
myDiagram.addDiagramListener("SelectionDeleting", function(e) {
console.log("inside SelectionDeleting");
console.log(e);
//CHECK IF LINK,
//IF YES PUT THAT OPTION BACK IN OPTION LIST
});
// define the Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// define the node's outer shape, which will surround the TextBlock
$(go.Shape, "RoundedRectangle",
{
parameter1: 20, // the corner has a large radius
fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
stroke: null,
portId: "", // this Shape is the Node's port, not the whole Node
fromLinkable: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock,
{
font: "bold 11pt helvetica, bold arial, sans-serif",
editable: true, // editing the text automatically updates the model data
//textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
// this specific TextBlock has its own choices:
textEditor: window.TextEditorRadioButtons,
//choices: JSON.parse('{{ choices | tojson | safe}}')
choices: nodeChoices
},
new go.Binding("text").makeTwoWay())
);
myDiagram.nodeTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
$(go.Placeholder)
),
$(go.Panel, "Horizontal",
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
$("Button",
{ click: editText }, // defined below, to support editing the text of the node
$(go.TextBlock, "t",
{ font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
),
$("Button",
{ // drawLink is defined below, to support interactively drawing new links
click: drawLink, // click on Button and then click on target node
actionMove: drawLink // drag from Button to the target node
},
$(go.Shape,
{ geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
),
$("Button",
{
actionMove: dragNewNode, // defined below, to support dragging from the button
_dragData: { text: "?????", color: "lightgray" }, // node data to copy
click: clickNewNode // defined below, to support a click on the button
},
$(go.Shape,
{ geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
)
)
);
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log(myDiagram.model.modelData.choices);
function editText(e, button) {
//console.log(e);
var node = button.part.adornedPart;
console.log("node");
//console.log(node);
e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
//$("#nodeText").val(node.findObject("TEXTBLOCK"));
}
function drawLink(e, button) {
var node = button.part.adornedPart;
var tool = e.diagram.toolManager.linkingTool;
tool.startObject = node.port;
e.diagram.currentTool = tool;
tool.doActivate();
}
// used by both clickNewNode and dragNewNode to create a node and a link
// from a given node to the new node
function createNodeAndLink(data, fromnode) {
var diagram = fromnode.diagram;
var model = diagram.model;
var nodedata = model.copyNodeData(data);
model.addNodeData(nodedata);
var newnode = diagram.findNodeForData(nodedata);
var linkdata = model.copyLinkData({});
model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
model.addLinkData(linkdata);
diagram.select(newnode);
return newnode;
}
// the Button.click event handler, called when the user clicks the "N" button
function clickNewNode(e, button) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("Create Node and Link");
var fromnode = button.part.adornedPart;
var newnode = createNodeAndLink(button._dragData, fromnode);
newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
e.diagram.commitTransaction("Create Node and Link");
}
// the Button.actionMove event handler, called when the user drags within the "N" button
function dragNewNode(e, button) {
var tool = e.diagram.toolManager.draggingTool;
if (tool.isBeyondDragSize()) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("button drag"); // see doDeactivate, below
var newnode = createNodeAndLink(data, button.part.adornedPart);
newnode.location = e.diagram.lastInput.documentPoint;
// don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
// set tool.currentPart to a selected movable Part and then activate the DraggingTool
tool.currentPart = newnode;
e.diagram.currentTool = tool;
tool.doActivate();
}
}
// using dragNewNode also requires modifying the standard DraggingTool so that it
// only calls commitTransaction when dragNewNode started a "button drag" transaction;
// do this by overriding DraggingTool.doDeactivate:
var tool = myDiagram.toolManager.draggingTool;
tool.doDeactivate = function() {
// commit "button drag" transaction, if it is ongoing; see dragNewNode, above
if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
tool.diagram.commitTransaction();
}
go.DraggingTool.prototype.doDeactivate.call(tool); // call the base method
};
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
$(go.Link, // the whole link panel
{
curve: go.Link.Bezier,
adjusting: go.Link.Stretch,
reshapable: true,
relinkableFrom: true,
relinkableTo: true,
toShortLength: 3
},
new go.Binding("points").makeTwoWay(),
new go.Binding("curviness"),
$(go.Shape, // the link shape
{ strokeWidth: 1.5 }),
$(go.Shape, // the arrowhead
{ toArrow: "standard", stroke: null }),
$(go.Panel, "Auto",
$(go.Shape, // the label background, which becomes transparent around the edges
{
fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
stroke: null
}),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
var choices = tb.diagram.model.modelData.choices;
console.log("choices");
console.log(choices);
console.log("newstr");
console.log(newstr);
console.log("oldstr");
console.log(oldstr);
var idx = choices.indexOf(newstr);
if (idx >= 0 && oldstr !== newstr) {
console.log(choices);
console.log("choices");
console.log(choices);
console.log("removing choice " + idx + ": " + newstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.splice(idx, 1);
tb.diagram.model.set(tb.diagram.model.modelData, "choices", newchoices);
tb.editable = false; // don't allow choice again
}
}
},
// editing the text automatically updates the model data
//new go.Binding("text"),
new go.Binding("text").makeTwoWay(),
new go.Binding("choices").ofModel())
)
);
var inspector = new Inspector('myInspectorDiv', myDiagram,
{
// uncomment this line to only inspect the named properties below instead of all properties on each object:
// includesOwnProperties: false,
properties: {
"text": { },
// an example of specifying the type
"password": { show: Inspector.showIfPresent, type: 'password' },
// key would be automatically added for nodes, but we want to declare it read-only also:
"key": { readOnly: true, show: Inspector.showIfPresent },
// color would be automatically added for nodes, but we want to declare it a color also:
"color": { show: Inspector.showIfPresent, type: 'color' },
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
"Comments": { show: Inspector.showIfNode },
"flag": { show: Inspector.showIfNode, type: 'checkbox' },
"LinkComments": { show: Inspector.showIfLink },
"isGroup": { readOnly: true, show: Inspector.showIfPresent }
}
});
// read in the JSON data from flask
loadGraphData();
}
function loadGraphData() {
var graphDataString = JSON.parse('{{ diagramData | tojson | safe}}');
//console.log("graphDataString");
//console.log(graphDataString);
myDiagram.model = go.Model.fromJson(graphDataString);
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
console.log("whole model");
console.log(myDiagram.model);
}
function saveGraphData(form, event) {
console.log("inside saveGraphData");
event.preventDefault();
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
form.submit();
}
function zoomToFit(){
console.log("inside zoomToFit");
myDiagram.zoomToRect(myDiagram.documentBounds);
}
function zoomIn(){
console.log("inside zoomIn");
myDiagram.commandHandler.increaseZoom();
}
function zoomOut(){
console.log("inside zoomOut");
myDiagram.commandHandler.decreaseZoom();
}
</script>
</head>
<body onload="init()">
<div id=formWrapper style="padding: 30px;">
<form method="POST" action="http://localhost:5000/updateResultFile" name="updateResultFileForm"
id="updateResultFileForm"
onsubmit="saveGraphData(this, event);">
<div id="graphWrapper" style="margin-bottom: 15px;">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 800px;margin-bottom: 15px;"></div>
<div style="display: none;"><input id="mySavedModel" name="mySavedModel"></div>
<button class="btn btn-default" type="submit"> Save <i class="fa fa-save"> </i> </button>
</div>
</form>
<div id="myInspectorDiv">
</div>
<div>
<button class="btn btn-default" onclick="zoomToFit()"> Zoom to fit <i class="fa fa-search"> </i> </button>
<button class="btn btn-default" onclick="zoomIn()"> Zoom in <i class="fa fa-search-plus"> </i> </button>
<button class="btn btn-default" onclick="zoomOut()"> Zoom out <i class="fa fa-search-minus"> </i> </button>
</div>
</div>
</body>
</html>
Now I want to make when the user used all options for links and none of them is available to use, I would like to disable availability to add a new link. Also if he uses all options for node texts, I would like to disable the possibility to add new nodes. But, if the user deletes some node and that option for node text is free again, I would like to enable him the possibility to add new nodes with that option but I don't have any idea how to make diagram disable his core part to add new nodes or links?
Also, it is somehow hard to make double click on link text to make dropdown with options appear. Is there any trick or option to make it easier to use?
I didn't realize you had asked this so many times.
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
initialContentAlignment: go.Spot.Center,
"undoManager.isEnabled": true,
"ModelChanged": function(e) { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
if (e.change === go.ChangedEvent.Remove) {
if (e.modelChange === "linkDataArray") {
var linkdata = e.oldValue;
var oldstr = linkdata.text;
if (!oldstr) return;
var linkchoices = e.model.modelData.linkchoices;
var idx = linkchoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding link choice: " + oldstr);
var newlinkchoices = Array.prototype.slice.call(linkchoices);
newlinkchoices.push(oldstr);
e.model.set(e.model.modelData, "linkchoices", newlinkchoices);
}
} else if (e.modelChange === "nodeDataArray") {
var nodedata = e.oldValue;
var oldstr = nodedata.text;
if (!oldstr) return;
var nodechoices = e.model.modelData.nodechoices;
var idx = nodechoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding node choice: " + oldstr);
var newnodechoices = Array.prototype.slice.call(nodechoices);
newnodechoices.push(oldstr);
e.model.set(e.model.modelData, "nodechoices", newnodechoices);
}
}
}
}
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{
margin: 8,
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var nodechoices = model.modelData.nodechoices;
if (!nodechoices) {
nodechoices = [];
model.set(model.modelData, "nodechoices", nodechoices);
}
var idx = nodechoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing node choice " + idx + ": " + newstr);
model.removeArrayItem(nodechoices, idx);
}
if (oldstr) {
console.log(" adding node choice " + oldstr);
model.addArrayItem(nodechoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "nodechoices").ofModel())
);
myDiagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" }),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var linkchoices = model.modelData.linkchoices;
if (!linkchoices) {
linkchoices = [];
model.set(model.modelData, "linkchoices", linkchoices);
}
var idx = linkchoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing link choice " + idx + ": " + newstr);
model.removeArrayItem(linkchoices, idx);
}
if (oldstr) {
console.log(" adding link choice " + oldstr);
model.addArrayItem(linkchoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "linkchoices").ofModel())
);
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, text: "one" },
{ from: 1, to: 3, text: "two" },
{ from: 3, to: 4, text: "three" }
]);
myDiagram.model.set(myDiagram.model.modelData, "nodechoices", ["Epsilon"]);
myDiagram.model.set(myDiagram.model.modelData, "linkchoices", ["four"]);
}

Categories

Resources