Skip to content

Commit 7210947

Browse files
hawkgsamishne
authored andcommitted
refactor(devtools): snap to root node on signal graph render
Drop the old `ResizeObserver` and resize logic in favor of a simpler "snap to root node" (template or first node) functionality.
1 parent c7eee83 commit 7210947

2 files changed

Lines changed: 41 additions & 22 deletions

File tree

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signals-view/signals-visualizer/signals-visualizer.component.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@ export class SignalsVisualizerComponent {
6262
return Array.from(clusterIds).map((id) => graph.clusters[id]);
6363
});
6464

65-
private onResize = () => this.signalsVisualizer?.resize();
66-
private observer = new ResizeObserver(this.onResize);
67-
6865
constructor() {
6966
const injector = inject(Injector);
7067

@@ -73,9 +70,18 @@ export class SignalsVisualizerComponent {
7370
this.setUpSignalsVisualizer();
7471

7572
runInInjectionContext(injector, () => {
73+
let lastElement: ElementPosition | undefined;
74+
7675
effect(() => {
7776
const graph = this.graph();
7877
this.signalsVisualizer!.render(graph!);
78+
const currElement = untracked(this.element);
79+
80+
// Snap to root node only if the element changes.
81+
if (lastElement !== currElement) {
82+
this.signalsVisualizer!.snapToRootNode();
83+
}
84+
lastElement = currElement;
7985
});
8086

8187
effect(() => {
@@ -92,12 +98,10 @@ export class SignalsVisualizerComponent {
9298
untracked(() => this.signalsVisualizer!.reset());
9399
});
94100
});
95-
this.observer.observe(this.svgHost().nativeElement);
96101
},
97102
});
98103

99104
inject(DestroyRef).onDestroy(() => {
100-
this.observer.disconnect();
101105
this.signalsVisualizer?.cleanup();
102106
});
103107
}

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signals-view/signals-visualizer/signals-visualizer.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ const NODE_WIDTH = 100;
5353
const NODE_HEIGHT = 60;
5454
const NODE_EPOCH_UPDATE_ANIM_DURATION = 250;
5555

56+
const TEMPL_NODE_ZOOM_SCALE = 0.8;
57+
5658
const CLUSTER_EXPAND_ANIM_DURATION = 1100; // Empirical value based on Dagre's behavior with an included leeway
5759

5860
// Terminology:
@@ -88,7 +90,6 @@ export class SignalsGraphVisualizer {
8890

8991
const d3svg = d3.select(this.svg);
9092
d3svg.attr('height', '100%').attr('width', '100%');
91-
this.resize();
9293

9394
const g = d3svg.append('g');
9495

@@ -100,6 +101,36 @@ export class SignalsGraphVisualizer {
100101
d3svg.call(this.zoomController);
101102
}
102103

104+
/** Snaps to the root node – either the template or the first node depending which exists. */
105+
snapToRootNode() {
106+
let node =
107+
this.inputGraph?.nodes.find((n) => isSignalNode(n) && n.kind === 'template') ??
108+
this.inputGraph?.nodes[0];
109+
110+
if (!node) {
111+
return;
112+
}
113+
114+
// TODO(Georgi): Drop `any` when Dagre is updated.
115+
const dagreNode = this.graph.node(node.id) as any;
116+
if (!dagreNode) {
117+
return;
118+
}
119+
120+
const contWidth = this.svg.clientWidth;
121+
const contHeight = this.svg.clientHeight;
122+
const x = contWidth / 2 - dagreNode.x * TEMPL_NODE_ZOOM_SCALE;
123+
const y = contHeight / 2 - dagreNode.y * TEMPL_NODE_ZOOM_SCALE;
124+
125+
d3.select(this.svg)
126+
.transition()
127+
.duration(500)
128+
.call(
129+
this.zoomController.transform,
130+
d3.zoomIdentity.translate(x, y).scale(TEMPL_NODE_ZOOM_SCALE),
131+
);
132+
}
133+
103134
setSelected(selected: string | null) {
104135
d3.select(this.svg)
105136
.select('.output .nodes')
@@ -144,28 +175,12 @@ export class SignalsGraphVisualizer {
144175

145176
this.drender(g, this.graph);
146177

147-
// if there are no nodes, we reset the transform to 0
148-
const {width, height} = this.graph.graph();
149-
const xTransform = isFinite(width) ? -width / 2 : 0;
150-
const yTransform = isFinite(height) ? -height / 2 : 0;
151-
g.select('.output').attr('transform', `translate(${xTransform}, ${yTransform})`);
152-
153178
this.addCloseButtonsToClusters(g);
154179
this.reinforceNodeDimensions();
155180

156181
this.inputGraph = signalGraph;
157182
}
158183

159-
resize() {
160-
const svg = d3.select(this.svg);
161-
svg.attr('viewBox', [
162-
-this.svg.clientWidth / 2,
163-
-this.svg.clientHeight / 2,
164-
this.svg.clientWidth,
165-
this.svg.clientHeight,
166-
]);
167-
}
168-
169184
setClusterState(clusterId: string, expanded: boolean) {
170185
if (!this.inputGraph) {
171186
return;

0 commit comments

Comments
 (0)