Managing links

Hoping someone can guide me. I’m a new GoJS user. I am experiencing the following behavior with the nodes that I create.

- When I relink a link to the same node but a different port the arrowhead stays the in the same orientation as it was when originally linked (programmatically the first time, interactively on the relink). Is this expected behavior? Is the arrowhead on the link considered the Adornment? If so can I tell it to update it’s adornment via RelinkingTool.updateAdornments? And if so what is the incantation?

- I want to ensure that all link reshaping points have their Reshaping behavior set to LinkReshapingTool.All. I can’t figure out the incantation to set this.

Can I allow my user to interactively move the label on the link around? Is there an example? I’ve looked but couldn’t find it.

Thanks for any help,

John

Here is my node and link templates

<br /> myDiagram.nodeTemplateMap.add("", // the default category<br /> $(go.Node, "Spot", nodeStyle(),<br /> // the main object is a Panel that surrounds a TextBlock with a rectangular Shape<br /> $(go.Panel, "Auto",<br /> $(go.Shape, "Rectangle",<br /> { fill: "#00A9C9", stroke: null },<br /> new go.Binding("figure", "figure")),<br /> $(go.TextBlock,<br /> {<br /> font: "bold 11pt Helvetica, Arial, sans-serif",<br /> stroke: lightText,<br /> margin: 8,<br /> maxSize: new go.Size(160, NaN),<br /> wrap: go.TextBlock.WrapFit,<br /> editable: true<br /> },<br /> new go.Binding("text", "text").makeTwoWay())<br /> ),<br /> // four named ports, one on each side:<br /> makePort("T", go.Spot.Top, true, true),<br /> makePort("L", go.Spot.Left, true, true),<br /> makePort("R", go.Spot.Right, true, true),<br /> makePort("B", go.Spot.Bottom, true, true)<br /> ));<br />
<br /> myDiagram.linkTemplate =<br /> $(go.Link, // the whole link panel<br /> // the link shape<br /> { routing: go.Link.AvoidsNodes, corner: 10, curve: go.Link.JumpOver, reshapable: true, relinkableTo: true, relinkableFrom: true },<br /> $(go.Shape, { isPanelMain: true, stroke: "black" } ),<br /> // the arrowhead<br /> $(go.Shape, { toArrow: "standard", stroke: null } ),<br /> $(go.Panel, "Auto",<br /> $(go.Shape, // the link shape<br /> {<br /> fill: $(go.Brush, go.Brush.Radial, { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),<br /> stroke: null<br /> }),<br /> $(go.TextBlock, // the label<br /> {<br /> textAlign: "center",<br /> font: "10pt helvetica, arial, sans-serif",<br /> stroke: "#555555",<br /> margin: 4<br /> },<br /> new go.Binding("text", "text"))<br /> )<br /> );<br /><br />

No, the arrowhead is just a Shape in the Link panel. It is not an Adornment, which would be a separate Part that depends on the Link. Read more about arrowheads at GoJS Link Labels -- Northwoods Software.

Are you relinking to a port with a different “direction”? In other words, does the other port have a different GraphObject.toSpot? If so, the end segment ought to be going in that direction, which would cause the arrowhead to point in that direction. You can confirm that that can happen by modifying the Dynamic Ports sample, just adding an arrowhead to the link template. But even with the FlowChart sample, which you seem to have started from, reconnecting a link to a port on a different side of the node will cause the end segment and the arrowhead to point in the appropriate (different) direction.

Not all of the reshape handles on a selected orthogonal link can be dragged in all directions. In particular the reshape handles at the ends are constrained to go only in one direction so that the link does not break orthogonality when the user moves the handle. So that behavior is intentional.

If you are looking to allow users to be able to add and remove segments from the reshapable link, try also setting resegmentable: true on the link template. Then the user will see diamond-shaped resegmenting handles in the middle of the segments, which they can drag in any direction. There are no direction constraints because it automatically will create new segments so that orthogonality is maintained.

To support the user’s dragging of link labels, see the State Chart with Draggable Link Labels sample, which makes use of an additional tool in the extensions directory, LinkLabelDraggingTool.js.

Walter,
Thanks for the reply. As far as the relinking goes, yes I am relinking to a port with a different direction. Two things of note when this happens.

1.) As mentioned previous, the arrowhead does not re-orient itself.
2.) When a user re-links to another port on the same node, gojs adds new segments and routes the link in a strange way. The original end segment of the link next to the arrowhead will remain in the same location and new segments will be added to extend the link to the new port. It is quite strange.

As far as the ability to reshape of an orthogonal link, I will play around with Link.Normal for routing to see if it produces the desired behavior.

Again, thanks for the help. I’ll check out the new link label dragging tool to see if that is the behavior that we are looking for. Very cool tool. Easy enough even a non-web programmer ( I’m C / C# / Java ) can pick it up and make sense of it… mostly Tongue

Thanks,

John

“A picture is worth a thousand words.”

Thanks for the compliments. The design has evolved from C++ to Java to C# and now to JavaScript, taking advantage of our learning and the particular features of the languages/platforms.

Okay,
Here is the link as it is originally layed out by GoJS and the way it looks after re-linking the from and to endpoints to ports on the same node. Also I included the full code for configuring GoJS that I am using.

Thanks,

John





Here is the full code in for GoJS:

<br /><br /> var myDiagram;<br /> function init() {<br /> var $ = go.GraphObject.make; // for conciseness in defining templates<br /> var lightText = 'whitesmoke';<br /><br /> myDiagram = $(go.Diagram, "cascadeOfCareDiagram", // must name or refer to the DIV HTML element<br /> {<br /> initialContentAlignment: go.Spot.Center,<br /> allowDrop: true, // must be true to accept drops from the Palette<br /> "LinkDrawn": showLinkLabel, // this DiagramEvent listener is defined below<br /> "LinkRelinked": showLinkLabel,<br /> "animationManager.duration": 800, // slightly longer than default (600ms) animation<br /> "undoManager.isEnabled": true, // enable undo & redo<br /> layout:<br /> $(go.LayeredDigraphLayout //,<br /> //{ defaultSpringLength: 30, defaultElectricalCharge: 100 }<br /> )<br /> });<br /> myDiagram["grid.visible"] = true;<br /> myDiagram.toolManager.draggingTool.isGridSnapEnabled = true;<br /> myDiagram.toolManager.resizingTool.isGridSnapEnabled = true;<br /> //myDiagram.toolManager.LinkReshapingTool.setReshapingBehavior(LinkReshapingTool.All);<br /><br /><br /> function nodeStyle() {<br /> return [<br /> // The Node.location comes from the "loc" property of the node data,<br /> // converted by the Point.parse static method.<br /> // If the Node.location is changed, it updates the "loc" property of the node data,<br /> // converting back using the Point.stringify static method.<br /> new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),<br /> {<br /> // the Node.location is at the center of each node<br /> locationSpot: go.Spot.Center,<br /> isShadowed: true,<br /> shadowColor: "#888",<br /> // handle mouse enter/leave events to show/hide the ports<br /> mouseEnter: function (e, obj) { showPorts(obj.part, true); },<br /> mouseLeave: function (e, obj) { showPorts(obj.part, false); }<br /> }<br /> ];<br /> }<br /><br /> // Define a function for creating a "port" that is normally transparent.<br /> // The "name" is used as the GraphObject.portId, the "spot" is used to control how links connect<br /> // and where the port is positioned on the node, and the boolean "output" and "input" arguments<br /> // control whether the user can draw links from or to the port.<br /> function makePort(name, spot, output, input) {<br /> // the port is basically just a small circle that has a white stroke when it is made visible<br /> return $(go.Shape, "Circle",<br /> {<br /> fill: "transparent",<br /> stroke: null, // this is changed to "white" in the showPorts function<br /> desiredSize: new go.Size(8, 8),<br /> alignment: spot, alignmentFocus: spot, // align the port on the main Shape<br /> portId: name, // declare this object to be a "port"<br /> fromSpot: spot, toSpot: spot, // declare where links may connect at this port<br /> fromLinkable: output, toLinkable: input, // declare whether the user may draw links to/from here<br /> cursor: "pointer" // show a different cursor to indicate potential link point<br /> });<br /> }<br /><br /> myDiagram.nodeTemplateMap.add("", // the default category<br /> $(go.Node, "Spot", nodeStyle(),<br /> // the main object is a Panel that surrounds a TextBlock with a rectangular Shape<br /> $(go.Panel, "Auto",<br /> $(go.Shape, "Rectangle",<br /> { fill: "#00A9C9", stroke: null },<br /> new go.Binding("figure", "figure")),<br /> $(go.TextBlock,<br /> {<br /> font: "bold 11pt Helvetica, Arial, sans-serif",<br /> stroke: lightText,<br /> margin: 8,<br /> maxSize: new go.Size(160, NaN),<br /> wrap: go.TextBlock.WrapFit,<br /> editable: true<br /> },<br /> new go.Binding("text", "text").makeTwoWay())<br /> ),<br /> // four named ports, one on each side:<br /> makePort("T", go.Spot.Top, true, true),<br /> makePort("L", go.Spot.Left, true, true),<br /> makePort("R", go.Spot.Right, true, true),<br /> makePort("B", go.Spot.Bottom, true, true)<br /> ));<br /><br /> myDiagram.nodeTemplateMap.add("Start",<br /> $(go.Node, "Spot", nodeStyle(),<br /> $(go.Panel, "Auto",<br /> $(go.Shape, "Circle",<br /> { minSize: new go.Size(40, 60), fill: "#79C900", stroke: null }),<br /> $(go.TextBlock, "Start",<br /> { margin: 5, font: "bold 11pt Helvetica, Arial, sans-serif", stroke: lightText })<br /> ),<br /> // three named ports, one on each side except the top, all output only:<br /> makePort("L", go.Spot.Left, true, true),<br /> makePort("R", go.Spot.Right, true, true),<br /> makePort("B", go.Spot.Bottom, true, true)<br /> ));<br /><br /> myDiagram.nodeTemplateMap.add("End",<br /> $(go.Node, "Spot", nodeStyle(),<br /> $(go.Panel, "Auto",<br /> $(go.Shape, "Circle",<br /> { minSize: new go.Size(40, 60), fill: "#DC3C00", stroke: null }),<br /> $(go.TextBlock, "End",<br /> { margin: 5, font: "bold 11pt Helvetica, Arial, sans-serif", stroke: lightText })<br /> ),<br /> // three named ports, one on each side except the bottom, all input only:<br /> makePort("T", go.Spot.Top, true, true),<br /> makePort("L", go.Spot.Left, true, true),<br /> makePort("R", go.Spot.Right, true, true)<br /> ));<br /><br /> myDiagram.nodeTemplateMap.add("Comment",<br /> $(go.Node, "Auto", nodeStyle(),<br /> $(go.Shape, "File",<br /> { fill: "#EFFAB4", stroke: null }),<br /> $(go.TextBlock,<br /> {<br /> margin: 5,<br /> maxSize: new go.Size(200, NaN),<br /> wrap: go.TextBlock.WrapFit,<br /> textAlign: "center",<br /> editable: true,<br /> font: "bold 12pt Helvetica, Arial, sans-serif",<br /> stroke: '#454545'<br /> },<br /> new go.Binding("text", "text").makeTwoWay())<br /> // no ports, because no links are allowed to connect with a comment<br /> ));<br /><br /><br /> // replace the default Link template in the linkTemplateMap<br /> myDiagram.linkTemplate =<br /> $(go.Link, // the whole link panel<br /> // the link shape<br /> { routing: go.Link.AvoidsNodes, corner: 10, curve: go.Link.JumpOver, reshapable: true, relinkableTo: true, relinkableFrom: true },<br /> $(go.Shape, { isPanelMain: true, stroke: "black" } ),<br /> // the arrowhead<br /> $(go.Shape, { toArrow: "standard", stroke: null } ),<br /> $(go.Panel, "Auto",<br /> $(go.Shape, // the link shape<br /> {<br /> fill: $(go.Brush, go.Brush.Radial, { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),<br /> stroke: null<br /> }),<br /> $(go.TextBlock, // the label<br /> {<br /> textAlign: "center",<br /> font: "10pt helvetica, arial, sans-serif",<br /> stroke: "#555555",<br /> margin: 4<br /> },<br /> new go.Binding("text", "text"))<br /> )<br /> );<br /><br /> function showLinkLabel(e) {<br /> var label = e.subject.findObject("LABEL");<br /> if (label !== null) label.visible = (e.subject.fromNode.data.figure === "Diamond");<br /> }<br /><br /> // temporary links used by LinkingTool and RelinkingTool are also orthogonal:<br /> myDiagram.toolManager.linkingTool.temporaryLink.routing = go.Link.Orthogonal;<br /> myDiagram.toolManager.relinkingTool.temporaryLink.routing = go.Link.Orthogonal;<br /><br /> jQuery.getJSON("campaign.json", function (data) { loadData(data); });<br /><br /><br /> }<br /><br /><br /> function loadData(jsonData) {<br /><br /> console.log(jsonData);<br /> console.log(jsonData.Campaign_Name);<br /><br /> var events = new Array();<br /> var links = new Array();<br /> var exitNodes = new Array();<br /> var startNodes = new Array();<br /><br /> events[events.length] = { category: "Comment", text: jsonData.Campaign_Name };<br /> for (var i = 0; i < jsonData.Events.length; i++) {<br /> var campaign_event = jsonData.Events<em>;<br /> //console.log("ClassName: " + campaign_event.class + ", Intervention Config Class: " + campaign_event.Event_Coordinator_Config.Intervention_Config.class + " Intervention config type: " + campaign_event.Event_Coordinator_Config.Intervention_Config.Actual_IndividualIntervention_Config.Event_Or_Config);<br /> if (campaign_event.class === "CampaignEvent" && campaign_event.Event_Coordinator_Config.Intervention_Config.class == "NodeLevelHealthTriggeredIV") {<br /><br /> var eventText = campaign_event.Event_Name;<br /> var interventionConfig = campaign_event.Event_Coordinator_Config.Intervention_Config.Actual_IndividualIntervention_Config;<br /> var eventKey = campaign_event.Event_Coordinator_Config.Intervention_Config.Trigger_Condition;<br /> var addEntry = false;<br /><br /> if (campaign_event.Event_Coordinator_Config.Intervention_Config.Actual_IndividualIntervention_Config.hasOwnProperty("Event_Or_Config")<br /> && campaign_event.Event_Coordinator_Config.Intervention_Config.Actual_IndividualIntervention_Config.Event_Or_Config == "Event") {<br /><br /> addEntry = true;<br /><br /> if (interventionConfig.hasOwnProperty("Positive_Diagnosis_Event")) {<br /> links[links.length] = { from: eventKey, to: interventionConfig.Positive_Diagnosis_Event, fromPort: "R", toPort: "L", text: "( + )" } //fromPort: "B", toPort: "T",<br /> }<br /><br /> if (interventionConfig.hasOwnProperty("Negative_Diagnosis_Event")) {<br /> links[links.length] = { from: eventKey, to: interventionConfig.Negative_Diagnosis_Event, fromPort: "R", toPort: "L", text: "( - )" } // fromPort: "B", toPort: "T",<br /> }<br /> }<br /> else if (campaign_event.Event_Coordinator_Config.Intervention_Config.Actual_IndividualIntervention_Config.hasOwnProperty("Choices")) {<br /> addEntry = true;<br /> for (var choiceName in interventionConfig.Choices) {<br /> links[links.length] = { from: eventKey, to: choiceName, fromPort: "R", toPort: "L", text: "( " + interventionConfig.Choices[choiceName] + " )" }; // fromPort: "B", toPort: "T", <br /> } <br /> }<br /> else if (campaign_event.Event_Coordinator_Config.Intervention_Config.Actual_IndividualIntervention_Config.hasOwnProperty("Broadcast_Event")) {<br /><br /> addEntry = true;<br /> links[links.length] = { from: eventKey, to: interventionConfig.Broadcast_Event, fromPort: "R", toPort: "L", text: "Broadcast Event" }//, text: "Broadcast Event" } // <br /> }<br /><br /> if (addEntry) {<br /> events[events.length] = { key: eventKey, text: eventText };<br /> }<br /> }<br /> }<br /><br /> console.log("Number of Events: " + events.length);<br /> console.log(events);<br /> console.log(links);<br /> var graphLinkModel = new go.GraphLinksModel(events, links);<br /> graphLinkModel.linkFromPortIdProperty = "fromPort";<br /> graphLinkModel.linkToPortIdProperty = "toPort";<br /> myDiagram.model = graphLinkModel;<br /><br /> }<br /><br /><br /> function showPorts(node, show) {<br /> var diagram = node.diagram;<br /> if (!diagram || diagram.isReadOnly || !diagram.allowLink) return;<br /> node.ports.each(function (port) {<br /> port.stroke = (show ? "white" : null);<br /> });<br /> }<br /><br /> function handleFileLoadRequest(evt) {<br /> var file = evt.target.files[0];<br /> if (file) {<br /> console.log("got a file");<br /> var reader = new FileReader();<br /> reader.onload = function(e) {<br /> var content = e.target.result;<br /> console.log(content);<br /> loadData(JSON.parse(content));<br /> }<br /> }<br /> reader.readAsText(file);<br /> }<br />

I haven’t examined your code carefully, but it appears that that Link is supposed to connect with the node at the top port, which is why it loaded that way. But the Diagram.layout is a LayeredDigraphLayout, which normally expects that the links go in the direction of the layout (i.e. rightward).

Do you expect people to draw new links and want them to maintain the “side” that they come from and go to? That is what the FlowChart sample expects. But usually when there is a directional layout such as LayeredDigraphLayout or TreeLayout, that is not the case, so there is no desire for having multiple “ports” on each node.

If you don’t need multiple ports per node, you can greatly simplify the design. Most of the samples are that way, actually. It’s just FlowChart and a few others that have the additional complexity of maintaining ports.