Issue with Merged Links in GoJS Version 2.2

am currently working with the GoJS library, specifically version 2.2, and I am encountering an issue related to link visibility when multiple links are present between the same nodes. It seems that the links are merging or overlapping, which creates confusion in the diagram’s presentation.

I have implemented a link template with properties that include link routing and appearance settings. Here’s a portion of my link template configuration:

linkTemplate: this.$(
        go.Link,
        {
          toPortChanged: functionBlockCreation.updateLinkColor,
          locationSpot: go.Spot.AllSides,
          routing: go.Link.AvoidsNodes,
          curve: go.Link.JumpOver,
          corner: 3,
          selectionAdorned: false,
          shadowOffset: new go.Point(0, 0),
          shadowBlur: 5,
          shadowColor: "blue",
          reshapable: true,
          relinkableFrom: true,
          relinkableTo: true,
          isLayoutPositioned: false,
          zOrder: 10,
          segmentFraction: 0.5,
          layerName: "Background"
        },
        new go.Binding("isShadowed", "isSelected").ofObject(),
        this.$(
          go.Shape,
          { name: "SHAPE", strokeWidth: 1.5, stroke: Colors.connectionPurple },
          new go.Binding(
            "stroke",
            "isHighlighted",
            functionBlockCreation.strokeColorLink
          ).ofObject(),
          new go.Binding("strokeWidth", "isHighlighted", function (h) {
            return h ? px13 : px15;
          }).ofObject()
        ),
        new go.Binding("points").makeTwoWay(),
        {contextMenu: new ContextMenuList().linkDataDeleteMenu()}
);

Issues Encountered:

  1. When multiple links exist between two nodes, they appear to merge or overlap, making it difficult to distinguish between individual links.
  2. I have tried using the go.Link.AvoidsNodes and go.Link.JumpOver settings, but the issue persists.
    please suggest any possible way to resolve it specially on 2.2 version.
    new AvoidsLinksRouter(); is available in v 3.0 onward.

Do you have a small screenshot showing the problem?

Do you set fromSpot and toSpot on your node’s port element(s)?

Have you set Diagram.layout to a layout, and if so, how have you initialized it?

go.Shape,
      {
        figure: shape,
        name: dataType,
        width: width,
        height: height,
        strokeWidth: 1,
        fill: fillColor,
        stroke: color,
        alignment: go.Spot.Center,
        alignmentFocus: go.Spot.Center,
        toLinkable: toLinkable,
        fromLinkable: fromLinkable,
        fromSpot: go.Spot.Right,
        toSpot: go.Spot.Left,
        angle: rotation,
        portId: portId,
        cursor: "pointer",
      },
      new go.Binding("figure", "figureType").makeTwoWay(),
      new go.Binding("fill", fillColor),
      new go.Binding("stroke", "Portcolor").makeTwoWay()

this is the from Spot and to spot am using
here the from Linkable and to linkable are set to true.
and related to layout there is no layout added try to add layout but due to that whenever we reload the diagram.model the positions of all the nodes is getting set as per the layout and that we dont want
currently if multiple links are connected to single node then it is showing like this


but we want

like this with more spacing between the line

It appears that your screenshot was from one of our samples demonstrating the AvoidsLinksRouter. Assuming you are using GoJS v3, just load that extension and add an instance of that router to the Diagram.routers list.

If you must use version 2.2, sorry, but that extension won’t work in version 2.*.

is there any way to achieve this functionality using the version 2.2.
How the same behavior i can implement using version 2.2.
I have provided these screenshots just for the reference of what kind of functionality i want.

If you are doing new development now on your app, why can’t you upgrade to v3?

You have the source code for the extension, so you could adapt the code to work in v2.2.

I am working on an old project and have a license for version 2.2. Due to significant changes and new features in the new version (v3), the existing codebase is not compatible. Adapting the code to work with v3 might require extensive rewriting or refactoring. I have tried to upgrade the version, but the existing code and templates for the nodes are failing, leading to major changes in the existing codebase and a time-consuming rewrite.

Most projects have no problems upgrading to v3.0. A rewrite should not be required.

When you tried it, what was the problem?

we have used the custom node templates with custom created shapes for the node and the code for that is quite big unable to identify exact what is failed

What was the error message?

There is a complete listing of incompatible changes at:

All of the incompatible changes have easy or simple workarounds to work in v3.0.

can you please let me know what are the required dependency’s for V3 for react ( electron ) app. also i am using the mfe structured application in that after updating the GOJS version to 3.0.0 facing issue to launch the application getting error
image

static{const t=AnimationManager.ux,i=(e,s,n,o,r,l)=>{e.position=new Point(o(r,s.x,n.x-s.x,l),o(r,s.y,n.y-s.y,l))};t.add("position:diagram",i),t.add("position",i),t.add("position:part",(e,s,n,o,r,l)=>{r<l?e.Nf(o(r,s.x,n.x-s.x,l),o(r,s.y,n.y-s.y,l),!1):e.position=new Point(o(r,s.x,n.x-s.x,l),o(r,s.y,n.y-s.y,l))}),t.add("location",(e,s,n,o,r,l)=>{r<l?e.Nf(o(r,s.x,n.x-s.x,l),o(r,s.y,n.y-s.y,l),!0):e.location=new Point(o(r,s.x,n.x-s.x,l),o(r,s.y,n.y-s.y,l))}),t.add("position:placeholder",(e,s,n,o,r,l)=>{r<l?e.Nf(o(r,s.x,n.x-s.x,l),o(r,s.y,n.y-s.y,l),!1):e.position=new Point(o(r,s.x,n.x-s.x,l),o(r,s.y,n.y-s.y,l))}),t.add("position:nodeCollapse",(e,s,n,o,r,l)=>{const h=e.actualBounds,a=n.actualBounds,f=a.x+a.width/2-h.width/2,c=a.y+a.height/2-h.height/2;r<l?e.Nf(o(r,s.x,f-s.x,l),o(r,s.y,c-s.y,l),!1):e.position=new Point(o(r,s.x,f-s.x,l),o(r,s.y,c-s.y,l))}),t.add("desiredSize",(e,s,n,o,r,l)=>{e.desiredSize=new Rect(o(r,s.width,n.width-s.width,l),o(r,s.height,n.height-s.height,l))}),t.add("width",(e,s,n,o,r,l)=>{e.width=o(r,s,n-s,l)}),t.add("height",(e,s,n,o,r,l)=>{e.height=o(r,s,n-s,l)}),t.add("fill",(e,s,n,o,r,l)=>{e.fill=AnimationManager.VM(s,n,r,l,o)}),t.add("stroke",(e,s,n,o,r,l)=>{e.stroke=AnimationManager.VM(s,n,r,l,o)}),t.add("strokeWidth",(e,s,n,o,r,l)=>{e.strokeWidth=o(r,s,n-s,l)}),t.add("strokeDashOffset",(e,s,n,o,r,l)=>{e.strokeDashOffset=o(r,s,n-s,l)}),t.add("background",(e,s,n,o,r,l)=>{e.background=AnimationManager.VM(s,n,r,l,o)}),t.add("opacity",(e,s,n,o,r,l)=>{e.opacity=o(r,s,n-s,l)}),t.add("scale",(e,s,n,o,r,l)=>{e.scale=o(r,s,n-s,l)}),t.add("angle",(e,s,n,o,r,l)=>{e.angle=o(r,s,n-s,l)})}static Default=1;static AnimateLocations=2;static None=3}class Animation{gx;Xm;Di;Fu;Vo;Ss;tA;If;we;mx;li;Qn;Ru;Of;px;yx;Df;BM;wx;bs;b;zM;XM;YM;constructor(t){this.b=null,this.bs=null,this.BM=null,this.wx=null,this.zM=!1,this.Di=!1,this.Df=!1,this.li=0,this.Qn=0,this.gx=Animation.EaseInOutQuad,this.Xm=Animation.EaseInOutQuad,this.Ru=!1,this.Of=!1,this.px=1,this.yx=0,this.Vo=NaN,this.Ss=NaN,this.XM=0,this.Fu=null,this.tA=Point.Do,this.we=new GMap,this.mx=new GMap,this.If=new GSet,this.YM=1,t&&Object.assign(this,t)}suspend(){this.Df=!0}advanceTo(t,i){i&&(this.Df=!1),this.Ru&&t>=this.Ss&&(this.Of=!0,t=t-this.Ss),this.XM=t,this.ZC(!0),this.bs.Lu(),this.b.Pr(),this.bs.Tu(),this.b.redraw()}fx(t){if(this.mx.clear(),this.Of=!1,this.yx=0,this.Ss=NaN,this.If.count>0&&this.If.clear(),t!==null){const i=t.links;for(;i.next();)i.value.Dr=null}}gF(){return this.we.count>0}start(){if(this.we.count===0)return this;if(this.Di)return this;let t=this.b;const i=this.we.iterator;for(;i.next();){const s=i.key;t===null&&(s instanceof Diagram?t=s:s instanceof GraphObject&&(t=s.diagram))}if(t!==null)this.b=t,this.bs=t.animationManager;else return this;const e=this.bs;return e.isEnabled===!1?this:(this.Ss=isNaN(this.Vo)?e.duration:this.Vo,this.Xm=this.gx,e.Lf&&e.Tf===1&&this===e.defaultAnimation&&(this.Xm=Animation.EaseOutExpo,this.Ss=isNaN(this.Vo)?e.duration===600?900:e.duration:this.Vo),this.YM=t.scrollMode,this.isViewportUnconstrained&&(t.$h=2),e.Lu(),this.If.each(s=>{s.data=null,t.add(s)}),e.Tu(),this.Di=!0,this.li=+new Date,this.Qn=this.li+this.Ss,e.fF(this),this)}addTemporaryPart(t,i){return t.De()?(Debug&&(i===void 0&&Util.n("addTemporaryPart: Required Diagram argument missing"),t.diagram===i&&Util.n("addTemporaryPart: Part already in Diagram, did you mean to pass in a copy?"),this.b!==null&&this.b!==i&&Util.n("addTemporaryPart: A different Diagram is already associated with this Animation: "+this.b.toString())),this.If.add(t),this.b=i,this):this}add(t,i,e,s,n){if(this.b===null&&(t instanceof Diagram?this.b=t:t instanceof GraphObject&&t.diagram!==null&&(this.b=t.diagram)),t instanceof Part){if(!t.isAnimated)return this;i==="position"&&(i="position:part")}return this.OM(t,i,e,s,n),this}OM(t,i,e,s,n){const o=this.we;let r,l,h;if(t instanceof Diagram&&i==="position"&&(i="position:diagram"),(i==="fill"||i==="stroke"||i==="background")&&(Brush.zo(e),Brush.KM(),e=[Brush.Yi.n0,Brush.Yi.n1,Brush.Yi.n2,Brush.Yi.n3],Brush.zo(s),Brush.KM(),s=[Brush.Yi.n0,Brush.Yi.n1,Brush.Yi.n2,Brush.Yi.n3]),o.contains(t))r=o.getValue(t),l=r.li,h=r.ks,l[i]===void 0&&(l[i]=this.Iu(e)),h[i]=this.Iu(s);else{if(i==="position"&&e.equalsApprox(s))return;l={},h={},l[i]=this.Iu(e),h[i]=this.Iu(s),r=new AnimationState(l,h,n),o.add(t,r)}const a=l[i];a instanceof Point&&!a.isReal()&&a.c(this.tA),n&&i.indexOf("position:")===0&&t instanceof Part?r.xx.location=this.Iu(t.location):n&&(r.xx[i]=this.Iu(e))}Iu(t){return t instanceof Point||t instanceof Size?t.copy():t}mF(t){const i=this.we;if(i.contains(t)){const e=i.getValue(t);e.bx=!0}}cx(t){if(!this.Di)return!1;const i=this.we.getValue(t);return i!==null&&i.bx}IM(t){if(!this.Di)return!1;const i=this.we.getValue(t);return i!==null&&!!(i.li.position||i.li["position:part"]||i.li.location)}cF(){if(this.If.count>0)return!0;const t=this.we.iterator;for(;t.next();){const i=t.key;if(i instanceof GraphObject&&i.diagram!==null||i instanceof Diagram)return!0}return!1}ZC(t){if(this.Df&&!t)return;const i=this.bs;if(this.Di===!1)return;const e=+new Date;let s=e>this.Qn?this.Ss:e-this.li;t&&(s=this.XM,s<this.Ss?(this.li=+new Date-s,this.Qn=this.li+this.Ss):s=this.Ss),i.Lu(),this.jC(s),this.b.Pr(!0),i.Tu(),e>this.Qn&&(this.Ru&&!this.Of?(this.li=+new Date,this.Qn=this.li+this.Ss,this.Of=!0):this.Du(!1))}jC(t){const i=this.Ss,e=this.we.iterator,s=this.Of;for(;e.next();){const n=e.key;if(n instanceof GraphObject&&n.diagram===null)continue;const o=e.value,r=s?o.ks:o.li,l=s?o.li:o.ks,h=AnimationManager.ux;for(const a in l)a==="position"&&(l["position:placeholder"]||l["position:nodeCollapse"])||h.get(a)!==null&&h.get(a)(n,r[a],l[a],this.Xm,t,i,this)}}stop(){return this.Di?(this.Du(!0),this):this}Du(t){if(this.wx!==null&&this.wx.pF(this.BM),!this.Di)return;const i=this.b,e=this.bs;e.Lf=!1,this.Di=!1,this.Df=!1,e.Lu();const s=this.we,n=this.If.iterator;for(;n.next();)i.remove(n.value);const o=this.Ru,r=s.iterator,l=AnimationManager.ux;for(;r.next();){const a=r.key,f=r.value,c=o?f.ks:f.li,u=o?f.li:f.ks,d=f.xx;for(const m in u)if(l.get(m)!==null){let g=m;f.kx&&(g==="position:nodeCollapse"||g==="position:placeholder")&&(g="position"),l.get(g)(a,c[m],d[m]!==void 0?d[m]:f.kx?c[m]:u[m],this.Xm,this.Ss,this.Ss,this)}f.kx&&d.location!==void 0&&a instanceof Part&&(a.location=d.location),f.bx&&a instanceof Part&&a.Ki(!1)}this.yx++;const h=!t&&this.px>this.yx;if(!h&&(this===e.Vm||this===e.defaultAnimation)&&this.we.clear(),i.Sx.clear(),i.PS(!1),i.invalidateDocumentBounds(),i.T(),i.Pr(!0),e.defaultAnimation===this){const a=e.zm.iterator;for(;a.next();)a.value.yF();e.zm.clear()}if(i.Pr(!0),this.isViewportUnconstrained&&(i.scrollMode=this.YM),e.Tu(),h){this.Of=!1,this.start();return}this.fx(null),e.Du(this),this.Fu&&this.Fu(this),i.requestUpdate()}Ff(t,i){const e=i.actualBounds;let s=null;if(i instanceof Group&&(s=i.placeholder),s!==null&&s.visible){const n=s.getDocumentPoint(Spot.TopLeft),o=s.padding;n.x+=o.left,n.y+=o.top,this.add(t,"position",n,t.position,!1)}else this.add(t,"position",new Point(e.x+e.width/2,e.y+e.height/2),t.position,!1);this.add(t,"scale",.01,t.scale,!1),t instanceof Group&&this.wF(t,i)}wF(t,i){const e=t.memberParts;for(;e.next();){const s=e.value;s instanceof Node&&this.Ff(s,i)}}Rf(t,i){if(!t.isVisible())return;let e=null;if(i instanceof Group&&(e=i.placeholder),e!==null&&e.visible){const s=e.getDocumentPoint(Spot.TopLeft),n=e.padding;s.x+=n.left,s.y+=n.top,this.add(t,"position:placeholder",t.position,s,!0)}else this.add(t,"position:nodeCollapse",t.position,i,!0);this.add(t,"scale",t.scale,.01,!0),this.mF(t),t instanceof Group&&this.xF(t,i)}xF(t,i){const e=t.memberParts;for(;e.next();){const s=e.value;s instanceof Node&&this.Rf(s,i)}}get duration(){return this.Vo}set duration(t){Util.t(t,"number",Animation,"duration"),t<1&&Util.G(t,">= 1",Animation,"duration"),this.Vo=t}get reversible(){return this.Ru}set reversible(t){this.Ru=t}get runCount(){return this.px}set runCount(t){t>0?this.px=t:Util.n("Animation.runCount value must be a positive integer.")}get finished(){return this.Fu}set finished(t){this.Fu!==t&&(t!==null&&Util.t(t,"function",Animation,"finished"),this.Fu=t)}get easing(){return this.gx}set easing(t){this.gx=t}get isViewportUnconstrained(){return this.zM}set isViewportUnconstrained(t){this.zM=t}get isAnimating(){return this.Di}getTemporaryState(t){let i=this.mx.get(t);return i===null&&(i={},this.mx.add(t,i)),i}static EaseLinear=(t,i,e,s)=>e*t/s+i;static EaseInOutQuad=(t,i,e,s)=>(t/=s/2,t<1?e/2*t*t+i:-e/2*(--t*(t-2)-1)+i);static EaseInQuad=(t,i,e,s)=>e*(t/=s)*t+i;static EaseOutQuad=(t,i,e,s)=>-e*(t/=s)*(t-2)+i;static EaseInExpo=(t,i,e,s)=>t===0?i:e*Math.pow(2,10*(t/s-1))+i;static EaseOutExpo=(t,i,e,s)=>t===s?i+e:e*(-Math.pow(2,-10*t/s)+1)+i}class AnimationState{li;ks;xx;kx;bx;constructor(t,i,e){this.li=t,this.ks=i,this.xx={},this.kx=e,this.bx=!1}}var TriggerStart=(w=>(w[w.Default=1]="Default",w[w.Immediate=2]="Immediate",w[w.Bundled=3]="Bundled",w))(TriggerStart||{});class AnimationTrigger{Te;gn;Ou;Ef;constructor(t,i,e){e&&Debug&&Util.it(e,TriggerStart,"TriggerStart"),this.Te=null,this.gn=t,this.Ou=e||1,this.Ef=null,i!==void 0&&(this.Ef=i,e===void 0&&(this.Ou=2))}copy(){const t=new AnimationTrigger(this.gn);t.Ou=this.Ou;const i=this.Ef;if(i!==null){const e={};i.duration!==void 0&&(e.duration=i.duration),i.finished!==void 0&&(e.finished=i.finished),i.easing!==void 0&&(e.easing=i.easing),t.Ef=e}return t}get propertyName(){return this.gn}set propertyName(t){this.gn=t}get animationSettings(){return this.Ef}set animationSettings(t){this.Ef=t}bF(t){const i=this.Ef;i!==null&&(i.duration&&(t.duration=i.duration),i.finished&&(t.finished=i.finished),i.easing&&(t.easing=i.easing))}get startCondition(){return this.Ou}set startCondition(t){Debug&&Util.it(t,TriggerStart,"TriggerStart"),this.Ou=t}static Default=1;static Immediate=2;static Bundled=3}class Layer{b;Tt;Qt;Fr;Xl;Yl;Kl;Ul;Gl;Hl;vl;ql;Wl;jl;Jl;Zl;Cf;$l;Mx;Ym;Eu;It;constructor(t){GSet.ji(this),this.b=null,this.It=new List,this.Tt="",this.Qt=1,this.Fr=!1,this.Xl=!0,this.Yl=!0,this.Kl=!0,this.Ul=!0,this.Gl=!0,this.Hl=!0,this.vl=!0,this.ql=!0,this.Wl=!0,this.jl=!0,this.Jl=!0,this.Zl=!0,this.Cf=!0,this.$l=!0,this.Mx=!0,this.Ym=!1,this.Eu=[],t&&Object.assign(this,t)}kF(){const t=this.It;for(let i=0;i<t.length;i++)t.h[i].UM(null);t.clear(),this.Eu.length=0}Bo(t){this.b=t}toString(t){t===void 0&&(t=0);const i='Layer "'+this.name+'"';if(t<=0)return i;let e=0,s=0,n=0,o=0,r=0;const l=this.It.iterator;for(;l.next();){const a=l.value;a instanceof Group?n++:a instanceof Node?s++:a instanceof Link?o++:a instanceof Adornment?r++:e++}let h="";if(e>0&&(h+=e+" Parts "),s>0&&(h+=s+" Nodes "),n>0&&(h+=n+" Groups "),o>0&&(h+=o+" Links "),r>0&&(h+=r+" Adornments "),t>1){const a=this.It.iterator;for(;a.next();){const f=a.value;h+=`

No version of GoJS has any dependency on any other software library. Really the only dependency is on what the browser provides, and for v3 we require something like ES2017.

I highly recommend using the latest version, 3.0.20, not an old baselevel.

Which GoJS library file are you loading? Are you using <script> tags – if so, load “go.js”. More likely: are you using import? If you are using ES6 modules, load “go.mjs” or “go-module.js”. (Those are actually the same files.)

i am not using this React app directly on Browser it used in electron
“electron”: “^13.2.1”,
“electron-builder”: “^22.11.7”,
and this the electron version i am using

import go from "gojs";
import { ReactDiagram } from "gojs-react";

this i am using to import gojs

Wow, that’s an old version of Electron/Chrome/Node/V8.

I suppose it’s possible that you cannot upgrade to GoJS v3.0 due to some missing language feature, but in reviewing V8 v9.1 I don’t see an obvious problem.

And Node.js v14 and Chrome v91 supported JavaScript modules, so that shouldn’t be a problem unless somehow the configuration is wrong. But I’ll ask again: which GoJS library file are you using, and which module system are you using?

i have used everywhere

import go from "gojs";

and after that all the things i am using components like

 private $ = go.GraphObject.make;
private nodeDataArray: go.ObjectData | GenericType;

 const templateData = this.$(
          go.Node,
          UiEventConstants.templateType,

new go.Binding("location", "loc", go.Point.parse).makeTwoWay(
            go.Point.stringify
          ),
 this.$(
            go.Panel,
            "Spot",
            this.$(
              go.Panel,
              "Verticle",

this.$(
                go.Panel,
                "Spot",
                {},
                this.$(go.Shape, "Rectangle", {
                  width: fbImageWidth,
                  height: fbImageHeight,
                  fill: null,
                  stroke: null
                }),
                this.$(go.Picture, this.nodeImage, {
                  alignment: go.Spot.Right,
                  alignmentFocus: go.Spot.Right,
                  width: fbImageWidth,
                  height: fbImageHeight,
                  margin: functionBlockCreation.changeImageMargin(
                    this.nodeDataArray[index].brandSpecificId
                  )
                })
              )

this are some samples

I was asking about your build configurations.

No matter – I don’t know what’s wrong, but I would recommend upgrading to a newer version of Electron. We know our customers are using GoJS v3.0 with newer versions of Electron, although off-hand I don’t know exactly which versions they are using.

I still recommend that you update to the latest version of GoJS, which is now 3.0.21.