microsoft / msagljs

A JavaScript graph layout engine: port of MSAGL
https://microsoft.github.io/msagljs/
MIT License
146 stars 15 forks source link

Edge Router Exception Due to Overlapping Nodes #90

Closed wolfmcnally closed 1 month ago

wolfmcnally commented 1 month ago

I am using msagljs/core directly, and specifically the SplineRouter class. My nodes are being positioned by a graph layout algorithm not in msagljs, but that has other features I need. This layout algorithm, being incremental, can result in layouts where some nodes overlap.

From what I can see, msagljs seems designed to handle these cases gracefully, (including values like OverlapDetected and ContinueOnOverlaps. However, the following test case fails:

test('overlap test', () => {
    const g = GeomGraph.mk('graph', Rectangle.mkEmpty());
    const as = g.graph.addNode(new Node('a'));
    const a = new GeomNode(as);
    a.boundaryCurve = CurveFactory.mkRectangleWithRoundedCorners(20, 20, 3, 3, new Point(0, 0))

    const bs = g.graph.addNode(new Node('b'));
    const b = new GeomNode(bs);
    b.boundaryCurve = CurveFactory.mkRectangleWithRoundedCorners(40, 20, 3, 3, new Point(-10, 210))

    const es = g.graph.addNode(new Node('e'));
    const e = new GeomNode(es);
    e.boundaryCurve = CurveFactory.mkRectangleWithRoundedCorners(60, 20, 3, 3, new Point(0, 50))

    const fs = g.graph.addNode(new Node('f'));
    const f = new GeomNode(fs);
    f.boundaryCurve = CurveFactory.mkRectangleWithRoundedCorners(60, 20, 3, 3, new Point(20, 60))

    g.setEdge('a', 'b');

    const sr = SplineRouter.mk4(g, 2, 4, Math.PI / 6);
    sr.run();
    SvgDebugWriter.writeGeomGraph('./tmp/overlap_test.svg', g);
})

The two nodes that overlap are e and f. If I remove f I get:

image

If I add f and remove the edge ab, I get:

image

But if I add f and edge ab I get an exception thrown:

● overlap test

    TypeError: Cannot read properties of undefined (reading 'Ports')

      301 |   SetLoosePolylinesForAnywherePorts() {
      302 |     for (const [shape, cpl] of this.shapesToTightLooseCouples) {
    > 303 |       for (const port of shape.Ports) {
          |                                ^
      304 |         const isHport = port instanceof HookUpAnywhereFromInsidePort
      305 |
      306 |         if (isHport) {

      at SplineRouter.SetLoosePolylinesForAnywherePorts (modules/core/src/routing/splineRouter.ts:303:32)
      at SplineRouter.RouteOnRoot (modules/core/src/routing/splineRouter.ts:268:10)
      at SplineRouter.run (modules/core/src/routing/splineRouter.ts:212:10)
      at Object.<anonymous> (modules/core/test/routing/splineRouter.spec.ts:735:8)

This is because an undefined key is being placed into the SplineRouter.shapesToTightLooseCouples map:

  shapesToTightLooseCouples: Map<Shape, TightLooseCouple> = new Map<Shape, TightLooseCouple>()

It's quite problematic to ensure that none of my nodes overlap before calling the edge router, and I don't think throwing an exception here is intended behavior in any case.

levnach commented 1 month ago

It is fixed and I published a new package @msagl/core (1.1.20) image.

wolfmcnally commented 1 month ago

This appears to be working in 1.1.20. Thank you!