Skip to content

Commit 959966a

Browse files
Add a deferNodeUpdates input that freezes the minimap
during drag, resize, and rotation
1 parent 9a14406 commit 959966a

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { computed, inject, Injectable, Signal } from '@angular/core';
2+
import { NgDiagramService } from '../../public-services/ng-diagram.service';
3+
4+
/**
5+
* Tracks whether a continuous node interaction (drag, resize, rotation)
6+
* is currently in progress.
7+
*
8+
* Used by the minimap to optionally defer node updates during interactions,
9+
* avoiding expensive per-frame recomputations.
10+
*
11+
* @internal
12+
*/
13+
@Injectable()
14+
export class MinimapInteractionTracker {
15+
private readonly diagramService = inject(NgDiagramService);
16+
17+
readonly isInteracting: Signal<boolean> = computed(() => {
18+
const actionState = this.diagramService.actionState();
19+
return !!actionState.dragging?.movementStarted || !!actionState.resize || !!actionState.rotation;
20+
});
21+
}

packages/ng-diagram/projects/ng-diagram/src/lib/components/minimap/ng-diagram-minimap.component.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import {
99
input,
1010
signal,
1111
} from '@angular/core';
12+
import { Rect } from '../../../core/src';
1213
import { FlowCoreProviderService } from '../../services';
1314
import { RendererService } from '../../services/renderer/renderer.service';
1415
import { NgDiagramPanelPosition } from '../../types/panel-position';
1516
import { NgDiagramPanelComponent } from '../panel/ng-diagram-panel.component';
1617
import { NgDiagramZoomControlsComponent } from '../zoom-controls/ng-diagram-zoom-controls.component';
1718
import { NgDiagramDefaultMinimapNodeComponent } from './default-node/ng-diagram-default-minimap-node.component';
1819
import { NgDiagramMinimapDiagramBoundsComponent } from './diagram-bounds/ng-diagram-minimap-diagram-bounds.component';
20+
import { MinimapInteractionTracker } from './minimap-interaction-tracker';
1921
import { NgDiagramMinimapNavigationDirective } from './ng-diagram-minimap-navigation.directive';
2022
import {
2123
calculateMinimapTransform,
@@ -60,7 +62,7 @@ import { VirtualizedMinimapStrategy } from './strategy/virtualized-minimap-strat
6062
templateUrl: './ng-diagram-minimap.component.html',
6163
styleUrls: ['./ng-diagram-minimap.component.scss'],
6264
changeDetection: ChangeDetectionStrategy.OnPush,
63-
providers: [DirectMinimapStrategy, VirtualizedMinimapStrategy],
65+
providers: [DirectMinimapStrategy, VirtualizedMinimapStrategy, MinimapInteractionTracker],
6466
host: {
6567
'[class]': 'position()',
6668
},
@@ -70,6 +72,7 @@ export class NgDiagramMinimapComponent implements AfterViewInit {
7072

7173
private readonly renderer = inject(RendererService);
7274
private readonly flowCoreProvider = inject(FlowCoreProviderService);
75+
private readonly interactionTracker = inject(MinimapInteractionTracker);
7376
private readonly elementRef = inject(ElementRef);
7477
private readonly directStrategy = inject(DirectMinimapStrategy);
7578
private readonly virtualizedStrategy = inject(VirtualizedMinimapStrategy);
@@ -120,6 +123,16 @@ export class NgDiagramMinimapComponent implements AfterViewInit {
120123
*/
121124
minimapNodeTemplateMap = input<NgDiagramMinimapNodeTemplateMap>(new NgDiagramMinimapNodeTemplateMap());
122125

126+
/**
127+
* When enabled, minimap node positions are frozen during drag, resize, and
128+
* rotation operations — updated only when the operation ends.
129+
*
130+
* The viewport indicator rectangle always updates in real-time.
131+
*
132+
* @default false
133+
*/
134+
deferNodeUpdates = input<boolean>(false);
135+
123136
/** @ignore */
124137
ngAfterViewInit(): void {
125138
// Cache stroke padding after view init to avoid layout thrashing
@@ -171,15 +184,29 @@ export class NgDiagramMinimapComponent implements AfterViewInit {
171184
return `translate(${t.offsetX}, ${t.offsetY}) scale(${t.scale})`;
172185
});
173186

187+
private isDeferringUpdates = computed(() => this.deferNodeUpdates() && this.interactionTracker.isInteracting());
188+
189+
private cachedMinimapNodes: MinimapNodeData[] = [];
190+
private cachedDiagramBounds: Rect = { x: 0, y: 0, width: 0, height: 0 };
191+
174192
/**
175193
* @internal
176194
* Pre-computed minimap node data in DIAGRAM coordinates (not minimap coordinates).
177195
* The SVG group transform handles the coordinate conversion, so nodes only recalculate
178196
* when diagram content changes, NOT during pan/zoom.
197+
*
198+
* When deferNodeUpdates is enabled, returns frozen data during interactions.
199+
* Angular's conditional signal tracking ensures renderer.nodes() is not subscribed
200+
* while deferring, avoiding per-frame recomputation entirely.
179201
*/
180-
protected minimapNodes = computed((): MinimapNodeData[] =>
181-
this.strategy().computeMinimapNodes(this.nodeStyle(), this.minimapNodeTemplateMap())
182-
);
202+
protected minimapNodes = computed((): MinimapNodeData[] => {
203+
if (this.isDeferringUpdates()) {
204+
return this.cachedMinimapNodes;
205+
}
206+
const nodes = this.strategy().computeMinimapNodes(this.nodeStyle(), this.minimapNodeTemplateMap());
207+
this.cachedMinimapNodes = nodes;
208+
return nodes;
209+
});
183210

184211
/**
185212
* @internal
@@ -190,7 +217,14 @@ export class NgDiagramMinimapComponent implements AfterViewInit {
190217
() => this.isDiagramInitialized() && this.flowCoreProvider.provide().isVirtualizationActive
191218
);
192219

193-
protected diagramBounds = computed(() => this.strategy().computeDiagramBounds());
220+
protected diagramBounds = computed(() => {
221+
if (this.isDeferringUpdates()) {
222+
return this.cachedDiagramBounds;
223+
}
224+
const bounds = this.strategy().computeDiagramBounds();
225+
this.cachedDiagramBounds = bounds;
226+
return bounds;
227+
});
194228

195229
private viewportBoundsInDiagramSpace = computed(() => convertViewportToDiagramBounds(this.viewport()));
196230

0 commit comments

Comments
 (0)