diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 3197c269fd90c..e0a188b4915a2 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -57,6 +57,20 @@ function useCytoscape(options: cytoscape.CytoscapeOptions) { return [ref, cy] as [React.MutableRefObject, cytoscape.Core | undefined]; } +function rotatePoint( + { x, y }: { x: number; y: number }, + degreesRotated: number +) { + const radiansPerDegree = Math.PI / 180; + const θ = radiansPerDegree * degreesRotated; + const cosθ = Math.cos(θ); + const sinθ = Math.sin(θ); + return { + x: x * cosθ - y * sinθ, + y: x * sinθ + y * cosθ + }; +} + function getLayoutOptions( selectedRoots: string[], height: number, @@ -71,10 +85,11 @@ function getLayoutOptions( animate: true, animationEasing: animationOptions.easing, animationDuration: animationOptions.duration, - // Rotate nodes from top -> bottom to display left -> right // @ts-ignore - transform: (node: any, { x, y }: cytoscape.Position) => ({ x: y, y: -x }), - // swap width/height of boundingBox to compensation for the rotation + // Rotate nodes counter-clockwise to transform layout from top→bottom to left→right. + // The extra 5° achieves the effect of separating overlapping taxi-styled edges. + transform: (node: any, pos: cytoscape.Position) => rotatePoint(pos, -95), + // swap width/height of boundingBox to compensate for the rotation boundingBox: { x1: 0, y1: 0, w: height, h: width } }; } @@ -109,20 +124,31 @@ export function Cytoscape({ // is required and can trigger rendering when changed. const divStyle = { ...style, height }; - const dataHandler = useCallback( - event => { + const resetConnectedEdgeStyle = useCallback( + (node?: cytoscape.NodeSingular) => { if (cy) { cy.edges().removeClass('highlight'); - if (serviceName) { - const focusedNode = cy.getElementById(serviceName); - focusedNode.connectedEdges().addClass('highlight'); + if (node) { + node.connectedEdges().addClass('highlight'); } + } + }, + [cy] + ); - // Add the "primary" class to the node if its id matches the serviceName. - if (cy.nodes().length > 0 && serviceName) { - cy.nodes().removeClass('primary'); - cy.getElementById(serviceName).addClass('primary'); + const dataHandler = useCallback( + event => { + if (cy) { + if (serviceName) { + resetConnectedEdgeStyle(cy.getElementById(serviceName)); + // Add the "primary" class to the node if its id matches the serviceName. + if (cy.nodes().length > 0) { + cy.nodes().removeClass('primary'); + cy.getElementById(serviceName).addClass('primary'); + } + } else { + resetConnectedEdgeStyle(); } if (event.cy.elements().length > 0) { const selectedRoots = selectRoots(event.cy); @@ -141,7 +167,7 @@ export function Cytoscape({ } } }, - [cy, serviceName, height, width] + [cy, resetConnectedEdgeStyle, serviceName, height, width] ); // Trigger a custom "data" event when data changes @@ -162,12 +188,20 @@ export function Cytoscape({ event.target.removeClass('hover'); event.target.connectedEdges().removeClass('nodeHover'); }; + const selectHandler: cytoscape.EventHandler = event => { + resetConnectedEdgeStyle(event.target); + }; + const unselectHandler: cytoscape.EventHandler = event => { + resetConnectedEdgeStyle(); + }; if (cy) { cy.on('data', dataHandler); cy.ready(dataHandler); cy.on('mouseover', 'edge, node', mouseoverHandler); cy.on('mouseout', 'edge, node', mouseoutHandler); + cy.on('select', 'node', selectHandler); + cy.on('unselect', 'node', unselectHandler); } return () => { @@ -181,7 +215,7 @@ export function Cytoscape({ cy.removeListener('mouseout', 'edge, node', mouseoutHandler); } }; - }, [cy, dataHandler, serviceName]); + }, [cy, dataHandler, resetConnectedEdgeStyle, serviceName]); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 30b36b58cb001..e19cb8ae4b646 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -121,15 +121,18 @@ const style: cytoscape.Stylesheet[] = [ { selector: 'edge.nodeHover', style: { - width: 4, + width: 2, // @ts-ignore - 'z-index': zIndexEdgeHover + 'z-index': zIndexEdgeHover, + 'line-color': theme.euiColorDarkShade, + 'source-arrow-color': theme.euiColorDarkShade, + 'target-arrow-color': theme.euiColorDarkShade } }, { selector: 'node.hover', style: { - 'border-width': 4 + 'border-width': 2 } }, { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index bea1de18384a3..dba31822dd23e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -79,7 +79,7 @@ export function KueryBar() { const disabled = /\/service-map$/.test(location.pathname); const disabledPlaceholder = i18n.translate( 'xpack.apm.kueryBar.disabledPlaceholder', - { defaultMessage: 'Search is not available for service maps' } + { defaultMessage: 'Search is not available for service map' } ); async function onChange(inputValue: string, selectionStart: number) {