Skip to content

Commit e2d498f

Browse files
authored
feat: creating a more condensed left to right topology layout (#1036)
* feat: creating a more condensed left to right topology layout * refactor: fixing lint * refactor: self review
1 parent 978312f commit e2d498f

File tree

7 files changed

+62
-25
lines changed

7 files changed

+62
-25
lines changed

projects/observability/src/shared/components/topology/d3/d3-topology.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
TOPOLOGY_INTERACTION_CONTROL_DATA
4040
} from './interactions/topology-interaction-control.component';
4141
import { TopologyZoom } from './interactions/zoom/topology-zoom';
42-
import { TreeLayout } from './layouts/tree-layout';
42+
import { CustomTreeLayout } from './layouts/custom-tree-layout';
4343

4444
export class D3Topology implements Topology {
4545
private static readonly CONTAINER_CLASS: string = 'topology-internal-container';
@@ -57,7 +57,7 @@ export class D3Topology implements Topology {
5757
protected readonly dataClearCallbacks: (() => void)[] = [];
5858
protected container?: HTMLDivElement;
5959
protected tooltip?: TopologyTooltip;
60-
protected layout: TopologyLayout = new TreeLayout(); // TODO: Make this configurable with Node and edge renderers
60+
protected layout: TopologyLayout = new CustomTreeLayout(); // TODO: Make this configurable with Node and edge renderers
6161

6262
protected readonly userNodes: TopologyNode[];
6363
protected readonly nodeRenderer: TopologyNodeRenderer;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { hierarchy, HierarchyNode } from 'd3-hierarchy';
2+
import { RenderableTopology, TopologyEdge, TopologyNode } from '../../topology';
3+
import { D3ProxyNode, TreeLayout } from './tree-layout';
4+
5+
export class CustomTreeLayout extends TreeLayout {
6+
public layout(topology: RenderableTopology<TopologyNode, TopologyEdge>): void {
7+
const rootHierarchyNode = hierarchy(this.buildHierarchyProxyNodes(topology.nodes, { x: 0, y: 0 }));
8+
9+
const nodeWidth = this.getNodeWidth(rootHierarchyNode);
10+
const nodeHeight = this.getNodeHeight(rootHierarchyNode);
11+
12+
this.updateLayout(rootHierarchyNode, 0, -1, nodeWidth * 1, nodeHeight);
13+
}
14+
15+
private updateLayout(
16+
hierarchyNode: HierarchyNode<D3ProxyNode>,
17+
nodeRowIndex: number,
18+
nodeColumnIndex: number,
19+
cellWidth: number,
20+
cellHeight: number
21+
): number {
22+
if (hierarchyNode.data.sourceNode !== undefined) {
23+
hierarchyNode.data.sourceNode.x = nodeColumnIndex * cellWidth;
24+
hierarchyNode.data.sourceNode.y = nodeRowIndex * cellHeight;
25+
}
26+
27+
hierarchyNode.children?.forEach((node, index) => {
28+
this.updateLayout(node, nodeRowIndex + index, nodeColumnIndex + 1, cellWidth, cellHeight);
29+
});
30+
31+
return (hierarchyNode.children?.length ?? 0) + 1;
32+
}
33+
}

projects/observability/src/shared/components/topology/d3/layouts/tree-layout.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class TreeLayout implements TopologyLayout {
4242
return Math.min(hierarchyNode.x, ...(hierarchyNode.children ?? []).map(node => this.getMinYPosition(node)));
4343
}
4444

45-
private buildHierarchyProxyNodes(
45+
protected buildHierarchyProxyNodes(
4646
nodes: RenderableTopologyNode[],
4747
startingLocation: TopologyCoordinates
4848
): D3ProxyNode {
@@ -66,7 +66,7 @@ export class TreeLayout implements TopologyLayout {
6666
return level0RootNode;
6767
}
6868

69-
private getNodeWidth(root: HierarchyNode<D3ProxyNode>): number {
69+
protected getNodeWidth(root: HierarchyNode<D3ProxyNode>): number {
7070
const leaf = first(root.leaves());
7171
let leafWidth = 240;
7272

@@ -81,7 +81,7 @@ export class TreeLayout implements TopologyLayout {
8181
return leafWidth + 160;
8282
}
8383

84-
private getNodeHeight(root: HierarchyNode<D3ProxyNode>): number {
84+
protected getNodeHeight(root: HierarchyNode<D3ProxyNode>): number {
8585
return this.getRenderedNodeHeight(first(root.leaves()));
8686
}
8787

@@ -99,7 +99,7 @@ export class TreeLayout implements TopologyLayout {
9999
return defaultNodeHeight;
100100
}
101101

102-
private buildTopologyHierarchyNodeMap(
102+
protected buildTopologyHierarchyNodeMap(
103103
nodes: RenderableTopologyNode[],
104104
startingLocation: TopologyCoordinates
105105
): Map<RenderableTopologyNode, D3ProxyNode> {
@@ -163,7 +163,7 @@ export class TreeLayout implements TopologyLayout {
163163
}
164164
}
165165

166-
interface D3ProxyNode {
166+
export interface D3ProxyNode {
167167
sourceNode: RenderableTopologyNode | undefined;
168168
hasIncomingEdges: boolean;
169169
children: D3ProxyNode[];

projects/observability/src/shared/components/topology/renderers/edge/topology-edge-renderer.service.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable, Renderer2 } from '@angular/core';
2-
import { distanceBetweenPoints, getVectorAngleRad } from '@hypertrace/common';
2+
import { distanceBetweenPoints, getVectorAngleRad, normalizeAngleRadians } from '@hypertrace/common';
33
import { D3UtilService } from '../../../utils/d3/d3-util.service';
44
import { RenderableTopologyEdge, TopologyEdge, TopologyEdgeRenderer, TopologyEdgeState } from '../../topology';
55

@@ -79,7 +79,7 @@ export class TopologyEdgeRendererService implements TopologyEdgeRenderer {
7979
}
8080

8181
const sourceRad = getVectorAngleRad(edge.source, edge.target);
82-
const targetRad = sourceRad + Math.PI;
82+
const targetRad = normalizeAngleRadians(sourceRad + Math.PI);
8383

8484
const sourceAttachPoint = sourceNodeRenderedData.getAttachmentPoint(sourceRad);
8585
const targetAttachPoint = targetNodeRenderedData.getAttachmentPoint(targetRad);
@@ -88,13 +88,17 @@ export class TopologyEdgeRendererService implements TopologyEdgeRenderer {
8888
if (distanceBetweenPoints(edge.source, sourceAttachPoint) > distanceBetweenPoints(edge.source, targetAttachPoint)) {
8989
return {
9090
source: targetAttachPoint,
91-
target: sourceAttachPoint
91+
sourceRad: targetRad,
92+
target: sourceAttachPoint,
93+
targetRad: sourceRad
9294
};
9395
}
9496

9597
return {
9698
source: sourceAttachPoint,
97-
target: targetAttachPoint
99+
sourceRad: sourceRad,
100+
target: targetAttachPoint,
101+
targetRad: targetRad
98102
};
99103
}
100104

@@ -112,6 +116,8 @@ export interface TopologyEdgePositionInformation {
112116
x: number;
113117
y: number;
114118
};
119+
sourceRad: number;
120+
targetRad: number;
115121
}
116122

117123
export interface TopologyEdgeRenderDelegate<T extends TopologyEdge = TopologyEdge> {

projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
stroke-width: 2;
1919
stroke-linecap: round;
2020
stroke-linejoin: round;
21-
stroke: $gray-3;
21+
stroke: $gray-2;
2222
fill: none;
2323

2424
@include chart-small-regular;

projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Injectable, Renderer2 } from '@angular/core';
22
import { Color, DomElementMeasurerService, NumericFormatter, selector } from '@hypertrace/common';
33
import { MetricAggregation } from '@hypertrace/distributed-tracing';
44
import { select, Selection } from 'd3-selection';
5-
import { linkHorizontal } from 'd3-shape';
5+
import { Link, linkHorizontal } from 'd3-shape';
66
import {
77
TopologyEdgePositionInformation,
88
TopologyEdgeRenderDelegate
@@ -114,7 +114,7 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat
114114
selection
115115
.select(selector(this.edgeLineClass))
116116
.select('.edge-path')
117-
.attr('stroke', edgeFocusedCategory?.strokeColor ?? Color.Gray3);
117+
.attr('stroke', edgeFocusedCategory?.strokeColor ?? Color.Gray2);
118118

119119
selection
120120
.select(selector(this.edgeMetricBubbleClass))
@@ -200,16 +200,14 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat
200200

201201
pathSelections.exit().remove();
202202

203-
pathSelections
204-
.enter()
205-
.append('path')
206-
.merge(pathSelections)
207-
.attr('d', data =>
208-
linkHorizontal<TopologyEdgePositionInformation, Position>()
209-
.x(datum => datum.x)
210-
.y(datum => datum.y)(data)
211-
)
212-
.classed('edge-path', true);
203+
const lineGenerator: Link<unknown, TopologyEdgePositionInformation, Position> = linkHorizontal<
204+
TopologyEdgePositionInformation,
205+
Position
206+
>()
207+
.x(datum => datum.x)
208+
.y(datum => datum.y);
209+
210+
pathSelections.enter().append('path').merge(pathSelections).attr('d', lineGenerator).classed('edge-path', true);
213211
}
214212

215213
private updateLabelPosition(

projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ export abstract class EntityNodeBoxRendererService implements TopologyNodeRender
396396
}
397397

398398
private isAngleInIQuadrant(angle: number): boolean {
399-
return angle > 0 && angle < Math.PI / 2;
399+
return angle >= 0 && angle < Math.PI / 2;
400400
}
401401

402402
private isAnglePerpendicularlyAbove(angle: number): boolean {

0 commit comments

Comments
 (0)