Sub group Link not showing Properly in Main Group of the Fishbone

This is the subgroup without a layout that has ports and links.

Sub Group

I am showing this subgraph in a fishbone. It’s working but the link is not showing.
One thing I identified is that in the walkJson function change the key. I have a different link template for this link. So How can I set this from fishbone?

First, confirm in the debugger that each of those two Nodes are actually connected by a Link.

If the Link is actually present, then my guess is that it is hidden behind the Group, because the Group has a big opaque Shape covering most of its area. If the Link template sets the layerName to something different than the Group’s, don’t do that unless the link layer is in front of the group layer.

Hi Walter, I am sure there is link data. But it’s not showing because. walkJson changes the from node key and to node key in nodeDataArray. but it doesn’t change the linkDataArray.

Are you using a TreeModel or a GraphLinksModel?
The former has no link data objects, and the node data objects do not have “from” or “to” properties. Whereas the latter does and link data objects do have those two properties.
Please read GoJS Using Models -- Northwoods Software

I am using the Graph links model. Sorry Actually, I want to mean the node key is changed by walkjson. But I have some link data array related to this node.

function walkJson(obj, narr, larr, grpkey) {
var key = narr.length;
obj.key = key;
if (grpkey !== undefined) obj.group = grpkey;
narr.push(obj);

			  var children = obj.causes;
			  if (Array.isArray(children)) {
			    for (var i = 0; i < children.length; i++) {
			      var o = children[i];
			      // instead of a reference to parent node data, add a link data object
			      larr.push({ from: key, to: narr.length});
			      
			      if (o.isGroup) {				    	 
			        o.key = narr.length;
			        if (grpkey !== undefined) o.group = grpkey;
			        narr.push(o);
			        var members = o.members;
			        if (Array.isArray(members)) {
			          members.forEach(m => walkJson(m, narr, larr, o.key));
			        }
			      } else {
			        walkJson(o, narr, larr, grpkey);
			      }
			    }
			  }
			}

This is load function code:
var nodeDataArray = [];
var linkDataArray = [];
var json = JSON.parse(jsonString);
walkJson(json, nodeDataArray, linkDataArray, 0);
linkDataArray = linkDataList;
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);

I cannot debug that for you – you need to make sure that the data passed to the model in the nodeDataArray and linkDataArray are correct and consistent.

Are you saying that there is a Link but it is not connecting the intended Nodes? If so, then you are correct that you need to make sure that the node keys and link key references are consistent.

But if the Link is present and connecting the intended Nodes, then my guess about Layers should be investigated.

I have made a sample diagram, for you so that we can identify the problem.
my problem is the link between procedure-05 and procedure1.2 is not showing.
Would you please test this?

Fishbone Diagram with Nested Fishbone Groups

Where is the code?

var $ = go.GraphObject.make; // for conciseness in defining templates
var portSize = new go.Size(5, 5);
myDiagram =
$(go.Diagram, “myDiagramDiv”, // refers to its DIV HTML element by id
{
layout:
$(FishboneLayout,
{
angle: 180,
layerSpacing: 10,
nodeSpacing: 20,
rowSpacing: 10
}),
isReadOnly: true
}); // do not allow the user to modify the diagram

// define the normal node template, just some text
myDiagram.nodeTemplate =
$(go.Node,
$(go.TextBlock,
new go.Binding(“text”),
new go.Binding(“font”, “”, convertFont))
);

myDiagram.nodeTemplateMap.add(“Procedure”, // the default category
$(go.Node, “Spot”,
// nodeStyle(),
{

				minSize: new go.Size(100, 40),
				resizeObjectName: "proNode",						
							
			},
			// new go.Binding("location", "", toLocation).makeTwoWay(fromLocation),
			 new go.Binding("movable", "isMovable"),				
			new go.Binding("resizable", "isResizable", function (data) {					
				if (data == true && isEditEnabled) {
					return true;
				} else {
					return false;
				}
			}),
				$(go.Panel, "Auto", {
					name: "proNode",
				},
					new go.Binding("desiredSize", "nodeSize", go.Size.parse).makeTwoWay(go.Size.stringify),
					$(go.Shape, "Rectangle",
						{
							fill: "#9dc8eb",							
							stroke: 'gray',
							strokeWidth: 0.5,
							portId: "",
						},
					),
					$(go.Panel, "Table",{
						 stretch: go.GraphObject.Fill,
					},
						$(go.TextBlock, "Procedure",
							{
								//textEditor: window.TextEditorSelectBox,
								name: "OBJTEXT",
								stroke: "black",									
								editable: true,
								margin: 8, textAlign: "center",
								wrap: go.TextBlock.WrapFit,									
								//textValidation: isNameOK,
								row: 0, column: 0,columnSpan:5
							},
							new go.Binding("text", "text").makeTwoWay(),
							new go.Binding("choices"),
						),
						$(go.TextBlock, {row: 2, column:3,font: "8px Tahoma",alignment: go.Spot.Center,margin: new go.Margin(1, 1, 1, 1),},
								new go.Binding("text", "reference")								
						),
					)
				),
				$(go.Panel, "Vertical",
					new go.Binding("itemArray", "leftArray"),
					{
						alignment: go.Spot.Left,
						alignmentFocus: new go.Spot(0, 0.5, 8, 0),
						itemTemplate: makeItemTemplate('left'),
					}
				),
				$(go.Panel, "Vertical",
					new go.Binding("itemArray", "rightArray"),
					{
						alignment: go.Spot.Right,
						alignmentFocus: new go.Spot(1, 0.5, -8, 0),
						itemTemplate: makeItemTemplate('right'),
					},
				),

				$(go.Panel, "Horizontal",
					new go.Binding("itemArray", "topArray"),
					{
						alignment: go.Spot.Top,
						alignmentFocus: new go.Spot(0, 0.9, 8, 0),
						itemTemplate: makeItemTemplate('top'),
					},
				),
				$(go.Panel, "Horizontal",
					new go.Binding("itemArray", "bottomArray"),
					{
						alignment: go.Spot.Bottom,
						alignmentFocus: new go.Spot(1, 0.6, -10, 0),
						itemTemplate: makeItemTemplate('bottom'),
					},
				)
			));
			
			function makeItemTemplate(side) {

			if (side == 'left') {
				return $(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
			}

			if (side == 'top') {
				return $(go.Panel,
					{
						_side: "top",
						fromSpot: go.Spot.Top, toSpot: go.Spot.Top,
						fromLinkable: true, toLinkable: true, cursor: "pointer",
						//contextMenu: portMenu
					},
					new go.Binding("portId", "portId"),
					$(go.Shape, "Rectangle",
						{
							stroke: null, strokeWidth: 0,
							desiredSize: portSize,
							margin: new go.Margin(0, 1)
						},
						new go.Binding("fill", "portColor"))
				)  // end itemTemplate
			}

			if (side == 'right') {
				return $(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
			}


			if (side == 'bottom') {
				return $(go.Panel,
					{
						_side: "bottom",
						fromSpot: go.Spot.Bottom, toSpot: go.Spot.Bottom,
						fromLinkable: true, toLinkable: true, cursor: "pointer",
						//contextMenu: portMenu
					},
					new go.Binding("portId", "portId"),
					$(go.Shape, "Rectangle",
						{
							stroke: null, strokeWidth: 0,
							desiredSize: portSize,
							margin: new go.Margin(0, 1)
						},
						new go.Binding("fill", "portColor"))
				)  // end itemTemplate
			}
		}

function convertFont(data) {
var size = data.size;
if (size === undefined) size = 13;
var weight = data.weight;
if (weight === undefined) weight = “”;
return weight + " " + size + “px sans-serif”;
}

go.Shape.defineFigureGenerator(“HexArrow”, function(shape, w, h) {
var param1 = shape ? shape.parameter1 : NaN; // pointiness of arrow, lower is more pointy
if (isNaN(param1)) param1 = .05;

var geo = new go.Geometry();
var fig = new go.PathFigure(w, h/2, true);
geo.add(fig);
fig.add(new go.PathSegment(go.PathSegment.Line, (1-param1) * w, h));
fig.add(new go.PathSegment(go.PathSegment.Line, 0, h));
fig.add(new go.PathSegment(go.PathSegment.Line, param1 * w, h/2));
fig.add(new go.PathSegment(go.PathSegment.Line, 0, 0));
fig.add(new go.PathSegment(go.PathSegment.Line, (1-param1) * w, 0).close());
geo.spot1 = new go.Spot(param1, 0);
geo.spot2 = new go.Spot(1-param1, 1);
return geo;
});

myDiagram.groupTemplate=
$(go.Group, “Auto”,
{
layout:
$(FishboneLayout,
{
angle: 180,
layerSpacing: 10,
nodeSpacing: 20,
rowSpacing: 10
})
},
$(go.Shape, “HexArrow”, { fill: “rgba(0,0,0,0.1)”, strokeWidth: 0 }),
$(go.Placeholder, { padding: 4 }));

myDiagram.groupTemplateMap.add("Parallel",	
  $(go.Group, "Vertical",
   new go.Binding("location", "", toLocation).makeTwoWay(fromLocation),
	$(go.Panel, "Auto",
	  $(go.Shape, "RoundedRectangle",  // surrounds the Placeholder
		{ parameter1: 14,
		  fill: "rgba(128,128,128,0.33)" }),
	  
	),
	//$(go.Placeholder, { padding: 4 })
	/*$(go.TextBlock,         // group title
	  { alignment: go.Spot.Right, font: "Bold 12pt Sans-Serif" },
	  new go.Binding("text", "text")) */
 ));
 
 myDiagram.groupTemplateMap.add("ProcessGroup",
			$(go.Group, "Spot",
				{
					
					ungroupable: true,
					
				},
				 new go.Binding("location", "", toLocation).makeTwoWay(fromLocation),							
							
				$(go.Panel, "Auto", {
					//name: "processGroupNode"
				},						
					$(go.Shape,"HexArrow",
						{
							name: "processGroupNode",
							minSize: new go.Size(300, 100),								
							// geometryString: geoString,
							fill: "red",								
							portId: "",
						}
					),
					$("SubGraphExpanderButton",
						{
							alignment: go.Spot.TopLeft,
							name: "EXPANDBUTTON", width: 10, height: 10, margin: 2,
						}),
					$(go.TextBlock,
						{
							width: 180, height: 20,
							alignment: go.Spot.TopLeft,
							editable: false,
							margin: new go.Margin(4,0,0,12),
							opacity: 0.95,  // allow some color to show through
							//stroke: "#404040",
							 font: "bold 8px Tahoma",
						}
						, new go.Binding("text", "text")							
					))					
				
			));

// This demo switches the Diagram.linkTemplate between the “normal” and the “fishbone” templates.
// If you are only doing a FishboneLayout, you could just set Diagram.linkTemplate
// to the template named “fishbone” here, and not switch templates dynamically.

// use this link template for fishbone layouts
myDiagram.linkTemplate =
$(FishboneLink, // defined above
$(go.Shape)
);

myDiagram.linkTemplateMap.add(“SimpleLink”,
$(go.Link, // the whole link panel
{
routing: go.Link.AvoidsNodes,
curve: go.Link.JumpOver,
corner: 5,
relinkableFrom: true,
relinkableTo: true,
reshapable: true,
resegmentable: true,
fromLinkable: true,
toLinkable: true,
selectionAdorned: false
},
//new go.Binding(“points”).makeTwoWay(),
new go.Binding(“fromSpot”, “fromSpot”, go.Spot.parse),
new go.Binding(“toSpot”, “toSpot”, go.Spot.parse),
$(go.Shape, // the link path shape
{isPanelMain: true, stroke: “gray”, strokeWidth: 1, name: “OBJSHAPE”},
),
$(go.Shape, // the arrowhead
{toArrow: “standard”, strokeWidth: 0, fill: “gray”, name: “ARWSHAPE”})

			));

var json = {
“id”: 298,
“category”: “MainNodeFishbone”,
“key”: 1,
“reference”: “P-FIS-01”,
“text”: “Fishbone-01”,
“choices”: [],
“isGroup”: false,
“group”: 0,
“nodeType”: “Node”,
“url”: null,
“angle”: 0,
“topArray”: [],
“leftArray”: [],
“rightArray”: [],
“bottomArray”: [],
“extraNodeData”: null,
“size”: “16”,
“weight”: “bold”,
“isFishbone”: false,
“members”: [],
“causes”: [
{

        "fishbone": false,           
        "id": 308,
        "category": "ProcessGroup",
        "key": 2,
        "reference": "ddd",
        "text": "ddddd",
        "choices": [],
        "isGroup": true,
        "group": 0,  
        "location": "205.73 0.00",
        "nodeSize": "300 157",
        "nodeType": "Node",
        "isMovable": true,
        "isResizable": true,          
        "angle": 0,
        "topArray": [],
        "leftArray": [],
        "rightArray": [],
        "bottomArray": [],
        "extraNodeData": {
            "nodeShape": null,
            "backgroundColor": "#d3d3d3",
            "borderColor": "#000000",
            "fontColor": "#000000",
            "borderWidth": 1,
            "borderDashLink": null
        },
        "size": null,
        "weight": null,
        "isFishbone": false,
        "members": [
            {                   
                "fishbone": false,
                "allNodePorts": [
                    {
                        "id": 16,
                        "portId": "right0",
                        "portColor": "#000000",
                        "portOpacity": "1.0"
                    }
                ],
                "id": 302,
                "category": "Procedure",
                "key": "PROC_303_308_121_PROS_308_298_122_298",
                "reference": "P-05",
                "text": "Procedure-05",
                "choices": [
                    "Procedure-05"
                ],
               
                "isGroup": false,
                "group": 2,
                "horiz": false,                   
                "location": "48.91 21.09",
                "nodeSize": "114 40",
                "nodeType": "SubNode",                   
                "url": "/cnm/proc/procedure/v/view/15",
                "angle": 0,
                "topArray": [],
                "leftArray": [],
                "rightArray": [
                    {
                        "id": 16,
                        "portId": "right0",
                        "portColor": "#000000",
                        "portOpacity": "1.0"                            
                    }
                ],
                "bottomArray": [],
                "extraNodeData": null,
                "size": null,
                "weight": null,
                "isFishbone": false,
                "members": [],
                "causes": [],                   
            },
            {
               
                "fishbone": false,
                "allNodePorts": [
                    {
                        "id": 15,
                        "portId": "right0",
                        "portColor": "#000000",
                        "portOpacity": "1.0"
                    }
                ],
                "id": 303,
                "category": "Procedure",
                "key": "PROC_302_308_121_PROS_308_298_122_298",
                "reference": "P-1.2",
                "text": "Procedure-1.2",
                "choices": [
                    "Procedure-1.2"
                ],
                "showIO": null,
                "isGroup": false,
                "group": 2,                 
                "location": "62.91 96.09",
                "nodeSize": null,
                "nodeType": "SubNode",
                "isMovable": false,
                "isResizable": false,                   
                "angle": 0,
                "topArray": [],
                "leftArray": [],
                "rightArray": [
                    {
                        "id": 15,
                        "portId": "right0",
                        "portColor": "#000000",
                        "portOpacity": "1.0",                            
                    }
                ],
                "bottomArray": [],
                "extraNodeData": null,
                "size": null,
                "weight": null,
                "isFishbone": false,
                "members": [],
                "causes": [],                    
            }
        ],
        "causes": []
       
    },
    {
      
        "fishbone": false,
        "allNodePorts": [],
        "id": 309,
        "category": "Procedure",
        "key": 5,
        "reference": "P-1.3",
        "text": "Procedure-1.3",
        "choices": [],
        "showIO": null,
        "isGroup": false,
        "group": 0,          
        "location": "436.23 178.00",
        "nodeSize": null,
        "nodeType": "Node",
        "isMovable": true,
        "isResizable": true,
        "url": null,
        "angle": 0,
        "topArray": [],
        "leftArray": [],
        "rightArray": [],
        "bottomArray": [],
        "extraNodeData": null,
        "size": null,
        "weight": null,
        "isFishbone": false,
        "members": [],
        "causes": []
       
    },
    {
      
        "fishbone": false,
        "allNodePorts": [],
        "id": 305,
        "category": "Procedure",
        "key": 6,
        "reference": "P-02",
        "text": "Procedure-02",
        "choices": [],
        "showIO": null,
        "isGroup": false,
        "group": 0,
        "horiz": false,           
        "location": "99.23 118.00",
        "nodeSize": null,
        "nodeType": "Node",
        "isMovable": true,
        "isResizable": true,
        "url": null,
        "angle": 0,
        "topArray": [],
        "leftArray": [],
        "rightArray": [],
        "bottomArray": [],
        "extraNodeData": null,
        "size": null,
        "weight": null,
        "isFishbone": false,
        "members": [],
        "causes": []           
    }
],

};

// the model is now a GraphLinksModel, not a TreeModel, so that we can use Groups
function walkJson(obj, narr, larr, grpkey) {
var key = narr.length;
obj.key = key;
if (grpkey !== undefined) obj.group = grpkey;
narr.push(obj);

var children = obj.causes;
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
var o = children[i];
// instead of a reference to parent node data, add a link data object
larr.push({ from: key, to: narr.length });
if (o.isGroup) {
o.key = narr.length;
if (grpkey !== undefined) o.group = grpkey;
narr.push(o);
var members = o.members;
if (Array.isArray(members)) {
members.forEach(m => walkJson(m, narr, larr, o.key));
}
} else {
walkJson(o, narr, larr, grpkey);
}
}
}
}

function toLocation(data) {
  var loc = go.Point.parse(data.loc);
  if (data.group !== undefined) {
    var groupdata = myDiagram.model.findNodeDataForKey(data.group);
    if (groupdata) {
      loc.add(toLocation(groupdata));
    }
  }
  return loc;
};

// fromLocation just saves in data.loc either the absolute location if there's no containing Group,
// or the relative location with its containing Group.
function fromLocation(location, data) {
  if (data.group !== undefined) {
    var group = myDiagram.findNodeForKey(data.group);
    if (group) {
      var loc = location.copy().subtract(group.location);
      data.loc = loc.x.toFixed(2) + " " + loc.y.toFixed(2);  //go.Point.stringify(loc);
    }
  } else {
    data.loc = go.Point.stringify(location);
  }
};

myDiagram.linkTemplateMap.add("SimpleLink",
			$(go.Link,  // the whole link panel
				{					   
					routing: go.Link.AvoidsNodes,
					curve: go.Link.JumpOver,
					corner: 5,						
					relinkableFrom: true,
					relinkableTo: true,
					reshapable: true,
					resegmentable: true,
					fromLinkable: true,
					toLinkable: true,						
					selectionAdorned: false
				},
				//new go.Binding("points").makeTwoWay(),
				new go.Binding("fromSpot", "fromSpot", go.Spot.parse),
				new go.Binding("toSpot", "toSpot", go.Spot.parse),					
				$(go.Shape,  // the link path shape
					{isPanelMain: true, stroke: "gray", strokeWidth: 1, name: "OBJSHAPE"}),
				$(go.Shape,  // the arrowhead
					{toArrow: "standard", strokeWidth: 0, fill: "gray", name: "ARWSHAPE"}),					 
				
			));

var nodeDataArray = [{ isGroup: true, key: 0 }
];
var linkDataArray = [
{

"from": "PROC_303_308_121_PROS_308_298_122_298",
"to": "PROC_302_308_121_PROS_308_298_122_298",    
"fromPort": "right0",
"toPort": "right0",   
"category": "SimpleLink"

}

];
walkJson(json, nodeDataArray, linkDataArray, 0);
console.log(nodeDataArray);
console.log(linkDataArray);
myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);

Thanks for the code. The model’s linkDataArray is:

"linkDataArray": [
{"from":"PROC_303_308_121_PROS_308_298_122_298","to":"PROC_302_308_121_PROS_308_298_122_298","fromPort":"right0","toPort":"right0","category":"SimpleLink"},
{"from":1,"to":2},
{"from":1,"to":5},
{"from":1,"to":6}
]

where the nodes are:

// console.log(d.key, d.isGroup, d.text)
0 true undefined
1 false Fishbone - 01
2 true ddddd
3 false Procedure-05
4 false Procedure-1.2
5 false Procedure-1.3
6 false Procedure-02

So the last three link data objects look good. But the first one? There’s no node that has a key of “PROC_303_308_121_PROS_308_298_122_298”, nor any node with a key of “PROC_302_308_121_PROS_308_298_122_298”. You probably don’t need the other properties on that link data object, either.

This is what I meant when I suggested that you make sure that the model data is correct and consistent.

One thing I want to explain you from the first post. WalkJson function changes my key. please check my Json there are “PROC_303_308_121_PROS_308_298_122_298” and “PROC_302_308_121_PROS_308_298_122_298”.key.

When I ran your code, the Array used by the model did not include any node data objects whose key is “PROC_30…”.

If you debug your walkJson function you can see how it assigns the “key” property on the data.

Please look at my JSON data in the code, not the node data array. I have set the key in JSON data but its changes the key.

Yes, I see that.

If you read your walkJson function you can see how it assigns the “key” property on the data. Don’t do that if you want to keep the “key” value.

1 Like

Yes, I understood that, What can I do for showing the link with the template
?

Make sure all of the key values and references are correct in all of the model data.

did you get any wrong values in my JSON data? unique key generation is very important to me. because one node I need to show multiple times for the subgraph.
There is another problem is the support of port and link templates in the fishbone.

Look, I cannot implement your code for you. Fix that walkJson code to do what you want. If necessary, start from scratch.