Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3b0bf7f
nodes: add the iscollapsed param to the node diplay filtering function
Math-R Oct 7, 2025
04056e8
feat: add grouping function for collapsed nodes in TrainrunSectionSer…
Math-R Oct 7, 2025
719f497
feat: implement TrainrunSection grouping in view layer for collapsed …
Math-R Oct 7, 2025
2c6fab2
fix: implement custom path calculation for collapsed node chains
Math-R Oct 7, 2025
871f667
fix : calc without temporal dto changes + avoid manual handroll
Math-R Oct 16, 2025
abf1f83
fixup! fix start node in groupTrainrunSectionsIntoChains()
emersion Oct 31, 2025
78e1a92
fixup! only create a single TrainrunSectionViewObject per collapsed c…
emersion Oct 31, 2025
42b4f8d
Add TrainrunSectionViewObject.getTrainrun()
emersion Oct 31, 2025
e9807c4
Store full trainrun section chain in TrainrunSectionViewObject
emersion Oct 31, 2025
c43b300
fixup! simplify text nodes position computation
emersion Oct 31, 2025
b8df4bb
fixup! drop unnecessary getCollapsedChainValueToShow() wrapper
emersion Oct 31, 2025
7ade86b
fixup! drop getAllSectionsInCollapsedChain()
emersion Oct 31, 2025
99bef05
fixup! fix path target position in TrainrunSectionViewObject.generate…
emersion Nov 5, 2025
1a466ae
Fix missing source arrival in TrainrunSectionViewObject.generateKey()
emersion Nov 5, 2025
6a3040c
fixup! use last section in TrainrunSectionViewObject.generateKey()
emersion Nov 5, 2025
c2d19b1
fixup! drop TrainrunSectionsView.getCollapsedChainPath()
emersion Nov 5, 2025
74da689
fixup! drop updateTrainrunSectionPathForCollapsedChain()
emersion Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/services/data/trainrun.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
BackwardTrainrunIterator,
NonStopTrainrunIterator,
TrainrunIterator,
ExpandedTrainrunIterator,
} from "../util/trainrun.iterator";
import {LogService} from "../../logger/log.service";
import {LabelService} from "./label.service";
Expand Down Expand Up @@ -799,6 +800,10 @@ export class TrainrunService {
public getBackwardNonStopIterator(node: Node, trainrunSection: TrainrunSection) {
return new BackwardNonStopTrainrunIterator(this.logService, node, trainrunSection);
}

public getExpandedIterator(node: Node, trainrunSection: TrainrunSection) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this function is used anymore

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, good catch i'll remove it

return new ExpandedTrainrunIterator(this.logService, node, trainrunSection);
}

// For each trainrun, get iterator from the smallest consecutiveTime.
public getRootIterators(): Map<number, TrainrunIterator> {
Expand Down
55 changes: 55 additions & 0 deletions src/app/services/data/trainrunsection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1469,4 +1469,59 @@ export class TrainrunSectionService implements OnDestroy {
trainrunSection.routeEdgeAndPlaceText();
trainrunSection.convertVec2DToPath();
}

/**
* Groups consecutive TrainrunSections that have collapsed nodes between them
* into chains. Each chain starts and ends with a non-collapsed node.
* Start and end nodes can be accessed via: sections[0].getSourceNode() and sections[sections.length - 1].getTargetNode()
* @param trainrunSections List of TrainrunSections to group
* @returns Array of section chains
*/
groupTrainrunSectionsIntoChains(trainrunSections: TrainrunSection[]): TrainrunSection[][] {
const groups: TrainrunSection[][] = [];
const visitedSections = new Set<number>();

trainrunSections.forEach((section) => {
if (visitedSections.has(section.getId())) {
return;
}

const backwardIterator = this.trainrunService.getBackwardIterator(
section.getTargetNode(),
section,
);
while (backwardIterator.hasNext() && backwardIterator.current().node.getIsCollapsed()) {
backwardIterator.next();
}
const startNode = backwardIterator.current().node;
const startSection = backwardIterator.current().trainrunSection;

// Build chain using TrainrunIterator to leverage existing graph traversal
const chain: TrainrunSection[] = [];
const iterator = this.trainrunService.getIterator(startNode, startSection);

// Traverse the trainrun and collect sections with collapsed intermediate nodes
while (iterator.hasNext()) {
const pair = iterator.next();

if (visitedSections.has(pair.trainrunSection.getId())) {
break; // Already processed this section
}
Comment on lines +1507 to +1509
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small question, this case should never happen right ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, normally this case should never happen, it's a relicat from previous development, but now we find the first section of the chain, thanks to the backward iterator.
But it can prevent infinite cycle if this iterator is changed in further development, maybe we can use this check to log an error and break to help and prevent regression in further development ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing an error sounds good to me.


chain.push(pair.trainrunSection);
visitedSections.add(pair.trainrunSection.getId());

// Stop if we reach a non-collapsed node (end of collapsed chain)
if (!pair.node.getIsCollapsed()) {
break;
}
}

if (chain.length > 0) {
groups.push(chain);
}
});

return groups;
}
}
14 changes: 14 additions & 0 deletions src/app/services/util/trainrun.iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,17 @@ export class BackwardNonStopTrainrunIterator extends BackwardTrainrunIterator {
return super.next();
}
}

export class ExpandedTrainrunIterator extends TrainrunIterator {
public next(): TrainrunSectionNodePair {
// Continue traversing only if the current node is collapsed
// Stop when we reach an expanded (non-collapsed) node
if (!this.pointerElement.node.getIsCollapsed()) {
// The trainrun has reached an expanded (non-collapsed) node and break the forward iteration
this.currentElement = Object.assign({}, this.pointerElement);
this.pointerElement = new TrainrunSectionNodePair(undefined, undefined);
return this.currentElement;
}
return super.next();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ export class ConnectionsView {
}

const node: Node = this.editorView.getNodeFromConnection(con);

// filter if node is collapsed - do not show connections for collapsed nodes
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit: why the comment here and not in transitions.view.ts?

if (node.getIsCollapsed()) {
return false;
}

const trainrunSection1: TrainrunSection = node.getPort(con.getPortId1()).getTrainrunSection();
const trainrunSection2: TrainrunSection = node.getPort(con.getPortId2()).getTrainrunSection();
const filterTrainrun1 = this.editorView.filterTrainrun(trainrunSection1.getTrainrun());
Expand Down
44 changes: 22 additions & 22 deletions src/app/view/editor-main-view/data-views/d3.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,21 @@ export class D3Utils {
d3.selectAll(StaticDomTags.EDGE_LINE_ARROW_DOM_REF)
.filter(
(d: TrainrunSectionViewObject) =>
d !== undefined && d.trainrunSection.getTrainrunId() === trainrunSection.getTrainrunId(),
d !== undefined && d.getTrainrun().getId() === trainrunSection.getTrainrunId(),
)
.classed(StaticDomTags.TAG_HOVER, true);

d3.selectAll(StaticDomTags.EDGE_LINE_DOM_REF)
.filter(
(d: TrainrunSectionViewObject) =>
d !== undefined && d.trainrunSection.getTrainrunId() === trainrunSection.getTrainrunId(),
d !== undefined && d.getTrainrun().getId() === trainrunSection.getTrainrunId(),
)
.classed(StaticDomTags.TAG_HOVER, true);

d3.selectAll(StaticDomTags.EDGE_ROOT_CONTAINER_DOM_REF)
.filter(
(d: TrainrunSectionViewObject) =>
d !== undefined && d.trainrunSection.getTrainrunId() === trainrunSection.getTrainrunId(),
d !== undefined && d.getTrainrun().getId() === trainrunSection.getTrainrunId(),
)
.classed(StaticDomTags.TAG_HOVER, true);

Expand Down Expand Up @@ -90,20 +90,20 @@ export class D3Utils {
d3.selectAll(StaticDomTags.EDGE_LINE_ARROW_DOM_REF)
.filter(
(d: TrainrunSectionViewObject) =>
d !== undefined && d.trainrunSection.getTrainrunId() === trainrunSection.getTrainrunId(),
d !== undefined && d.trainrunSections[0].getTrainrunId() === trainrunSection.getTrainrunId(),
)
.classed(StaticDomTags.TAG_HOVER, false);
d3.selectAll(StaticDomTags.EDGE_LINE_DOM_REF)
.filter(
(d: TrainrunSectionViewObject) =>
d !== undefined && d.trainrunSection.getTrainrunId() === trainrunSection.getTrainrunId(),
d !== undefined && d.trainrunSections[0].getTrainrunId() === trainrunSection.getTrainrunId(),
)
.classed(StaticDomTags.TAG_HOVER, false);

d3.selectAll(StaticDomTags.EDGE_ROOT_CONTAINER_DOM_REF)
.filter(
(d: TrainrunSectionViewObject) =>
d !== undefined && d.trainrunSection.getTrainrunId() === trainrunSection.getTrainrunId(),
d !== undefined && d.trainrunSections[0].getTrainrunId() === trainrunSection.getTrainrunId(),
)
.classed(StaticDomTags.TAG_HOVER, false);

Expand Down Expand Up @@ -347,7 +347,7 @@ export class D3Utils {
if (d === undefined) {
return false;
}
return d.trainrunSection.getId() === trainrunSection.getId();
return d.trainrunSections[0].getId() === trainrunSection.getId();
})
.classed(StaticDomTags.TAG_SELECTED, false)
.classed(StaticDomTags.TAG_HOVER, false)
Expand All @@ -359,7 +359,7 @@ export class D3Utils {
if (d === undefined) {
return false;
}
return d.trainrunSection.getId() === trainrunSection.getId();
return d.trainrunSections[0].getId() === trainrunSection.getId();
})
.classed(StaticDomTags.TAG_SELECTED, false)
.classed(StaticDomTags.TAG_HOVER, false)
Expand All @@ -371,7 +371,7 @@ export class D3Utils {
if (d === undefined) {
return false;
}
return d.trainrunSection.getId() === trainrunSection.getId();
return d.trainrunSections[0].getId() === trainrunSection.getId();
})
.classed(StaticDomTags.TAG_SELECTED, false)
.classed(StaticDomTags.TAG_HOVER, false)
Expand All @@ -383,7 +383,7 @@ export class D3Utils {
if (d === undefined) {
return false;
}
return d.trainrunSection.getId() === trainrunSection.getId();
return d.trainrunSections[0].getId() === trainrunSection.getId();
})
.classed(StaticDomTags.EDGE_LINE_GRAYEDOUT, true);

Expand All @@ -394,8 +394,8 @@ export class D3Utils {
return false;
}
return (
d.trainrunSection.getId() === trainrunSection.getId() &&
d.trainrunSection.getSourceNodeId() === grayoutEdgeLinePinNode.getId()
d.trainrunSections[0].getId() === trainrunSection.getId() &&
d.trainrunSections[0].getSourceNodeId() === grayoutEdgeLinePinNode.getId()
);
})
.classed(StaticDomTags.EDGE_LINE_GRAYEDOUT, true);
Expand All @@ -405,8 +405,8 @@ export class D3Utils {
return false;
}
return (
d.trainrunSection.getId() === trainrunSection.getId() &&
d.trainrunSection.getTargetNodeId() === grayoutEdgeLinePinNode.getId()
d.trainrunSections[0].getId() === trainrunSection.getId() &&
d.trainrunSections[0].getTargetNodeId() === grayoutEdgeLinePinNode.getId()
);
})
.classed(StaticDomTags.EDGE_LINE_GRAYEDOUT, true);
Expand All @@ -419,7 +419,7 @@ export class D3Utils {
if (d === undefined) {
return false;
}
return d.trainrunSection.getId() === trainrunSection.getId();
return d.trainrunSections[0].getId() === trainrunSection.getId();
})
.classed(StaticDomTags.TAG_SELECTED, true)
.classed(StaticDomTags.TAG_HOVER, false)
Expand All @@ -431,7 +431,7 @@ export class D3Utils {
if (d === undefined) {
return false;
}
return d.trainrunSection.getId() === trainrunSection.getId();
return d.trainrunSections[0].getId() === trainrunSection.getId();
})
.classed(StaticDomTags.TAG_SELECTED, true)
.classed(StaticDomTags.TAG_HOVER, false)
Expand All @@ -443,7 +443,7 @@ export class D3Utils {
if (d === undefined) {
return false;
}
return d.trainrunSection.getId() === trainrunSection.getId();
return d.trainrunSections[0].getId() === trainrunSection.getId();
})
.classed(StaticDomTags.TAG_SELECTED, true)
.classed(StaticDomTags.TAG_HOVER, false)
Expand All @@ -455,7 +455,7 @@ export class D3Utils {
if (d === undefined) {
return false;
}
return d.trainrunSection.getId() === trainrunSection.getId();
return d.trainrunSections[0].getId() === trainrunSection.getId();
})
.classed(StaticDomTags.EDGE_LINE_GRAYEDOUT, false);

Expand All @@ -466,8 +466,8 @@ export class D3Utils {
return false;
}
return (
d.trainrunSection.getId() === trainrunSection.getId() &&
d.trainrunSection.getSourceNodeId() === grayoutEdgeLinePinNode.getId()
d.trainrunSections[0].getId() === trainrunSection.getId() &&
d.trainrunSections[0].getSourceNodeId() === grayoutEdgeLinePinNode.getId()
);
})
.classed(StaticDomTags.EDGE_LINE_GRAYEDOUT, false);
Expand All @@ -477,8 +477,8 @@ export class D3Utils {
return false;
}
return (
d.trainrunSection.getId() === trainrunSection.getId() &&
d.trainrunSection.getTargetNodeId() === grayoutEdgeLinePinNode.getId()
d.trainrunSections[0].getId() === trainrunSection.getId() &&
d.trainrunSections[0].getTargetNodeId() === grayoutEdgeLinePinNode.getId()
);
})
.classed(StaticDomTags.EDGE_LINE_GRAYEDOUT, false);
Expand Down
2 changes: 1 addition & 1 deletion src/app/view/editor-main-view/data-views/editor.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export class EditorView implements SVGMouseControllerObserver {
this.nodesView = new NodesView(this);
this.transitionsView = new TransitionsView(this);
this.connectionsView = new ConnectionsView(this);
this.trainrunSectionsView = new TrainrunSectionsView(this);
this.trainrunSectionsView = new TrainrunSectionsView(this, trainrunSectionService);
this.trainrunSectionPreviewLineView = new TrainrunSectionPreviewLineView(
nodeService,
filterService,
Expand Down
2 changes: 1 addition & 1 deletion src/app/view/editor-main-view/data-views/nodes.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class NodesView {
}

filterNodesToDisplay(node: Node): boolean {
return this.editorView.isNodeVisible(node);
return this.editorView.isNodeVisible(node) && !node.getIsCollapsed();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: isCollapsed() would be nice instead of getIsCollapsed()

}

displayNodes(inputNodes: Node[]) {
Expand Down
Loading
Loading