Skip to content

Commit

Permalink
[APM] Service Map - Separate overlapping edges by rotating nodes (#60477
Browse files Browse the repository at this point in the history
) (#60843)

* Adds rotation transform which does the top->bottom to left->right
transformation + an extra 5 degrees which results in taxi edges
separating when rendered.

* PR feedback to reduce edge width on hover, and assure that connected
edges are highlighted when node is selected/focused

* update disabled kuery bar placeholder text for service map

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
ogupte and elasticmachine authored Mar 23, 2020
1 parent 67ed3a3 commit 49f4a1f
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ function useCytoscape(options: cytoscape.CytoscapeOptions) {
return [ref, cy] as [React.MutableRefObject<any>, 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,
Expand All @@ -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 }
};
}
Expand Down Expand Up @@ -109,20 +124,31 @@ export function Cytoscape({
// is required and can trigger rendering when changed.
const divStyle = { ...style, height };

const dataHandler = useCallback<cytoscape.EventHandler>(
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<cytoscape.EventHandler>(
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);
Expand All @@ -141,7 +167,7 @@ export function Cytoscape({
}
}
},
[cy, serviceName, height, width]
[cy, resetConnectedEdgeStyle, serviceName, height, width]
);

// Trigger a custom "data" event when data changes
Expand All @@ -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 () => {
Expand All @@ -181,7 +215,7 @@ export function Cytoscape({
cy.removeListener('mouseout', 'edge, node', mouseoutHandler);
}
};
}, [cy, dataHandler, serviceName]);
}, [cy, dataHandler, resetConnectedEdgeStyle, serviceName]);

return (
<CytoscapeContext.Provider value={cy}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 49f4a1f

Please sign in to comment.