diff --git a/projects/observability/src/shared/components/topology/d3/d3-topology.ts b/projects/observability/src/shared/components/topology/d3/d3-topology.ts index f6821ca61..e241f7f43 100644 --- a/projects/observability/src/shared/components/topology/d3/d3-topology.ts +++ b/projects/observability/src/shared/components/topology/d3/d3-topology.ts @@ -39,7 +39,7 @@ import { TOPOLOGY_INTERACTION_CONTROL_DATA } from './interactions/topology-interaction-control.component'; import { TopologyZoom } from './interactions/zoom/topology-zoom'; -import { TreeLayout } from './layouts/tree-layout'; +import { CustomTreeLayout } from './layouts/custom-tree-layout'; export class D3Topology implements Topology { private static readonly CONTAINER_CLASS: string = 'topology-internal-container'; @@ -57,7 +57,7 @@ export class D3Topology implements Topology { protected readonly dataClearCallbacks: (() => void)[] = []; protected container?: HTMLDivElement; protected tooltip?: TopologyTooltip; - protected layout: TopologyLayout = new TreeLayout(); // TODO: Make this configurable with Node and edge renderers + protected layout: TopologyLayout = new CustomTreeLayout(); // TODO: Make this configurable with Node and edge renderers protected readonly userNodes: TopologyNode[]; protected readonly nodeRenderer: TopologyNodeRenderer; diff --git a/projects/observability/src/shared/components/topology/d3/layouts/custom-tree-layout.ts b/projects/observability/src/shared/components/topology/d3/layouts/custom-tree-layout.ts new file mode 100644 index 000000000..a099d231b --- /dev/null +++ b/projects/observability/src/shared/components/topology/d3/layouts/custom-tree-layout.ts @@ -0,0 +1,33 @@ +import { hierarchy, HierarchyNode } from 'd3-hierarchy'; +import { RenderableTopology, TopologyEdge, TopologyNode } from '../../topology'; +import { D3ProxyNode, TreeLayout } from './tree-layout'; + +export class CustomTreeLayout extends TreeLayout { + public layout(topology: RenderableTopology): void { + const rootHierarchyNode = hierarchy(this.buildHierarchyProxyNodes(topology.nodes, { x: 0, y: 0 })); + + const nodeWidth = this.getNodeWidth(rootHierarchyNode); + const nodeHeight = this.getNodeHeight(rootHierarchyNode); + + this.updateLayout(rootHierarchyNode, 0, -1, nodeWidth * 1, nodeHeight); + } + + private updateLayout( + hierarchyNode: HierarchyNode, + nodeRowIndex: number, + nodeColumnIndex: number, + cellWidth: number, + cellHeight: number + ): number { + if (hierarchyNode.data.sourceNode !== undefined) { + hierarchyNode.data.sourceNode.x = nodeColumnIndex * cellWidth; + hierarchyNode.data.sourceNode.y = nodeRowIndex * cellHeight; + } + + hierarchyNode.children?.forEach((node, index) => { + this.updateLayout(node, nodeRowIndex + index, nodeColumnIndex + 1, cellWidth, cellHeight); + }); + + return (hierarchyNode.children?.length ?? 0) + 1; + } +} diff --git a/projects/observability/src/shared/components/topology/d3/layouts/tree-layout.ts b/projects/observability/src/shared/components/topology/d3/layouts/tree-layout.ts index c14eb4dea..ed2d3a223 100644 --- a/projects/observability/src/shared/components/topology/d3/layouts/tree-layout.ts +++ b/projects/observability/src/shared/components/topology/d3/layouts/tree-layout.ts @@ -42,7 +42,7 @@ export class TreeLayout implements TopologyLayout { return Math.min(hierarchyNode.x, ...(hierarchyNode.children ?? []).map(node => this.getMinYPosition(node))); } - private buildHierarchyProxyNodes( + protected buildHierarchyProxyNodes( nodes: RenderableTopologyNode[], startingLocation: TopologyCoordinates ): D3ProxyNode { @@ -66,7 +66,7 @@ export class TreeLayout implements TopologyLayout { return level0RootNode; } - private getNodeWidth(root: HierarchyNode): number { + protected getNodeWidth(root: HierarchyNode): number { const leaf = first(root.leaves()); let leafWidth = 240; @@ -81,7 +81,7 @@ export class TreeLayout implements TopologyLayout { return leafWidth + 160; } - private getNodeHeight(root: HierarchyNode): number { + protected getNodeHeight(root: HierarchyNode): number { return this.getRenderedNodeHeight(first(root.leaves())); } @@ -99,7 +99,7 @@ export class TreeLayout implements TopologyLayout { return defaultNodeHeight; } - private buildTopologyHierarchyNodeMap( + protected buildTopologyHierarchyNodeMap( nodes: RenderableTopologyNode[], startingLocation: TopologyCoordinates ): Map { @@ -163,7 +163,7 @@ export class TreeLayout implements TopologyLayout { } } -interface D3ProxyNode { +export interface D3ProxyNode { sourceNode: RenderableTopologyNode | undefined; hasIncomingEdges: boolean; children: D3ProxyNode[]; diff --git a/projects/observability/src/shared/components/topology/renderers/edge/topology-edge-renderer.service.ts b/projects/observability/src/shared/components/topology/renderers/edge/topology-edge-renderer.service.ts index 604755ac3..15d614c91 100644 --- a/projects/observability/src/shared/components/topology/renderers/edge/topology-edge-renderer.service.ts +++ b/projects/observability/src/shared/components/topology/renderers/edge/topology-edge-renderer.service.ts @@ -1,5 +1,5 @@ import { Injectable, Renderer2 } from '@angular/core'; -import { distanceBetweenPoints, getVectorAngleRad } from '@hypertrace/common'; +import { distanceBetweenPoints, getVectorAngleRad, normalizeAngleRadians } from '@hypertrace/common'; import { D3UtilService } from '../../../utils/d3/d3-util.service'; import { RenderableTopologyEdge, TopologyEdge, TopologyEdgeRenderer, TopologyEdgeState } from '../../topology'; @@ -79,7 +79,7 @@ export class TopologyEdgeRendererService implements TopologyEdgeRenderer { } const sourceRad = getVectorAngleRad(edge.source, edge.target); - const targetRad = sourceRad + Math.PI; + const targetRad = normalizeAngleRadians(sourceRad + Math.PI); const sourceAttachPoint = sourceNodeRenderedData.getAttachmentPoint(sourceRad); const targetAttachPoint = targetNodeRenderedData.getAttachmentPoint(targetRad); @@ -88,13 +88,17 @@ export class TopologyEdgeRendererService implements TopologyEdgeRenderer { if (distanceBetweenPoints(edge.source, sourceAttachPoint) > distanceBetweenPoints(edge.source, targetAttachPoint)) { return { source: targetAttachPoint, - target: sourceAttachPoint + sourceRad: targetRad, + target: sourceAttachPoint, + targetRad: sourceRad }; } return { source: sourceAttachPoint, - target: targetAttachPoint + sourceRad: sourceRad, + target: targetAttachPoint, + targetRad: targetRad }; } @@ -112,6 +116,8 @@ export interface TopologyEdgePositionInformation { x: number; y: number; }; + sourceRad: number; + targetRad: number; } export interface TopologyEdgeRenderDelegate { diff --git a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss index 855f71e51..b12b23b5a 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss +++ b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.scss @@ -18,7 +18,7 @@ stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; - stroke: $gray-3; + stroke: $gray-2; fill: none; @include chart-small-regular; diff --git a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts index da93fea4b..c43fb9bbc 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/edge/curved/entity-edge-curve-renderer.service.ts @@ -2,7 +2,7 @@ import { Injectable, Renderer2 } from '@angular/core'; import { Color, DomElementMeasurerService, NumericFormatter, selector } from '@hypertrace/common'; import { MetricAggregation } from '@hypertrace/distributed-tracing'; import { select, Selection } from 'd3-selection'; -import { linkHorizontal } from 'd3-shape'; +import { Link, linkHorizontal } from 'd3-shape'; import { TopologyEdgePositionInformation, TopologyEdgeRenderDelegate @@ -114,7 +114,7 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat selection .select(selector(this.edgeLineClass)) .select('.edge-path') - .attr('stroke', edgeFocusedCategory?.strokeColor ?? Color.Gray3); + .attr('stroke', edgeFocusedCategory?.strokeColor ?? Color.Gray2); selection .select(selector(this.edgeMetricBubbleClass)) @@ -200,16 +200,14 @@ export class EntityEdgeCurveRendererService implements TopologyEdgeRenderDelegat pathSelections.exit().remove(); - pathSelections - .enter() - .append('path') - .merge(pathSelections) - .attr('d', data => - linkHorizontal() - .x(datum => datum.x) - .y(datum => datum.y)(data) - ) - .classed('edge-path', true); + const lineGenerator: Link = linkHorizontal< + TopologyEdgePositionInformation, + Position + >() + .x(datum => datum.x) + .y(datum => datum.y); + + pathSelections.enter().append('path').merge(pathSelections).attr('d', lineGenerator).classed('edge-path', true); } private updateLabelPosition( diff --git a/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts b/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts index 209392247..30d0b64f5 100644 --- a/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts +++ b/projects/observability/src/shared/dashboard/widgets/topology/node/box/entity-node-box-renderer.service.ts @@ -396,7 +396,7 @@ export abstract class EntityNodeBoxRendererService implements TopologyNodeRender } private isAngleInIQuadrant(angle: number): boolean { - return angle > 0 && angle < Math.PI / 2; + return angle >= 0 && angle < Math.PI / 2; } private isAnglePerpendicularlyAbove(angle: number): boolean {