From 3f78d2f5227c7acd476f0c91ebc2a76f61f92ca4 Mon Sep 17 00:00:00 2001 From: Adrian Egli Date: Mon, 4 Mar 2024 13:22:50 +0100 Subject: [PATCH 01/12] 10 bug graphic timetable streckengrafik rendering performance issue (#12) Performance issue fixed: - Streckengrafik (graphical timetable) - Editor --- .../grid/time-grid/time-grid.component.ts | 4 +- .../time-slider/time-slider.component.ts | 2 +- .../components/streckengrafik.component.html | 352 +++++++++--------- .../components/streckengrafik.component.ts | 43 ++- .../train-run-item.component.html | 56 +-- .../train-run-item.component.ts | 81 +++- .../trainrun-node.component.html | 2 +- ...run-section-stops-component.component.html | 34 +- ...n-run-section-stops-component.component.ts | 8 + .../train-run-section.component.html | 27 -- .../train-run-section.component.scss | 6 + .../train-run-section.component.ts | 103 ++--- .../train-run/train-run.component.html | 1 + .../train-run/train-run.component.ts | 10 +- .../streckengrafik-display-element.service.ts | 2 +- 15 files changed, 426 insertions(+), 305 deletions(-) diff --git a/src/app/streckengrafik/components/grid/time-grid/time-grid.component.ts b/src/app/streckengrafik/components/grid/time-grid/time-grid.component.ts index ac2c62d7..c83b58b2 100644 --- a/src/app/streckengrafik/components/grid/time-grid/time-grid.component.ts +++ b/src/app/streckengrafik/components/grid/time-grid/time-grid.component.ts @@ -61,10 +61,10 @@ export class TimeGridComponent this.timesTicksIndices = []; } this.sliderChangeInfo = sliderChangeInfo; - this.render(); if (changed) { this.cd.markForCheck(); } + this.render(); }); this.viewBoxService @@ -77,10 +77,10 @@ export class TimeGridComponent this.viewBoxChangeInfo.x !== viewBoxChangeInfo.x || this.viewBoxChangeInfo.y !== viewBoxChangeInfo.y; this.viewBoxChangeInfo = viewBoxChangeInfo; - this.render(); if (changed) { this.cd.markForCheck(); } + this.render(); }); } diff --git a/src/app/streckengrafik/components/slider/time-slider/time-slider.component.ts b/src/app/streckengrafik/components/slider/time-slider/time-slider.component.ts index dced0dee..86be626a 100644 --- a/src/app/streckengrafik/components/slider/time-slider/time-slider.component.ts +++ b/src/app/streckengrafik/components/slider/time-slider/time-slider.component.ts @@ -90,10 +90,10 @@ export class TimeSliderComponent this.timesTicksIndices = []; } this.sliderChangeInfo = sliderChangeInfo; - this.render(); if (changed) { this.cd.markForCheck(); } + this.render(); }); this.viewBoxService diff --git a/src/app/streckengrafik/components/streckengrafik.component.html b/src/app/streckengrafik/components/streckengrafik.component.html index 3bdede78..0cce5300 100644 --- a/src/app/streckengrafik/components/streckengrafik.component.html +++ b/src/app/streckengrafik/components/streckengrafik.component.html @@ -1,183 +1,195 @@
- - -
-
-
- -
-
-
- -
-
-
- - - - - - -
-
-
+ + +
+
+
+ +
+
+
+ +
+
+
+ + + + + + +
+
+
- -
- - - - -
+ +
+ + + + +
-
- - - - - - - - - - - - -
+
+ + + + + + + + + + + + -
- - - - -
-
+ + + - - - - - - +
+ + + + +
+
- - - - -
- + + + - - + -
- - + + + + + + +
+ + + + +
+
diff --git a/src/app/streckengrafik/components/streckengrafik.component.ts b/src/app/streckengrafik/components/streckengrafik.component.ts index 6e199bce..05e52a8b 100644 --- a/src/app/streckengrafik/components/streckengrafik.component.ts +++ b/src/app/streckengrafik/components/streckengrafik.component.ts @@ -20,7 +20,9 @@ import {SliderChangeInfo} from "../model/util/sliderChangeInfo"; import {TimeSliderService} from "../services/time-slider.service"; import {UpdateCounterTriggerSerivce} from "../services/util/update-counter.service"; import {Sg4ToggleTrackOccupierService} from "../services/sg-4-toggle-track-occupier.service"; -import {StreckengrafikDisplayElementService} from "../services/util/streckengrafik-display-element.service"; +import { + StreckengrafikDisplayElementService +} from "../services/util/streckengrafik-display-element.service"; import {StreckengrafikDrawingContext} from "../model/util/streckengrafik.drawing.context"; @Component({ @@ -29,8 +31,7 @@ import {StreckengrafikDrawingContext} from "../model/util/streckengrafik.drawing styleUrls: ["./streckengrafik.component.scss"], }) export class StreckengrafikComponent - implements OnInit, OnDestroy, AfterViewInit -{ + implements OnInit, OnDestroy, AfterViewInit { @ViewChild("svg") svgRef: ElementRef; viewBox: string; @@ -48,6 +49,9 @@ export class StreckengrafikComponent private oldResizeChangeInfo: ResizeChangeInfo = new ResizeChangeInfo(-1, -1); private oldRect: DOMRect = undefined; + private doShowTrainruns = false; + private isLoading = true; + constructor( private readonly timeSliderService: TimeSliderService, private readonly viewBoxService: ViewBoxService, @@ -57,7 +61,8 @@ export class StreckengrafikComponent private resizeService: ResizeService, private streckengrafikDisplayElementService: StreckengrafikDisplayElementService, private ngZone: NgZone, - ) {} + ) { + } ngOnInit(): void { this.oldRect = undefined; @@ -79,12 +84,15 @@ export class StreckengrafikComponent this.yZoom = this.sliderChangeInfo.zoom; this.renderViewBox(); }); + + this.doShowTrainruns = false; } ngAfterViewInit() { this.ngZone.runOutsideAngular(() => { this.triggeredOnResizeCheck(); }); + this.onResize(); } ngOnDestroy(): void { @@ -92,6 +100,10 @@ export class StreckengrafikComponent this.destroyed$.complete(); } + getIsLoading(): boolean { + return !this.doShowTrainruns;//this.isLoading; + } + onResetButton() { this.timeSliderService.reset(); } @@ -231,9 +243,20 @@ export class StreckengrafikComponent .pipe(takeUntil(this.destroyed$)) .subscribe(() => { this.onResize(); + + // lazzy loading ... + if (!this.doShowTrainruns) { + this.doShowTrainruns = true; + this.cd.markForCheck(); + this.cd.detectChanges(); + } }); } + getShowTrainruns(): boolean { + return this.doShowTrainruns; + } + toggleDisplayTools() { this.disabledDisplayTools = !this.disabledDisplayTools; } @@ -258,8 +281,12 @@ export class StreckengrafikComponent } private renderViewBox() { + if (this.viewBoxChangeInfo.width === 0 && this.viewBoxChangeInfo.height === 0) { + return; + } + let viewBox = ""; if (this.pathAlignmentHorizontal) { - this.viewBox = + viewBox = "0 " + this.sliderChangeInfo.move + " " + @@ -267,7 +294,7 @@ export class StreckengrafikComponent " " + this.viewBoxChangeInfo.height; } else { - this.viewBox = + viewBox = " " + this.sliderChangeInfo.move + " " + @@ -276,6 +303,10 @@ export class StreckengrafikComponent " " + this.viewBoxChangeInfo.height; } + + if (this.viewBox !== viewBox) { + this.viewBox = viewBox; + } } private render(width: number, height: number) { diff --git a/src/app/streckengrafik/components/train-run-item/train-run-item.component.html b/src/app/streckengrafik/components/train-run-item/train-run-item.component.html index 69e227a2..76e48a1b 100644 --- a/src/app/streckengrafik/components/train-run-item/train-run-item.component.html +++ b/src/app/streckengrafik/components/train-run-item/train-run-item.component.html @@ -1,37 +1,39 @@ - - - + + + - - - - - - + > + + + + + + + - + diff --git a/src/app/streckengrafik/components/train-run-item/train-run-item.component.ts b/src/app/streckengrafik/components/train-run-item/train-run-item.component.ts index 93438967..83d6f627 100644 --- a/src/app/streckengrafik/components/train-run-item/train-run-item.component.ts +++ b/src/app/streckengrafik/components/train-run-item/train-run-item.component.ts @@ -1,10 +1,10 @@ -import {Component, Input, OnDestroy, OnInit} from "@angular/core"; +import {ChangeDetectorRef, Component, Input, NgZone, OnDestroy, OnInit} from "@angular/core"; import {SgTrainrun} from "../../model/streckengrafik-model/sg-trainrun"; import {SgTrainrunItem} from "../../model/streckengrafik-model/sg-trainrun-item"; import {TimeSliderService} from "../../services/time-slider.service"; import {takeUntil} from "rxjs/operators"; import {SliderChangeInfo} from "../../model/util/sliderChangeInfo"; -import {Subject} from "rxjs"; +import {interval, Subject} from "rxjs"; import {ViewBoxChangeInfo} from "../../model/util/viewBoxChangeInfo"; import {ViewBoxService} from "../../services/util/view-box.service"; import * as d3 from "d3"; @@ -21,11 +21,16 @@ import { styleUrls: ["./train-run-item.component.scss"], }) export class TrainRunItemComponent - implements OnInit, OnDestroy, UpdateCounterHandler -{ + implements OnInit, OnDestroy, UpdateCounterHandler { @Input() trainrun: SgTrainrun; + @Input() + horizontal = true; + + @Input() + doShowTrainruns = false; + public yZoom = 0; public yMove = 0; public offsets: number[] = []; @@ -38,13 +43,30 @@ export class TrainRunItemComponent private fullDetailRenderingUpdateCounter = 0; private updateCounterController: UpdateCounterController = undefined; private recalc: boolean; + private internalDoShowTrainruns = false; constructor( private readonly timeSliderService: TimeSliderService, private readonly viewBoxService: ViewBoxService, private readonly updateCounterTriggerSerivce: UpdateCounterTriggerSerivce, + private readonly cd: ChangeDetectorRef, + private readonly ngZone: NgZone, ) { // use ngOnInit because @Input this.trainrun is used + this.ngZone.runOutsideAngular(() => { + const stopLoop$ = new Subject(); + interval(50) + .pipe(takeUntil(stopLoop$)) + .subscribe(() => { + if (!this.internalDoShowTrainruns) { + this.internalDoShowTrainruns = true; + this.cd.markForCheck(); + this.cd.detectChanges(); + stopLoop$.next(); + stopLoop$.complete(); + } + }); + }); } ngOnInit(): void { @@ -71,6 +93,10 @@ export class TrainRunItemComponent this.viewBoxChangeInfo = viewBoxChangeInfo; this.doDelayedExtraBound(); }); + + if (this.doShowTrainruns) { + this.internalDoShowTrainruns = true; + } } ngOnDestroy(): void { @@ -90,6 +116,53 @@ export class TrainRunItemComponent return "" + path.getStartposition(); } + isElementNotFrustumCulled(item: SgTrainrunItem, offset: number, yZoom: number): boolean { + if (!this.internalDoShowTrainruns) { + return false; + } + if (!this.horizontal) { + return true; + } + if (this.viewBoxChangeInfo.height === 0 && this.viewBoxChangeInfo.width) { + return false; + } + // extend view box for frustum culling (the 2 * yZoom extra space is just a heuristics - might + // thus must be more calculated based on pixel height !!! + const fromTime = this.viewBoxChangeInfo.y - 2 * yZoom; + const toTime = this.viewBoxChangeInfo.height + this.viewBoxChangeInfo.y + 2 * yZoom; + let fromPoint = 0; + let toPoint = 0; + if (item.isNode()) { + const node = item.getTrainrunNode(); + fromPoint = (node.departureTime + offset) * yZoom; + toPoint = (node.arrivalTime + offset + node.minimumHeadwayTime) * yZoom; + if (node.isEndNode()) { + if (!item.getPathNode().trackOccupier) { + return false; + } + if (node.unusedForTurnaround) { + return false; + } + fromPoint -= 2 * this.trainrun.frequency * yZoom; + toPoint += 2 * this.trainrun.frequency * yZoom; + } + if (!item.getPathNode().trackOccupier) { + if (node.departureTime === node.arrivalTime) { + return false; + } + } + + } + if (item.isSection()) { + const ts = item.getTrainrunSection(); + fromPoint = (ts.departureTime + offset) * yZoom; + toPoint = (ts.arrivalTime + offset + ts.minimumHeadwayTime) * yZoom; + } + return (fromPoint >= fromTime && fromPoint <= toTime) || + (toPoint >= fromTime && toPoint <= toTime) || + (fromPoint <= fromTime && toPoint >= toTime); + } + getId(trainrun: SgTrainrun, trainrunItem: SgTrainrunItem) { return ( "streckengrafik_trainrun_item_" + diff --git a/src/app/streckengrafik/components/train-run-node/trainrun-node.component.html b/src/app/streckengrafik/components/train-run-node/trainrun-node.component.html index 91d5930c..f9b3bc0d 100644 --- a/src/app/streckengrafik/components/train-run-node/trainrun-node.component.html +++ b/src/app/streckengrafik/components/train-run-node/trainrun-node.component.html @@ -12,7 +12,7 @@ diff --git a/src/app/streckengrafik/components/train-run-section-stops-component/train-run-section-stops-component.component.html b/src/app/streckengrafik/components/train-run-section-stops-component/train-run-section-stops-component.component.html index cf9c72bc..0d3543fa 100644 --- a/src/app/streckengrafik/components/train-run-section-stops-component/train-run-section-stops-component.component.html +++ b/src/app/streckengrafik/components/train-run-section-stops-component/train-run-section-stops-component.component.html @@ -1,18 +1,20 @@ - - - - {{ getNumberOfStops() }} - - + + + + + {{ getNumberOfStops() }} + + + diff --git a/src/app/streckengrafik/components/train-run-section-stops-component/train-run-section-stops-component.component.ts b/src/app/streckengrafik/components/train-run-section-stops-component/train-run-section-stops-component.component.ts index f6aa1709..3dd7b533 100644 --- a/src/app/streckengrafik/components/train-run-section-stops-component/train-run-section-stops-component.component.ts +++ b/src/app/streckengrafik/components/train-run-section-stops-component/train-run-section-stops-component.component.ts @@ -60,6 +60,14 @@ export class TrainRunSectionStopsComponentComponent { return retTag; } + hasStopElements(): boolean { + if (!this.trainrunItem.isSection()) { + return false; + } + const ps = this.trainrunItem.getTrainrunSection(); + return ps.numberOfStops > 0; + } + getStopElements(): Vec2D[] { if (!this.trainrunItem.isSection()) { return []; diff --git a/src/app/streckengrafik/components/train-run-section/train-run-section.component.html b/src/app/streckengrafik/components/train-run-section/train-run-section.component.html index 58ea31e1..87cb1283 100644 --- a/src/app/streckengrafik/components/train-run-section/train-run-section.component.html +++ b/src/app/streckengrafik/components/train-run-section/train-run-section.component.html @@ -25,15 +25,6 @@ - - - - - - (); private updateCounterController: UpdateCounterController = undefined; @@ -94,6 +95,7 @@ export class TrainRunSectionComponent this.yZoom = sliderChangeInfo.zoom; this.yMove = sliderChangeInfo.move; this.recalc = sliderChangeInfo.recalc; + this.cd.markForCheck(); this.doDelayedRendering(); }); @@ -103,6 +105,7 @@ export class TrainRunSectionComponent .subscribe((viewBoxChangeInfo) => { this.viewBoxChangeInfo = viewBoxChangeInfo; this.cd.markForCheck(); + this.doDelayedRendering(); }); this.sg4ToggleTrackOccupierService @@ -118,6 +121,7 @@ export class TrainRunSectionComponent .subscribe(() => { this.cd.markForCheck(); }); + } ngOnDestroy(): void { @@ -137,7 +141,7 @@ export class TrainRunSectionComponent } else { const param: InformSelectedTrainrunClick = { trainrunSectionId: - this.trainrunItem.getTrainrunSection().trainrunSectionId, + this.trainrunItem.getTrainrunSection().trainrunSectionId, open: true, }; this.trainrunSectionService.clickSelectedTrainrunSection(param); @@ -152,7 +156,7 @@ export class TrainRunSectionComponent " trainrunBranchType_" + TrainrunBranchType[ this.trainrunItem.getTrainrunSection().trainrunBranchType - ]; + ]; return retTag; } @@ -308,15 +312,15 @@ export class TrainRunSectionComponent private showArrivalBranch() { if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchWithSection || + TrainrunBranchType.ArrivalBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchFilter + TrainrunBranchType.ArrivalBranchFilter ) { return true; } if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchOnly && + TrainrunBranchType.ArrivalBranchOnly && !this.trainrunItem.backward && this.trainrunItem.getPathSection().arrivalPathNode && this.trainrunItem.getPathSection().arrivalPathNode.trackOccupier @@ -325,7 +329,7 @@ export class TrainRunSectionComponent } return ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchOnly && + TrainrunBranchType.ArrivalBranchOnly && this.trainrunItem.backward && this.trainrunItem.getPathSection().departurePathNode && this.trainrunItem.getPathSection().departurePathNode.trackOccupier @@ -335,15 +339,15 @@ export class TrainRunSectionComponent private showDepartureBranch() { if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchWithSection || + TrainrunBranchType.DepartureBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchFilter + TrainrunBranchType.DepartureBranchFilter ) { return true; } if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchOnly && + TrainrunBranchType.DepartureBranchOnly && !this.trainrunItem.backward && this.trainrunItem.getPathSection().departurePathNode && this.trainrunItem.getPathSection().departurePathNode.trackOccupier @@ -352,7 +356,7 @@ export class TrainRunSectionComponent } return ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchOnly && + TrainrunBranchType.DepartureBranchOnly && this.trainrunItem.backward && this.trainrunItem.getPathSection().arrivalPathNode && this.trainrunItem.getPathSection().arrivalPathNode.trackOccupier @@ -504,11 +508,11 @@ export class TrainRunSectionComponent let orgTime = this.trainrunItem.departureTime; if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchWithSection || + TrainrunBranchType.ArrivalBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchOnly || + TrainrunBranchType.ArrivalBranchOnly || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchFilter + TrainrunBranchType.ArrivalBranchFilter ) { orgTime = this.trainrunItem.arrivalTime; } @@ -527,11 +531,11 @@ export class TrainRunSectionComponent getArrivalText(): string { if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchWithSection || + TrainrunBranchType.ArrivalBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchOnly || + TrainrunBranchType.ArrivalBranchOnly || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchFilter + TrainrunBranchType.ArrivalBranchFilter ) { if (this.trainrunItem.backward) { if ( @@ -552,11 +556,11 @@ export class TrainRunSectionComponent } if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchWithSection || + TrainrunBranchType.DepartureBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchOnly || + TrainrunBranchType.DepartureBranchOnly || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchFilter + TrainrunBranchType.DepartureBranchFilter ) { if (this.trainrunItem.backward) { if ( @@ -597,11 +601,11 @@ export class TrainRunSectionComponent if (!this.filterService.isFilterShowNonStopTimeEnabled()) { if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchWithSection || + TrainrunBranchType.ArrivalBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchOnly || + TrainrunBranchType.ArrivalBranchOnly || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchFilter + TrainrunBranchType.ArrivalBranchFilter ) { if (this.trainrunItem.backward) { if ( @@ -627,11 +631,11 @@ export class TrainRunSectionComponent } if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchWithSection || + TrainrunBranchType.DepartureBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchOnly || + TrainrunBranchType.DepartureBranchOnly || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchFilter + TrainrunBranchType.DepartureBranchFilter ) { if (this.trainrunItem.backward) { if ( @@ -751,9 +755,9 @@ export class TrainRunSectionComponent this.trainrunSectionService.getSelectedTrainrunSection(); if ( selectedTrainrunSection.getTargetNode().getId() === - this.trainrunItem.getTrainrunSection().departureNodeId && + this.trainrunItem.getTrainrunSection().departureNodeId && selectedTrainrunSection.getSourceNode().getId() === - this.trainrunItem.getTrainrunSection().arrivalNodeId + this.trainrunItem.getTrainrunSection().arrivalNodeId ) { if (sgTrainrunItem.backward) { parameter.trainrunSectionText = TrainrunSectionText.SourceArrival; @@ -763,9 +767,9 @@ export class TrainRunSectionComponent } if ( selectedTrainrunSection.getTargetNode().getId() === - this.trainrunItem.getTrainrunSection().arrivalNodeId && + this.trainrunItem.getTrainrunSection().arrivalNodeId && selectedTrainrunSection.getSourceNode().getId() === - this.trainrunItem.getTrainrunSection().departureNodeId + this.trainrunItem.getTrainrunSection().departureNodeId ) { if (sgTrainrunItem.backward) { parameter.trainrunSectionText = TrainrunSectionText.TargetArrival; @@ -798,9 +802,9 @@ export class TrainRunSectionComponent this.trainrunSectionService.getSelectedTrainrunSection(); if ( selectedTrainrunSection.getTargetNode().getId() === - this.trainrunItem.getTrainrunSection().departureNodeId && + this.trainrunItem.getTrainrunSection().departureNodeId && selectedTrainrunSection.getSourceNode().getId() === - this.trainrunItem.getTrainrunSection().arrivalNodeId + this.trainrunItem.getTrainrunSection().arrivalNodeId ) { if (sgTrainrunItem.backward) { parameter.trainrunSectionText = TrainrunSectionText.TargetDeparture; @@ -810,9 +814,9 @@ export class TrainRunSectionComponent } if ( selectedTrainrunSection.getTargetNode().getId() === - this.trainrunItem.getTrainrunSection().arrivalNodeId && + this.trainrunItem.getTrainrunSection().arrivalNodeId && selectedTrainrunSection.getSourceNode().getId() === - this.trainrunItem.getTrainrunSection().departureNodeId + this.trainrunItem.getTrainrunSection().departureNodeId ) { if (sgTrainrunItem.backward) { parameter.trainrunSectionText = TrainrunSectionText.SourceDeparture; @@ -931,9 +935,9 @@ export class TrainRunSectionComponent if ( this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.Trainrun || + TrainrunBranchType.Trainrun || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.Filter + TrainrunBranchType.Filter ) { if ( this.streckengrafikDisplayElementService.isFilterStreckengrafikTimeNotFocusNorEnabled() @@ -946,7 +950,7 @@ export class TrainRunSectionComponent this.trainrunItem && this.trainrunItem.isSection() && this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.Trainrun + TrainrunBranchType.Trainrun ) { return this.isArrivalTimeTextFiltering(); } @@ -967,7 +971,7 @@ export class TrainRunSectionComponent this.trainrunItem && this.trainrunItem.isSection() && this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.Trainrun + TrainrunBranchType.Trainrun ) { return this.isDepatureTimeTextFiltering(true); } @@ -978,11 +982,11 @@ export class TrainRunSectionComponent (this.trainrunItem.getTrainrunSection().trainrunBranchType === TrainrunBranchType.ArrivalBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchWithSection || + TrainrunBranchType.DepartureBranchWithSection || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.ArrivalBranchOnly || + TrainrunBranchType.ArrivalBranchOnly || this.trainrunItem.getTrainrunSection().trainrunBranchType === - TrainrunBranchType.DepartureBranchOnly) + TrainrunBranchType.DepartureBranchOnly) ) { return this.isDepatureTimeTextFiltering(false); } @@ -996,7 +1000,7 @@ export class TrainRunSectionComponent this.updateCounterController.clear(); } if (this.recalc) { - this.updateCounterCallback(); + this.updateCounterCallback(true); } else { this.updateCounterController = new UpdateCounterController( this.fullDetailRenderingUpdateCounter, @@ -1009,8 +1013,8 @@ export class TrainRunSectionComponent return this.updateCounterTriggerSerivce; } - updateCounterCallback() { - this.delayedRendering = true; + updateCounterCallback(delayedRendering = true) { + this.delayedRendering = delayedRendering; this.cd.markForCheck(); } @@ -1031,5 +1035,6 @@ export class ScaledPath { constructor( public scaledPathFrom: Vec2D, public scaledPathTo: Vec2D, - ) {} + ) { + } } diff --git a/src/app/streckengrafik/components/train-run/train-run.component.html b/src/app/streckengrafik/components/train-run/train-run.component.html index 9c1a70f4..9b6be4cd 100644 --- a/src/app/streckengrafik/components/train-run/train-run.component.html +++ b/src/app/streckengrafik/components/train-run/train-run.component.html @@ -3,6 +3,7 @@ sbb-train-run-item [attr.id]="getId(trainrun)" [trainrun]="trainrun" + [doShowTrainruns]="getShowTrainruns()" (mouseover)="bringToFront(trainrun, $event)" > diff --git a/src/app/streckengrafik/components/train-run/train-run.component.ts b/src/app/streckengrafik/components/train-run/train-run.component.ts index 1b2f6b4c..31751976 100644 --- a/src/app/streckengrafik/components/train-run/train-run.component.ts +++ b/src/app/streckengrafik/components/train-run/train-run.component.ts @@ -1,4 +1,4 @@ -import {Component} from "@angular/core"; +import {Component, Input} from "@angular/core"; import {Observable} from "rxjs"; import {Sg8RenderService} from "../../services/sg-8-render.service"; import {SgTrainrun} from "../../model/streckengrafik-model/sg-trainrun"; @@ -11,12 +11,20 @@ import * as d3 from "d3"; styleUrls: ["./train-run.component.scss"], }) export class TrainRunComponent { + + @Input() + doShowTrainruns = false; + public trainrun$: Observable; constructor(private readonly sg8RenderService: Sg8RenderService) { this.trainrun$ = this.sg8RenderService.getTrainrun(); } + getShowTrainruns(): boolean { + return this.doShowTrainruns; + } + getId(trainrun: SgTrainrun) { return "streckengrafik_trainrun_item_" + trainrun.getId(); } diff --git a/src/app/streckengrafik/services/util/streckengrafik-display-element.service.ts b/src/app/streckengrafik/services/util/streckengrafik-display-element.service.ts index 2f847c40..28e6195a 100644 --- a/src/app/streckengrafik/services/util/streckengrafik-display-element.service.ts +++ b/src/app/streckengrafik/services/util/streckengrafik-display-element.service.ts @@ -1,5 +1,5 @@ import {Injectable} from "@angular/core"; -import {BehaviorSubject, Observable} from "rxjs"; +import {BehaviorSubject, Observable, Subject} from "rxjs"; @Injectable({ providedIn: "root", From 567f70df6b0ab3d6849fec37430bda0a025d35d8 Mon Sep 17 00:00:00 2001 From: Adrian Egli Date: Tue, 5 Mar 2024 22:12:28 +0100 Subject: [PATCH 02/12] 13 bug propagation issue reports lock and propagation does not work propery (#14) * fix the perlenkette * trainrun section services * trainrun section services * test fixed --- .../perlenkette-node.component.ts | 30 +-- .../perlenkette-section.component.html | 16 +- .../perlenkette-section.component.scss | 5 + .../perlenkette-section.component.ts | 80 ++++-- .../perlenkette/perlenkette.component.html | 6 +- .../data/trainrun-section-times.service.ts | 24 +- .../services/data/trainrunsection.service.ts | 230 ++++++++++++++---- src/app/services/ui/filter.service.ts | 11 +- .../util/trainrunsection.helper.spec.ts | 6 +- .../services/util/trainrunsection.helper.ts | 104 +++++--- .../editor-main-view/data-views/d3.utils.ts | 29 ++- .../data-views/trainrunsections.view.ts | 4 +- 12 files changed, 395 insertions(+), 150 deletions(-) diff --git a/src/app/perlenkette/perlenkette-node/perlenkette-node.component.ts b/src/app/perlenkette/perlenkette-node/perlenkette-node.component.ts index e3738bfa..c190e4a7 100644 --- a/src/app/perlenkette/perlenkette-node/perlenkette-node.component.ts +++ b/src/app/perlenkette/perlenkette-node/perlenkette-node.component.ts @@ -22,6 +22,8 @@ import {UiInteractionService} from "../../services/ui/ui.interaction.service"; export class PerlenketteNodeComponent implements OnInit { @Input() perlenketteNode: PerlenketteNode; @Input() perlenketteTrainrun: PerlenketteTrainrun; + @Input() isTopNode = false; + @Input() isBottomNode = false; @Output() signalIsBeingEdited = new EventEmitter(); @Output() signalHeightChanged = new EventEmitter(); @@ -35,7 +37,8 @@ export class PerlenketteNodeComponent implements OnInit { public trainrunService: TrainrunService, readonly filterService: FilterService, readonly uiInteractionService: UiInteractionService, - ) {} + ) { + } ngOnInit() { this.isExpanded = true; @@ -303,11 +306,8 @@ export class PerlenketteNodeComponent implements OnInit { toggleNonStop() { const node = this.nodeService.getNodeFromId(this.perlenketteNode.nodeId); const transition: Transition = this.perlenketteNode.transition; - if (transition !== undefined) { - this.nodeService.toggleNonStop( - this.perlenketteNode.nodeId, - transition.getId(), - ); + if (transition !== undefined && node !== undefined) { + this.nodeService.toggleNonStop(node.getId(), transition.getId()); this.trainrunService.trainrunsUpdated(); } } @@ -348,15 +348,15 @@ export class PerlenketteNodeComponent implements OnInit { item .getPerlenketteNode() .connections.forEach((connection: PerlenketteConnection) => { - const name = connection.categoryShortName + "" + connection.title; - maxTrainrunNameLen = Math.max( - 3 + connection.terminalStationBackward.length, - Math.max( - 3 + connection.terminalStation.length, - Math.max(name.length, maxTrainrunNameLen), - ), - ); - }); + const name = connection.categoryShortName + "" + connection.title; + maxTrainrunNameLen = Math.max( + 3 + connection.terminalStationBackward.length, + Math.max( + 3 + connection.terminalStation.length, + Math.max(name.length, maxTrainrunNameLen), + ), + ); + }); } }); diff --git a/src/app/perlenkette/perlenkette-section/perlenkette-section.component.html b/src/app/perlenkette/perlenkette-section/perlenkette-section.component.html index 6bf3ecc1..39345628 100644 --- a/src/app/perlenkette/perlenkette-section/perlenkette-section.component.html +++ b/src/app/perlenkette/perlenkette-section/perlenkette-section.component.html @@ -111,8 +111,8 @@ (click)="switchSectionView($event, 'leftDepartureTime')" >
-
+
+ - Streckengrafik + Editor + Fahrzeitvoreinstellungs (Heuristik) +
+ + + {{ option.name }} + + +
+ + + Streckengrafik Achsenskalierung (Distanz) -
+
Einstellungen {{ option.name }}
+ diff --git a/src/app/view/editor-properties-view-component/editor-properties-view.component.ts b/src/app/view/editor-properties-view-component/editor-properties-view.component.ts index fd3847eb..e7e10003 100644 --- a/src/app/view/editor-properties-view-component/editor-properties-view.component.ts +++ b/src/app/view/editor-properties-view-component/editor-properties-view.component.ts @@ -4,7 +4,10 @@ import {UiInteractionService} from "../../services/ui/ui.interaction.service"; import {SbbRadioChange} from "@sbb-esta/angular/radio-button"; import {ThemeBase} from "../themes/theme-base"; import {ThemeRegistration} from "../themes/theme-registration"; -import {StreckengrafikRenderingType} from "../themes/streckengrafik-rendering-type"; +import { + StreckengrafikRenderingType +} from "../themes/streckengrafik-rendering-type"; +import {TravelTimeCreationEstimatorType} from "../themes/editor-trainrun-traveltime-creator-type"; @Component({ selector: "sbb-editor-properties-view-component", @@ -58,16 +61,36 @@ export class EditorPropertiesViewComponent { streckengrafikRenderingTypeOptions = [ { name: "fahrzeitskaliert", - streckengrafikRenderingType: - StreckengrafikRenderingType.TimeScaledDistance, + title: "Die Streckengrafikabschnitte werden fahrzeitskaliert dargestellt, " + + "d.h. es wird angenommen, dass der ausgewählte Zug mit konstanter " + + "Geschwindigkeit verkehrt.", + streckengrafikRenderingType: StreckengrafikRenderingType.TimeScaledDistance, }, { name: "gleichmässig", + title: "Die Streckengrafikabschnitte werden gleichmässig skaliert dargestellt.", streckengrafikRenderingType: StreckengrafikRenderingType.UniformDistance, }, ]; activeStreckengrafikRenderingType: StreckengrafikRenderingType = null; + + travelTimeCreationEstimatorTypeOptions = [ + { + name: "Konstant 1min.", + title: "Übernimmt die Fahrzeit mit konstant 1min (Default).", + travelTimeCreationEstimatorType: TravelTimeCreationEstimatorType.Fixed, + }, + { + name: "Abschnittsfahrzeit", + title: "Übernimmt die max. Fahrzeit auf dem selben Abschnitt aller Züge " + + "gleicher Kategorie, sonst max. Fahrzeit aller Züge, sonst 1 Min.", + travelTimeCreationEstimatorType: TravelTimeCreationEstimatorType.RetrieveFromEdge, + }, + ]; + activeTravelTimeCreationEstimatorType: TravelTimeCreationEstimatorType = null; + + activeDarkBackgroundColor = EditorPropertiesViewComponent.DEFAULT_DARK_BACKGROUNDCOLOR; activeBackgroundColor = EditorPropertiesViewComponent.DEFAULT_BACKGROUNDCOLOR; @@ -80,6 +103,9 @@ export class EditorPropertiesViewComponent { this.activeColorTheme = activeTheme; this.activeStreckengrafikRenderingType = this.uiInteractionService.getActiveStreckengrafikRenderingType(); + this.activeTravelTimeCreationEstimatorType = + this.uiInteractionService.getActiveTravelTimeCreationEstimatorType(); + if (activeTheme.isDark) { this.activeDarkBackgroundColor = this.getHexColor( activeTheme.backgroundColor, @@ -107,6 +133,11 @@ export class EditorPropertiesViewComponent { this.uiInteractionService.setActiveStreckengrafikRenderingType(event.value); } + + onUpdateaTravelTimeCreationEstimatorType(event: SbbRadioChange) { + this.uiInteractionService.setActiveTravelTimeCreationEstimatorType(event.value); + } + colorPicked(value) { this.onUpdateColorTheme( new SbbRadioChange( diff --git a/src/app/view/themes/editor-trainrun-traveltime-creator-type.ts b/src/app/view/themes/editor-trainrun-traveltime-creator-type.ts new file mode 100644 index 00000000..ff27b05d --- /dev/null +++ b/src/app/view/themes/editor-trainrun-traveltime-creator-type.ts @@ -0,0 +1,4 @@ +export enum TravelTimeCreationEstimatorType { + Fixed, + RetrieveFromEdge, +} From b75e57007c9d6e18d46f873535f23e59fccebec7 Mon Sep 17 00:00:00 2001 From: Adrian Egli Date: Wed, 6 Mar 2024 15:11:21 +0100 Subject: [PATCH 07/12] Rendering issue hotfix (#21) --- .../editor-properties-view.component.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/view/editor-properties-view-component/editor-properties-view.component.scss b/src/app/view/editor-properties-view-component/editor-properties-view.component.scss index e69de29b..94d51516 100644 --- a/src/app/view/editor-properties-view-component/editor-properties-view.component.scss +++ b/src/app/view/editor-properties-view-component/editor-properties-view.component.scss @@ -0,0 +1,4 @@ +@import "../../../variables"; +@import "./../../view/editor-filter-view/editor-filter-view.component.scss"; +@import "../rastering/definitions"; + From ab7d8173169532965d22fe7a965398cdf2323594 Mon Sep 17 00:00:00 2001 From: Adrian Egli Date: Wed, 6 Mar 2024 17:33:02 +0100 Subject: [PATCH 08/12] bug fixed (#23) --- src/app/services/data/trainrunsection.service.ts | 6 +++++- .../trainrun-filter-tab.component.ts | 7 +++++++ .../trainrun-section-tab.component.ts | 13 +++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/app/services/data/trainrunsection.service.ts b/src/app/services/data/trainrunsection.service.ts index f64308ce..74c47143 100644 --- a/src/app/services/data/trainrunsection.service.ts +++ b/src/app/services/data/trainrunsection.service.ts @@ -952,7 +952,11 @@ export class TrainrunSectionService implements OnDestroy { const trainrunSections = this.getAllTrainrunSectionsForTrainrun(trainrunIdToCopyFrom); trainrunSections.forEach((trainrunSection) => { - this.copyTrainrunSectionAndAddToNodes(trainrunSection, newTrainrunId); + const newSection = this.copyTrainrunSectionAndAddToNodes(trainrunSection, newTrainrunId); + if (trainrunSection.selected()) { + trainrunSection.unselect(); + newSection.select(); + } }); } diff --git a/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-filter-tab/trainrun-filter-tab.component.ts b/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-filter-tab/trainrun-filter-tab.component.ts index 0c665332..5ba9a5bf 100644 --- a/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-filter-tab/trainrun-filter-tab.component.ts +++ b/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-filter-tab/trainrun-filter-tab.component.ts @@ -52,6 +52,13 @@ export class TrainrunFilterTabComponent implements OnInit, OnDestroy { .subscribe(() => { this.updateTrainrunLabelsAutoCompleteOptions(); }); + + this.trainrunSectionService.trainrunSections + .pipe(takeUntil(this.destroyed)) + .subscribe(() => { + this.initializeWithCurrentSelectedTrainrun(); + }); + this.updateTrainrunLabelsAutoCompleteOptions(); } diff --git a/src/app/view/dialogs/trainrun-and-section-dialog/trainrunsection-tab/trainrun-section-tab.component.ts b/src/app/view/dialogs/trainrun-and-section-dialog/trainrunsection-tab/trainrun-section-tab.component.ts index b1508b1c..49549989 100644 --- a/src/app/view/dialogs/trainrun-and-section-dialog/trainrunsection-tab/trainrun-section-tab.component.ts +++ b/src/app/view/dialogs/trainrun-and-section-dialog/trainrunsection-tab/trainrun-section-tab.component.ts @@ -22,7 +22,9 @@ import {Subject} from "rxjs"; import {LinePatternRefs} from "../../../../data-structures/business.data.structures"; import {StaticDomTags} from "../../../editor-main-view/data-views/static.dom.tags"; import {ColorRefType} from "../../../../data-structures/technical.data.structures"; -import {TrainrunSectionTimesService} from "../../../../services/data/trainrun-section-times.service"; +import { + TrainrunSectionTimesService +} from "../../../../services/data/trainrun-section-times.service"; export interface LeftAndRightTimeStructure { leftDepartureTime: number; @@ -93,6 +95,13 @@ export class TrainrunSectionTabComponent implements AfterViewInit, OnDestroy { this.resetOffsetAfterTrainrunChanged(); this.updateAllValues(); }); + this.trainrunSectionService.trainrunSections.pipe(takeUntil(this.destroyed)) + .subscribe(() => { + if (this.selectedTrainrunSection !== this.trainrunSectionService.getSelectedTrainrunSection()) { + this.resetOffsetAfterTrainrunChanged(); + this.updateAllValues(); + } + }); } updateAllValues() { @@ -301,7 +310,7 @@ export class TrainrunSectionTabComponent implements AfterViewInit, OnDestroy { if (this.trainrunDialogParameter.offset < 0) { this.trainrunSectionTimesService.setOffset( Math.ceil(Math.abs(this.trainrunDialogParameter.offset) / 60) * 60 - - Math.abs(this.trainrunDialogParameter.offset), + Math.abs(this.trainrunDialogParameter.offset), ); } else { this.trainrunSectionTimesService.setOffset( From 7c7d53e609b3f3bfa4b7fc67f7d2d72133a56289 Mon Sep 17 00:00:00 2001 From: Adrian Egli Date: Wed, 6 Mar 2024 21:35:17 +0100 Subject: [PATCH 09/12] reported bug fixed (#25) --- .../data/trainrun-section-times.service.ts | 10 ++++----- .../services/data/trainrunsection.service.ts | 6 ++++-- .../services/util/trainrunsection.helper.ts | 21 ++++++++++++------- .../trainrun-section-tab.component.html | 14 ++++++------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/app/services/data/trainrun-section-times.service.ts b/src/app/services/data/trainrun-section-times.service.ts index 4166f900..afb821a5 100644 --- a/src/app/services/data/trainrun-section-times.service.ts +++ b/src/app/services/data/trainrun-section-times.service.ts @@ -1,9 +1,6 @@ import {Injectable} from "@angular/core"; import {MathUtils} from "../../utils/math"; -import { - LeftAndRightElement, - TrainrunsectionHelper, -} from "../util/trainrunsection.helper"; +import {LeftAndRightElement, TrainrunsectionHelper,} from "../util/trainrunsection.helper"; import { LeftAndRightLockStructure, LeftAndRightTimeStructure, @@ -492,7 +489,7 @@ export class TrainrunSectionTimesService { this.initialLeftAndRightElement === LeftAndRightElement.LeftDeparture || this.initialLeftAndRightElement === LeftAndRightElement.RightArrival || this.initialLeftAndRightElement === - LeftAndRightElement.LeftRightTrainrunName + LeftAndRightElement.LeftRightTrainrunName ) { this.timeStructure.leftDepartureTime = (this.timeStructure.leftDepartureTime + this.offset) % 60; @@ -522,7 +519,7 @@ export class TrainrunSectionTimesService { this.initialLeftAndRightElement === LeftAndRightElement.LeftDeparture || this.initialLeftAndRightElement === LeftAndRightElement.RightArrival || this.initialLeftAndRightElement === - LeftAndRightElement.LeftRightTrainrunName + LeftAndRightElement.LeftRightTrainrunName ) { this.timeStructure.leftDepartureTime = (maxMinutes + this.timeStructure.leftDepartureTime - this.offset) % 60; @@ -601,6 +598,7 @@ export class TrainrunSectionTimesService { this.timeStructure, ), this.selectedTrainrunSection, + this.filterService.getTimeDisplayPrecision() ); } } diff --git a/src/app/services/data/trainrunsection.service.ts b/src/app/services/data/trainrunsection.service.ts index 74c47143..97009c9a 100644 --- a/src/app/services/data/trainrunsection.service.ts +++ b/src/app/services/data/trainrunsection.service.ts @@ -867,6 +867,7 @@ export class TrainrunSectionService implements OnDestroy { setTimeStructureToTrainrunSections( timeStructure: LeftAndRightTimeStructure, trainrunSection: TrainrunSection, + precision=0 ) { const newTotalTravelTime = timeStructure.travelTime; @@ -905,11 +906,12 @@ export class TrainrunSectionService implements OnDestroy { travelTimeFactor, nextPair.trainrunSection.getTravelTime(), isRightNodeNonStop, + precision ); trsTimeStructure.rightArrivalTime = - TrainrunsectionHelper.getRightArrivalTime(trsTimeStructure); + TrainrunsectionHelper.getRightArrivalTime(trsTimeStructure, precision); trsTimeStructure.rightDepartureTime = - TrainrunsectionHelper.getRightDepartureTime(trsTimeStructure); + TrainrunsectionHelper.getRightDepartureTime(trsTimeStructure, precision); const rightIsTarget = nextPair.node.getId() === nextPair.trainrunSection.getTargetNode().getId(); diff --git a/src/app/services/util/trainrunsection.helper.ts b/src/app/services/util/trainrunsection.helper.ts index 5a7cfc40..f0c0a91f 100644 --- a/src/app/services/util/trainrunsection.helper.ts +++ b/src/app/services/util/trainrunsection.helper.ts @@ -20,7 +20,8 @@ export enum LeftAndRightElement { } export class TrainrunsectionHelper { - constructor(private trainrunService: TrainrunService) {} + constructor(private trainrunService: TrainrunService) { + } static getSymmetricTime(time: number) { return time === 0 ? 0 : 60 - time; @@ -44,30 +45,36 @@ export class TrainrunsectionHelper { travelTimeFactor: number, trsTravelTime: number, isRightNodeNonStopTransit: boolean, + precision = 0 ): number { if (isRightNodeNonStopTransit) { - return Math.max(MathUtils.round(trsTravelTime * travelTimeFactor, 0), 1); + return Math.max(MathUtils.round(trsTravelTime * travelTimeFactor, 0), + 1.0 / Math.pow(10, precision)); } else { return Math.max( - MathUtils.round(totalTravelTime - summedTravelTime, 0), - 1, + MathUtils.round(totalTravelTime - summedTravelTime, precision), + 1.0 / Math.pow(10, precision), ); } } - static getRightArrivalTime(timeStructure: LeftAndRightTimeStructure): number { + static getRightArrivalTime( + timeStructure: LeftAndRightTimeStructure, + precision = 0 + ): number { return MathUtils.round( (timeStructure.leftDepartureTime + (timeStructure.travelTime % 60)) % 60, - 0, + precision, ); } static getRightDepartureTime( timeStructure: LeftAndRightTimeStructure, + precision = 0 ): number { return MathUtils.round( this.getSymmetricTime(timeStructure.rightArrivalTime), - 0, + precision, ); } diff --git a/src/app/view/dialogs/trainrun-and-section-dialog/trainrunsection-tab/trainrun-section-tab.component.html b/src/app/view/dialogs/trainrun-and-section-dialog/trainrunsection-tab/trainrun-section-tab.component.html index 9745db20..289478df 100644 --- a/src/app/view/dialogs/trainrun-and-section-dialog/trainrunsection-tab/trainrun-section-tab.component.html +++ b/src/app/view/dialogs/trainrun-and-section-dialog/trainrunsection-tab/trainrun-section-tab.component.html @@ -36,7 +36,7 @@ max="99" maxlength="2" spellcheck="false" - pattern="^[0-9]*" + pattern="^[0-9.]*" [(ngModel)]="numberOfStops" (change)="onNumberOfStopsChanged()" (mouseenter)="onMouseEnterNbrStopInput()" @@ -161,7 +161,7 @@ type="number" sbbInput spellcheck="false" - pattern="^[0-9]*" + pattern="^[0-9.]*" [(ngModel)]=" trainrunSectionTimesService.getTimeStructure().leftArrivalTime " @@ -205,7 +205,7 @@ type="number" sbbInput spellcheck="false" - pattern="^[0-9]*" + pattern="^[0-9.]*" [(ngModel)]=" trainrunSectionTimesService.getTimeStructure().leftDepartureTime " @@ -248,7 +248,7 @@ type="number" sbbInput spellcheck="false" - pattern="^[0-9]*" + pattern="^[0-9.]*" [(ngModel)]=" trainrunSectionTimesService.getTimeStructure().rightArrivalTime " @@ -291,7 +291,7 @@ type="number" sbbInput spellcheck="false" - pattern="^[0-9]*" + pattern="^[0-9.]*" [(ngModel)]=" trainrunSectionTimesService.getTimeStructure() .rightDepartureTime @@ -339,10 +339,10 @@ trainrunSectionTimesService.getHighlightTravelTimeElement() }" type="number" - min="1" + min="0.0001" sbbInput spellcheck="false" - pattern="^[0-9]*" + pattern="^[0-9.]*" minlength="1" [(ngModel)]=" trainrunSectionTimesService.getTimeStructure().travelTime From 7a09fc92e36df4ecb9897292fff31ea059fe9465 Mon Sep 17 00:00:00 2001 From: Adrian Egli Date: Mon, 18 Mar 2024 22:23:58 +0100 Subject: [PATCH 10/12] 27 feature request combine and split trainruns (#28) * feature added * lint issue fixed * issue fixed * fixed * style fix * doc * version update --- documentation/CREATE_TRAINRUN.md | 10 ++ package-lock.json | 4 +- package.json | 2 +- src/app/models/node.model.ts | 7 ++ .../services/data/trainrun.service.spec.ts | 2 +- src/app/services/data/trainrun.service.ts | 110 +++++++++++++++-- .../trainrun-filter-tab.component.ts | 2 +- .../trainrun-tab/trainrun-tab.component.ts | 2 +- .../data-views/editor.keyEvents.ts | 34 ++++-- .../data-views/editor.view.ts | 10 ++ .../editor-main-view/data-views/nodes.view.ts | 7 ++ .../data-views/static.dom.tags.ts | 3 + .../data-views/trainrunsections.view.scss | 16 +++ .../data-views/trainrunsections.view.ts | 113 +++++++++++------- .../data-views/transition.view.scss | 4 + .../data-views/transitions.view.ts | 13 +- .../editor-main-view.component.ts | 10 ++ 17 files changed, 285 insertions(+), 64 deletions(-) diff --git a/documentation/CREATE_TRAINRUN.md b/documentation/CREATE_TRAINRUN.md index f3d90331..57694fac 100644 --- a/documentation/CREATE_TRAINRUN.md +++ b/documentation/CREATE_TRAINRUN.md @@ -81,3 +81,13 @@ To switch a train from a stop to a non-stop at a node, follow these steps: signifies trainrun transitions (stop/non-stop). [2024-01-25-Toogle_Stop_NonStop_trainrun_at_node.webm](https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/assets/2674075/8a72350c-ed19-4395-8183-c33dfe824c5a) + +### Split / Combine two trainruns + +To split a train into two separate ones, you first have to select the train. Then you navigate to the node where you like to split the trainrun. Inside the node +the trainrun has to have a transition. Press CTRL and click with the mouse the "stop / non-stop toggle button". The trainrun gets split into two trains. + +To combine two trainruns, you have to select one of the two trains. Then you have to navigate to the node where the trainrun ends (or starts). Now you can draw +the new transition similar to creating a connection - but you have to press CTRL key and it must be hold pressed as long you are drawing a new transition. +Once you finish drawing the new transition, the both trains will be combined to one single trainrun. +Please have as well a look into [Create Connections](CREATE_CONNECTIONS.md). diff --git a/package-lock.json b/package-lock.json index 82a85f9f..b20fffd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "netzgrafik-frontend", - "version": "2.3.0", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "netzgrafik-frontend", - "version": "2.3.0", + "version": "2.4.0", "dependencies": { "@angular/animations": "^17.0.5", "@angular/cdk": "^17.0.1", diff --git a/package.json b/package.json index 7f3dfceb..f92deb61 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "local-test": "ng test --watch --code-coverage" }, "name": "netzgrafik-frontend", - "version": "2.3.0", + "version": "2.4.0", "files": [ "dist/*" ], diff --git a/src/app/models/node.model.ts b/src/app/models/node.model.ts index f74892c0..53bd8f59 100644 --- a/src/app/models/node.model.ts +++ b/src/app/models/node.model.ts @@ -620,6 +620,13 @@ export class Node { ); } + removeTransitionFromId(t: Transition) { + this.transitions = this.transitions.filter( + (transition) => + transition.getId() !== t.getId() + ); + } + removeConnectionFromTrainrunSection(trainrunSection: TrainrunSection) { let portId = trainrunSection.getSourcePortId(); if (this.getId() === trainrunSection.getTargetNodeId()) { diff --git a/src/app/services/data/trainrun.service.spec.ts b/src/app/services/data/trainrun.service.spec.ts index 6298e0b1..60aa4f64 100644 --- a/src/app/services/data/trainrun.service.spec.ts +++ b/src/app/services/data/trainrun.service.spec.ts @@ -224,7 +224,7 @@ describe("TrainrunService", () => { const t = ts.getTrainrun(); const n1 = nodeService.getNodeFromId(ts.getSourceNodeId()); const n2 = nodeService.getNodeFromId(ts.getTargetNodeId()); - const copied = trainrunService.duplicateTrainrun(t.getId()); + const copied = trainrunService.duplicateTrainrunAndSections(t.getId()); const ts1 = trainrunSectionService.getAllTrainrunSectionsForTrainrun( t.getId(), ); diff --git a/src/app/services/data/trainrun.service.ts b/src/app/services/data/trainrun.service.ts index 2ff6ab59..1a580f00 100644 --- a/src/app/services/data/trainrun.service.ts +++ b/src/app/services/data/trainrun.service.ts @@ -16,13 +16,13 @@ import {DataService} from "./data.service"; import {Node} from "../../models/node.model"; import {TrainrunSection} from "../../models/trainrunsection.model"; import {GeneralViewFunctions} from "../../view/util/generalViewFunctions"; -import { - NonStopTrainrunIterator, - TrainrunIterator, -} from "../util/trainrun.iterator"; +import {NonStopTrainrunIterator, TrainrunIterator,} from "../util/trainrun.iterator"; import {LogService} from "../../logger/log.service"; import {LabelService} from "./label.serivce"; import {FilterService} from "../ui/filter.service"; +import {Transition} from "../../models/transition.model"; +import {Port} from "../../models/port.model"; +import {Connection} from "../../models/connection.model"; @Injectable({ providedIn: "root", @@ -32,7 +32,7 @@ export class TrainrunService { trainrunsSubject = new BehaviorSubject([]); readonly trainruns = this.trainrunsSubject.asObservable(); - trainrunsStore: {trainruns: Trainrun[]} = {trainruns: []}; // store the data in memory + trainrunsStore: { trainruns: Trainrun[] } = {trainruns: []}; // store the data in memory private dataService: DataService = null; private nodeService: NodeService = null; @@ -42,7 +42,8 @@ export class TrainrunService { private logService: LogService, private labelService: LabelService, private filterService: FilterService, - ) {} + ) { + } setDataService(dataService: DataService) { this.dataService = dataService; @@ -359,6 +360,92 @@ export class TrainrunService { return this.trainrunsStore.trainruns.map((trainrun) => trainrun.getDto()); } + splitTrainrunIntoTwoParts(t: Transition) { + const trainrun2split = t.getTrainrun(); + const portId1 = t.getPortId1(); + const portId2 = t.getPortId2(); + + const node = this.nodeService.getNodeFromTransition(t); + node.removeTransitionFromId(t); + + const port1 = node.getPort(portId1); + const port2 = node.getPort(portId2); + const trainrunSection2 = port2.getTrainrunSection(); + const newTrainrun = + this.duplicateTrainrun( + trainrunSection2.getTrainrunId(), + false, + "-2"); + + trainrunSection2.setTrainrun(newTrainrun); + const iterator = this.getIterator(node, trainrunSection2); + while (iterator.hasNext()) { + iterator.next(); + const trans = iterator.current().node.getTransition(iterator.current().trainrunSection.getId()); + if (trans) { + trans.setTrainrun(newTrainrun); + } + iterator.current().trainrunSection.setTrainrun(newTrainrun); + } + + this.nodeService.checkAndFixMissingTransitions( + port1.getTrainrunSection().getSourceNodeId(), + port1.getTrainrunSection().getTargetNodeId(), + port1.getTrainrunSection().getId(), + false, + ); + + trainrun2split.unselect(); + newTrainrun.select(); + this.nodeService.transitionsUpdated(); + this.trainrunsUpdated(); + } + + combineTwoTrainruns(node: Node, port1: Port, port2: Port) { + const trainrun1 = port1.getTrainrunSection().getTrainrun(); + const trainrun2 = port2.getTrainrunSection().getTrainrun(); + if (trainrun1.getId() === trainrun2.getId()) { + return; + } + const trainrunSection = port2.getTrainrunSection(); + trainrunSection.setTrainrun(trainrun1); + const iterator = this.getIterator(node, trainrunSection); + while (iterator.hasNext()) { + iterator.next(); + const trans = iterator.current().node.getTransition(iterator.current().trainrunSection.getId()); + if (trans) { + trans.setTrainrun(trainrun1); + } + iterator.current().trainrunSection.setTrainrun(trainrun1); + } + + const trans1 = node.getTransitionFromPortId(port1.getId()); + const trans2 = node.getTransitionFromPortId(port2.getId()); + if (trans1 === undefined && trans2 === undefined) { + node.addTransitionAndComputeRouting(port1, port2, trainrun1); + } + + const connections2delete = node.getConnections().filter((c: Connection) => { + return (c.getPortId1() === port1.getId() && c.getPortId2() === port2.getId()) || + (c.getPortId1() === port2.getId() && c.getPortId2() === port1.getId()); + }); + connections2delete.forEach((c: Connection) => { + node.removeConnection(c.getId()); + }); + + trainrun1.unselect(); + trainrun2.unselect(); + + this.deleteTrainrun(trainrun2); + + trainrun1.select(); + this.nodeService.reorderPortsOnNodesForTrainrun(trainrun1); + this.trainrunsUpdated(); + this.nodeService.nodesUpdated(); + this.nodeService.connectionsUpdated(); + this.nodeService.transitionsUpdated(); + } + duplicateTrainrun( trainrunId: number, enforceUpdate = true, @@ -372,8 +459,17 @@ export class TrainrunService { copiedtrainrun.setTitle(trainrun.getTitle() + postfix); copiedtrainrun.setLabelIds(trainrun.getLabelIds()); this.trainrunsStore.trainruns.push(copiedtrainrun); + return copiedtrainrun; + } + + duplicateTrainrunAndSections( + trainrunId: number, + enforceUpdate = true, + postfix = " COPY", + ): Trainrun { + const copiedtrainrun = this.duplicateTrainrun(trainrunId, enforceUpdate, postfix); this.trainrunSectionService.copyAllTrainrunSectionsForTrainrun( - trainrun.getId(), + trainrunId, copiedtrainrun.getId(), ); this.setTrainrunAsSelected(copiedtrainrun.getId(), false); diff --git a/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-filter-tab/trainrun-filter-tab.component.ts b/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-filter-tab/trainrun-filter-tab.component.ts index 5ba9a5bf..c2ddbadc 100644 --- a/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-filter-tab/trainrun-filter-tab.component.ts +++ b/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-filter-tab/trainrun-filter-tab.component.ts @@ -116,7 +116,7 @@ export class TrainrunFilterTabComponent implements OnInit, OnDestroy { } onDuplicateTrainrun() { - this.trainrunService.duplicateTrainrun(this.selectedTrainrun.getId()); + this.trainrunService.duplicateTrainrunAndSections(this.selectedTrainrun.getId()); this.initializeWithCurrentSelectedTrainrun(); } diff --git a/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-tab/trainrun-tab.component.ts b/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-tab/trainrun-tab.component.ts index 0d90da94..0d5a38d5 100644 --- a/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-tab/trainrun-tab.component.ts +++ b/src/app/view/dialogs/trainrun-and-section-dialog/trainrun-tab/trainrun-tab.component.ts @@ -180,7 +180,7 @@ export class TrainrunTabComponent implements OnDestroy { } onDuplicateTrainrun() { - this.trainrunService.duplicateTrainrun(this.selectedTrainrun.getId()); + this.trainrunService.duplicateTrainrunAndSections(this.selectedTrainrun.getId()); this.initializeWithCurrentSelectedTrainrun(); } diff --git a/src/app/view/editor-main-view/data-views/editor.keyEvents.ts b/src/app/view/editor-main-view/data-views/editor.keyEvents.ts index 882aaf67..f5ad4b65 100644 --- a/src/app/view/editor-main-view/data-views/editor.keyEvents.ts +++ b/src/app/view/editor-main-view/data-views/editor.keyEvents.ts @@ -16,10 +16,7 @@ import {CopyService} from "../../../services/data/copy.service"; import {Port} from "../../../models/port.model"; import {FilterService} from "../../../services/ui/filter.service"; import {Connection} from "../../../models/connection.model"; -import { - PreviewLineMode, - TrainrunSectionPreviewLineView, -} from "./trainrunsection.previewline.view"; +import {PreviewLineMode, TrainrunSectionPreviewLineView,} from "./trainrunsection.previewline.view"; import {TrainrunSection} from "../../../models/trainrunsection.model"; import {Trainrun} from "../../../models/trainrun.model"; @@ -43,7 +40,8 @@ export class EditorKeyEvents { } deactivateMousekeyDownHandler() { - d3.select("body").on("keydown", () => {}); + d3.select("body").on("keydown", () => { + }); } ignoreKeyEvent(event: KeyboardEvent): boolean { @@ -53,11 +51,20 @@ export class EditorKeyEvents { activateMousekeyDownHandler(editorMode: EditorMode) { this.editorMode = editorMode; + d3.select("body").on("keyup", () => { + if (this.ignoreKeyEvent(d3.event)) { + return; + } + this.forwardCtrlKeyInformation(); + }); + d3.select("body").on("keydown", () => { if (this.ignoreKeyEvent(d3.event)) { return; } + this.forwardCtrlKeyInformation(); + if ( this.trainrunSectionPreviewLineView.getMode() !== PreviewLineMode.NotDragging @@ -127,6 +134,17 @@ export class EditorKeyEvents { }); } + private forwardCtrlKeyInformation() { + const obj1 = d3.selectAll(StaticDomTags.EDGE_LINE_PIN_DOM_REF); + obj1.each(function () { + d3.select(this).classed(StaticDomTags.TAG_CTRLKEY, d3.event.ctrlKey); + }); + const obj2 = d3.selectAll(StaticDomTags.TRANSITION_BUTTON_DOM_REF); + obj2.each(function () { + d3.select(this).classed(StaticDomTags.TAG_CTRLKEY, d3.event.ctrlKey); + }); + } + private getSelectedTrainSectionId(): number { let selectedTrainrunSectionId: number = undefined; d3.select( @@ -205,7 +223,7 @@ export class EditorKeyEvents { private doDuplicateTrainrun(): boolean { const selectedTrainrunSectionId = this.getSelectedTrainSectionId(); if (selectedTrainrunSectionId !== undefined) { - this.trainrunService.duplicateTrainrun(selectedTrainrunSectionId); + this.trainrunService.duplicateTrainrunAndSections(selectedTrainrunSectionId); return true; } return false; @@ -285,7 +303,7 @@ export class EditorKeyEvents { // >>> Pass 2.3 -- remove all duplicate trainrun sections that are not encapsulated by marked nodes const newTrainrunSectionToModify: TrainrunSection[] = []; collectedTrainrun.forEach((t: Trainrun) => { - const newTrainrun = this.trainrunService.duplicateTrainrun( + const newTrainrun = this.trainrunService.duplicateTrainrunAndSections( t.getId(), false, "", @@ -459,7 +477,7 @@ export class EditorKeyEvents { private onSelectAll(): boolean { if ( this.uiInteractionService.getEditorMode() === - EditorMode.MultiNodeMoving || + EditorMode.MultiNodeMoving || this.uiInteractionService.getEditorMode() === EditorMode.NetzgrafikEditing ) { this.uiInteractionService.setEditorMode(EditorMode.MultiNodeMoving); diff --git a/src/app/view/editor-main-view/data-views/editor.view.ts b/src/app/view/editor-main-view/data-views/editor.view.ts index 28017c34..e7a33d59 100644 --- a/src/app/view/editor-main-view/data-views/editor.view.ts +++ b/src/app/view/editor-main-view/data-views/editor.view.ts @@ -72,6 +72,8 @@ export class EditorView implements SVGMouseControllerObserver { getConnectedTrainrunIds = null; toggleNonStop = null; getNodeFromTransition = null; + splitTrainrunIntoTwoParts = null; + combineTwoTrainruns = null; getNodeFromConnection = null; isFilterTravelTimeEnabled = null; isFilterTrainrunNameEnabled = null; @@ -247,6 +249,14 @@ export class EditorView implements SVGMouseControllerObserver { this.getNodeFromTransition = callback; } + bindSplitTrainrunIntoTwoParts(callback) { + this.splitTrainrunIntoTwoParts = callback; + } + + bindCombineTwoTrainruns(callback) { + this.combineTwoTrainruns = callback; + } + bindGetNodeFromConnection(callback) { this.getNodeFromConnection = callback; } diff --git a/src/app/view/editor-main-view/data-views/nodes.view.ts b/src/app/view/editor-main-view/data-views/nodes.view.ts index 6d7ebbdd..9344157e 100644 --- a/src/app/view/editor-main-view/data-views/nodes.view.ts +++ b/src/app/view/editor-main-view/data-views/nodes.view.ts @@ -389,6 +389,9 @@ export class NodesView { .on("mouseover", (n: NodeViewObject) => this.onNodeMouseover(n.node, null), ) + .on("mousemove", (n: NodeViewObject) => + this.onNodeMousemove(n.node, null), + ) .on("mouseout", (n: NodeViewObject) => this.onNodeMouseout(n.node, null)) .on("mousedown", (n: NodeViewObject) => this.onNodeMousedown(n.node)) .on("mouseup", (n: NodeViewObject) => this.onNodeMouseup(n.node)); @@ -501,6 +504,10 @@ export class NodesView { this.hoverNode(node, domObj); } + onNodeMousemove(node: Node, domObj : any){ + this.hoverPinsAsConnectionDropable(node); + } + onNodeLabelAreaMouseover(node: Node, domObj: any) { this.hoverNode(node, domObj); d3.selectAll(StaticDomTags.NODE_HOVER_DRAG_AREA_DOM_REF) diff --git a/src/app/view/editor-main-view/data-views/static.dom.tags.ts b/src/app/view/editor-main-view/data-views/static.dom.tags.ts index 31bf07cc..35ea7546 100644 --- a/src/app/view/editor-main-view/data-views/static.dom.tags.ts +++ b/src/app/view/editor-main-view/data-views/static.dom.tags.ts @@ -10,6 +10,7 @@ export class StaticDomTags { static TAG_MUTED = "muted"; static TAG_RELATED = "related"; static TAG_HIDDEN = "hidden"; + static TAG_CTRLKEY = "ctrlKey"; static TAG_COLOR_REF = "ColorRef"; static PREFIX_COLOR_VARIABLE = "COLOR_VARIABLE_ColorRef"; @@ -161,6 +162,8 @@ export class StaticDomTags { static EDGE_NODE_ID = "node_id"; static EDGE_IS_SOURCE = "is_source"; static EDGE_IS_TARGET = "is_target"; + static EDGE_IS_END_NODE = "is_end_node"; + static EDGE_IS_NOT_END_NODE = "is_not_end_node"; static EDGE_LINE_SVG = "path"; static EDGE_LINE_CLASS = "edge_line"; diff --git a/src/app/view/editor-main-view/data-views/trainrunsections.view.scss b/src/app/view/editor-main-view/data-views/trainrunsections.view.scss index cffecdc8..cb218e8c 100644 --- a/src/app/view/editor-main-view/data-views/trainrunsections.view.scss +++ b/src/app/view/editor-main-view/data-views/trainrunsections.view.scss @@ -91,6 +91,18 @@ r: $PIN_RADIUS_HOVER; } +::ng-deep circle.edge_line_pin.is_not_end_node.ctrlKey { + stroke: none; + fill: none; + r: $PIN_RADIUS_HIDDEN; + cursor: pointer; +} + +::ng-deep circle.edge_line_pin.is_end_node.ctrlKey { + stroke: $COLOR_Edit; + cursor: cell; +} + ::ng-deep circle.edge_line_pin.connection_pin { stroke: $COLOR_Default; fill: $COLOR_MUTED_SILVER; @@ -105,6 +117,10 @@ cursor: pointer; } +::ng-deep circle.edge_line_pin.connection_pin.hover.is_end_node.ctrlKey { + cursor: cell; +} + ::ng-deep rect.edge_text_background { stroke-width: 1px; stroke: $COLOR_BACKGROUND; diff --git a/src/app/view/editor-main-view/data-views/trainrunsections.view.ts b/src/app/view/editor-main-view/data-views/trainrunsections.view.ts index 2db4c715..ec13481c 100644 --- a/src/app/view/editor-main-view/data-views/trainrunsections.view.ts +++ b/src/app/view/editor-main-view/data-views/trainrunsections.view.ts @@ -19,10 +19,7 @@ import {StaticDomTags} from "./static.dom.tags"; import {TrainrunSection} from "../../../models/trainrunsection.model"; import {EditorView} from "./editor.view"; import {D3Utils} from "./d3.utils"; -import { - DragIntermediateStopInfo, - PreviewLineMode, -} from "./trainrunsection.previewline.view"; +import {DragIntermediateStopInfo, PreviewLineMode,} from "./trainrunsection.previewline.view"; import {MathUtils} from "../../../utils/math"; import {Trainrun} from "../../../models/trainrun.model"; import {TrainrunSectionViewObject} from "./trainrunSectionViewObject"; @@ -34,7 +31,8 @@ import {InformSelectedTrainrunClick} from "../../../services/data/trainrunsectio export class TrainrunSectionsView { trainrunSectionGroup; - constructor(private editorView: EditorView) {} + constructor(private editorView: EditorView) { + } static translateAndRotateText( trainrunSection: TrainrunSection, @@ -396,9 +394,9 @@ export class TrainrunSectionsView { " " + (colorRef === undefined ? StaticDomTags.makeClassTag( - StaticDomTags.TAG_COLOR_REF, - trainrunSection.getTrainrun().getCategoryColorRef(), - ) + StaticDomTags.TAG_COLOR_REF, + trainrunSection.getTrainrun().getCategoryColorRef(), + ) : colorRef); switch (textElement) { case TrainrunSectionText.SourceDeparture: @@ -957,29 +955,28 @@ export class TrainrunSectionsView { !trainrunSection.getTrainrun().selected() && TrainrunSectionsView.isBothSideNonStop(trainrunSection) ); - case TrainrunSectionText.TrainrunSectionName: - { - if (!this.editorView.isFilterTrainrunNameEnabled()) { - return true; - } - const srcNode = TrainrunSectionsView.getNode(trainrunSection, true); - const trgNode = TrainrunSectionsView.getNode(trainrunSection, false); - if ( - !this.editorView.checkFilterNonStopNode(srcNode) || - !this.editorView.checkFilterNonStopNode(trgNode) - ) { - const transSrc = srcNode.getTransition(trainrunSection.getId()); - const transTrg = trgNode.getTransition(trainrunSection.getId()); - if (transSrc !== undefined && transTrg !== undefined) { - if ( - transSrc.getIsNonStopTransit() && - transTrg.getIsNonStopTransit() - ) { - return true; - } + case TrainrunSectionText.TrainrunSectionName: { + if (!this.editorView.isFilterTrainrunNameEnabled()) { + return true; + } + const srcNode = TrainrunSectionsView.getNode(trainrunSection, true); + const trgNode = TrainrunSectionsView.getNode(trainrunSection, false); + if ( + !this.editorView.checkFilterNonStopNode(srcNode) || + !this.editorView.checkFilterNonStopNode(trgNode) + ) { + const transSrc = srcNode.getTransition(trainrunSection.getId()); + const transTrg = trgNode.getTransition(trainrunSection.getId()); + if (transSrc !== undefined && transTrg !== undefined) { + if ( + transSrc.getIsNonStopTransit() && + transTrg.getIsNonStopTransit() + ) { + return true; } } } + } return false; default: return false; @@ -1041,7 +1038,7 @@ export class TrainrunSectionsView { d.trainrunSection, lineTextElement, ) / - 2, + 2, ) .attr( "y", @@ -1261,6 +1258,31 @@ export class TrainrunSectionsView { atSource ? StaticDomTags.EDGE_IS_SOURCE : StaticDomTags.EDGE_IS_TARGET, true, ) + .classed( + StaticDomTags.EDGE_IS_END_NODE, + (d: TrainrunSectionViewObject) => { + let node = d.trainrunSection.getTargetNode(); + if (atSource) { + node = d.trainrunSection.getSourceNode(); + } + const port = node.getPortOfTrainrunSection(d.trainrunSection.getId()); + const trans = node.getTransitionFromPortId(port.getId()); + return (trans === undefined); + } + ) + .classed( + StaticDomTags.EDGE_IS_NOT_END_NODE, + (d: TrainrunSectionViewObject) => { + let node = d.trainrunSection.getTargetNode(); + if (atSource) { + node = d.trainrunSection.getSourceNode(); + } + const port = node.getPortOfTrainrunSection(d.trainrunSection.getId()); + const trans = node.getTransitionFromPortId(port.getId()); + return (trans !== undefined); + } + ) + .classed(StaticDomTags.TAG_MUTED, (d: TrainrunSectionViewObject) => TrainrunSectionsView.isMuted( d.trainrunSection, @@ -1469,14 +1491,14 @@ export class TrainrunSectionsView { .attr( "class", StaticDomTags.EDGE_LINE_TEXT_CLASS + - " " + - TrainrunSectionsView.createTrainrunSectionFrequencyClassAttribute( - trainrunSection, - selectedTrainrun, - connectedTrainIds, - ) + - " " + - TrainrunSectionText[TrainrunSectionText.TrainrunSectionNumberOfStops], + " " + + TrainrunSectionsView.createTrainrunSectionFrequencyClassAttribute( + trainrunSection, + selectedTrainrun, + connectedTrainIds, + ) + + " " + + TrainrunSectionText[TrainrunSectionText.TrainrunSectionNumberOfStops], ) .attr(StaticDomTags.EDGE_ID, () => trainrunSection.getId()) .attr(StaticDomTags.EDGE_LINE_LINE_ID, () => @@ -1876,10 +1898,10 @@ export class TrainrunSectionsView { const obj = d3 .selectAll( StaticDomTags.EDGE_LINE_PIN_DOM_REF + - "." + - (atSource - ? StaticDomTags.EDGE_IS_TARGET - : StaticDomTags.EDGE_IS_SOURCE), + "." + + (atSource + ? StaticDomTags.EDGE_IS_TARGET + : StaticDomTags.EDGE_IS_SOURCE), ) .filter( (d: TrainrunSectionViewObject) => @@ -2524,6 +2546,15 @@ export class TrainrunSectionsView { trainrunSectionFrom.getTrainrunId() !== trainrunSection.getTrainrunId() ) { this.editorView.trainrunSectionPreviewLineView.stopPreviewLine(); + if (d3.event.ctrlKey) { + const n: Node = endNode; + this.editorView.combineTwoTrainruns( + endNode, + n.getPortOfTrainrunSection(trainrunSectionFrom.getId()), + n.getPortOfTrainrunSection(trainrunSection.getId()) + ); + return; + } this.editorView.addConnectionToNode( endNode, trainrunSectionFrom, diff --git a/src/app/view/editor-main-view/data-views/transition.view.scss b/src/app/view/editor-main-view/data-views/transition.view.scss index 7378b0c7..0f37a2a3 100644 --- a/src/app/view/editor-main-view/data-views/transition.view.scss +++ b/src/app/view/editor-main-view/data-views/transition.view.scss @@ -124,6 +124,10 @@ pointer-events: auto; } +::ng-deep polygon.transition_pin.ctrlKey { + cursor: cell; +} + /// FREQ_null -> new TimeCategory(HVZ, default) ::ng-deep path.transition_line.LinePatternRef_HVZ { stroke-dasharray: 16px 4px 2px 4px; diff --git a/src/app/view/editor-main-view/data-views/transitions.view.ts b/src/app/view/editor-main-view/data-views/transitions.view.ts index 9236fea8..d963a580 100644 --- a/src/app/view/editor-main-view/data-views/transitions.view.ts +++ b/src/app/view/editor-main-view/data-views/transitions.view.ts @@ -179,7 +179,11 @@ export class TransitionsView { .classed(StaticDomTags.TRANSITION_IS_NONSTOP, (t: TransitionViewObject) => t.transition.getIsNonStopTransit(), ) - .on("mousemove", () => this.onTransitionMousemove()) + .on("mousemove", (t: TransitionViewObject, i, a) => + this.onTransitionMousemove( + a[i], + ), + ) .on("mouseover", (t: TransitionViewObject, i, a) => this.onTransitionMouseover( t.transition.getTrainrun(), @@ -333,6 +337,10 @@ export class TransitionsView { port1.getTrainrunSection().getTrainrun().selected() || port2.getTrainrunSection().getTrainrun().selected() ) { + if (d3.event.ctrlKey) { + this.editorView.splitTrainrunIntoTwoParts(transition); + return; + } this.editorView.toggleNonStop(node, transition); } return; @@ -351,6 +359,7 @@ export class TransitionsView { transition: Transition, ) { d3.select(domObj).classed(StaticDomTags.TAG_HOVER, false); + const node: Node = this.editorView.getNodeFromTransition(transition); this.editorView.nodesView.unhoverNodeDockable(node, null); @@ -393,7 +402,7 @@ export class TransitionsView { this.editorView.trainrunSectionPreviewLineView.updatePreviewLine(); } - onTransitionMousemove() { + onTransitionMousemove(domObj: any) { d3.event.stopPropagation(); } diff --git a/src/app/view/editor-main-view/editor-main-view.component.ts b/src/app/view/editor-main-view/editor-main-view.component.ts index 1822589a..414aa8ca 100644 --- a/src/app/view/editor-main-view/editor-main-view.component.ts +++ b/src/app/view/editor-main-view/editor-main-view.component.ts @@ -40,6 +40,7 @@ import { StreckengrafikDrawingContext } from "../../streckengrafik/model/util/streckengrafik.drawing.context"; import {TravelTimeCreationEstimatorType} from "../themes/editor-trainrun-traveltime-creator-type"; +import {Port} from "../../models/port.model"; @Component({ selector: "sbb-editor-main-view", @@ -384,6 +385,15 @@ export class EditorMainViewComponent implements AfterViewInit, OnDestroy { this.nodeService.getNodeFromTransition(t), ); + this.editorView.bindSplitTrainrunIntoTwoParts((t: Transition) => { + this.trainrunService.splitTrainrunIntoTwoParts(t); + }); + + this.editorView.bindCombineTwoTrainruns((n: Node, port1: Port, port2: Port) => { + this.trainrunService.combineTwoTrainruns(n, port1, port2); + }); + + this.editorView.bindGetNodeFromConnection((c: Connection) => this.nodeService.getNodeForConnection(c), ); From 7954d1adba576289de1413fc60a35e901a3a4dee Mon Sep 17 00:00:00 2001 From: Adrian Egli Date: Tue, 19 Mar 2024 10:36:53 +0100 Subject: [PATCH 11/12] fixed (#30) * fixed * inline code doc --- src/app/services/data/trainrun.service.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/app/services/data/trainrun.service.ts b/src/app/services/data/trainrun.service.ts index 1a580f00..23ec10ef 100644 --- a/src/app/services/data/trainrun.service.ts +++ b/src/app/services/data/trainrun.service.ts @@ -407,6 +407,8 @@ export class TrainrunService { if (trainrun1.getId() === trainrun2.getId()) { return; } + + // update trainrun references (trainrunSections and transitions) const trainrunSection = port2.getTrainrunSection(); trainrunSection.setTrainrun(trainrun1); const iterator = this.getIterator(node, trainrunSection); @@ -419,12 +421,14 @@ export class TrainrunService { iterator.current().trainrunSection.setTrainrun(trainrun1); } + // update trainrun references (1st transition) const trans1 = node.getTransitionFromPortId(port1.getId()); const trans2 = node.getTransitionFromPortId(port2.getId()); if (trans1 === undefined && trans2 === undefined) { node.addTransitionAndComputeRouting(port1, port2, trainrun1); } + // update trainrun references (connection) const connections2delete = node.getConnections().filter((c: Connection) => { return (c.getPortId1() === port1.getId() && c.getPortId2() === port2.getId()) || (c.getPortId1() === port2.getId() && c.getPortId2() === port1.getId()); @@ -433,13 +437,18 @@ export class TrainrunService { node.removeConnection(c.getId()); }); + // unselect both trainruns trainrun1.unselect(); trainrun2.unselect(); - this.deleteTrainrun(trainrun2); + // remove empty trainrun + this.deleteTrainrun(trainrun2, false); + // select trainrun1.select(); - this.nodeService.reorderPortsOnNodesForTrainrun(trainrun1); + this.nodeService.reorderPortsOnNodesForTrainrun(trainrun1, false); + + // update this.trainrunsUpdated(); this.nodeService.nodesUpdated(); this.nodeService.connectionsUpdated(); From bb4585ab8c6b344c97924c480da79d41cc5b7781 Mon Sep 17 00:00:00 2001 From: Adrian Egli Date: Tue, 19 Mar 2024 14:46:15 +0100 Subject: [PATCH 12/12] release prepared and update version numbering (#32) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b20fffd1..d2b73c44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "netzgrafik-frontend", - "version": "2.4.0", + "version": "2.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "netzgrafik-frontend", - "version": "2.4.0", + "version": "2.5.0", "dependencies": { "@angular/animations": "^17.0.5", "@angular/cdk": "^17.0.1", diff --git a/package.json b/package.json index f92deb61..be7c7b5b 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "local-test": "ng test --watch --code-coverage" }, "name": "netzgrafik-frontend", - "version": "2.4.0", + "version": "2.5.0", "files": [ "dist/*" ],