Skip to content

Commit 44aa6a5

Browse files
authored
vis: TopologyView: add activity animations (#1622)
1 parent 29f9126 commit 44aa6a5

File tree

3 files changed

+103
-11
lines changed

3 files changed

+103
-11
lines changed

packages/ott-vis-panel/src/components/views/TopologyView.tsx

+88-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import "./topology-view.css";
1717
import { useColorProvider } from "colors";
1818
import { useD3Zoom } from "chartutils";
1919
import { dedupeItems } from "aggregate";
20+
import { useEventBus } from "eventbus";
2021

2122
/**
2223
* The goal of this component is to show a more accurate topology view from the perspective of actual network connections.
@@ -164,6 +165,7 @@ export const TopologyView: React.FC<TopologyViewProps> = ({
164165
.attr("data-nodeid-target", d => d.target.data.id)
165166
.transition(tr)
166167
.attr("d", diagonal)
168+
.attr("stroke", "#fff")
167169
.attr("stroke-width", 1.5);
168170

169171
group
@@ -191,7 +193,9 @@ export const TopologyView: React.FC<TopologyViewProps> = ({
191193
.attr("cx", (d: any) => d.x)
192194
.attr("cy", (d: any) => d.y)
193195
.attr("r", d => getRadius(d.data.group))
194-
.attr("fill", d => colors.assign(d.data.group));
196+
.attr("fill", d => colors.assign(d.data.group))
197+
.attr("stroke", "#fff")
198+
.attr("stroke-width", 2);
195199

196200
group
197201
.select(".texts")
@@ -354,9 +358,12 @@ export const TopologyView: React.FC<TopologyViewProps> = ({
354358
update => update,
355359
exit => exit.transition(tr).attr("stroke-width", 0).remove()
356360
)
361+
.attr("data-nodeid-source", d => d.source.tree.data.id)
362+
.attr("data-nodeid-target", d => d.target.tree.data.id)
357363
.transition(tr)
358364
.attr("d", diagonal)
359-
.attr("stroke-width", 1.5);
365+
.attr("stroke-width", 1.5)
366+
.attr("stroke", "#fff");
360367
}
361368

362369
const monolithBuiltRegions = new Map<string, Region>();
@@ -400,6 +407,85 @@ export const TopologyView: React.FC<TopologyViewProps> = ({
400407

401408
useD3Zoom(svgRef);
402409

410+
const eventBus = useEventBus();
411+
useEffect(() => {
412+
const rxColor = "#0f0";
413+
const txColor = "#00f";
414+
415+
function animateNode(
416+
node: d3.Selection<any, d3.HierarchyNode<TreeNode>, any, any>,
417+
color: string
418+
) {
419+
if (node.empty()) {
420+
return;
421+
}
422+
const data = node.data()[0] as d3.HierarchyNode<TreeNode>;
423+
const endRadius = data ? getRadius(data.data.group) : 20;
424+
let radiusCurrent = parseFloat(node.attr("r"));
425+
let colorCurrent = d3.color(node.attr("stroke"));
426+
if (isNaN(radiusCurrent)) {
427+
radiusCurrent = 0;
428+
}
429+
const newRadius = Math.max(Math.min(radiusCurrent + 5, 40), endRadius);
430+
const newColor = d3.interpolateRgb(colorCurrent?.formatRgb() ?? "#fff", color)(0.5);
431+
node.transition("highlight")
432+
.duration(333)
433+
.ease(d3.easeCubicOut)
434+
.attrTween("stroke", () => d3.interpolateRgb(newColor, "#fff"))
435+
.attrTween("stroke-width", () => t => d3.interpolateNumber(4, 1.5)(t).toString())
436+
.attrTween(
437+
"r",
438+
() => t => d3.interpolateNumber(newRadius, endRadius)(t).toString()
439+
);
440+
}
441+
442+
function animateLinks(
443+
links: d3.Selection<any, d3.HierarchyLink<TreeNode>, any, any>,
444+
color: string
445+
) {
446+
links
447+
.transition("highlight")
448+
.duration(333)
449+
.ease(d3.easeCubicOut)
450+
.attrTween("stroke", function () {
451+
const link = d3.select<d3.BaseType, d3.HierarchyLink<TreeNode>>(
452+
this
453+
) as d3.Selection<any, d3.HierarchyLink<TreeNode>, any, unknown>;
454+
let colorCurrent = d3.color(link.attr("stroke"));
455+
const newColor = d3.interpolateRgb(
456+
colorCurrent?.formatRgb() ?? "#fff",
457+
color
458+
)(0.5);
459+
460+
return d3.interpolateRgb(newColor, "#fff");
461+
})
462+
.attrTween("stroke-width", () => t => d3.interpolateNumber(4, 1.5)(t).toString());
463+
}
464+
465+
const sub = eventBus.subscribe(event => {
466+
const color = event.direction === "rx" ? rxColor : txColor;
467+
const node = d3.select<d3.BaseType, d3.HierarchyNode<TreeNode>>(
468+
`[data-nodeid="${event.node_id}"]`
469+
);
470+
animateNode(node, color);
471+
if (!node.empty()) {
472+
const parent = d3.select<d3.BaseType, d3.HierarchyNode<TreeNode>>(
473+
`[data-nodeid="${node.datum().parent?.data.id}"]`
474+
);
475+
animateNode(parent, color);
476+
}
477+
478+
const links = d3.selectAll<d3.BaseType, d3.HierarchyLink<TreeNode>>(
479+
`[data-nodeid-target="${event.node_id}"]`
480+
);
481+
animateLinks(links, color);
482+
});
483+
484+
return () => {
485+
sub.unsubscribe();
486+
};
487+
}, [eventBus, getRadius]);
488+
403489
return (
404490
<svg
405491
viewBox={`${-width / 2} ${-height / 2} ${width} ${height}`}

packages/ott-vis-panel/src/components/views/topology-view.css

-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
.links {
2-
stroke: white;
3-
stroke-width: 1.5;
42
fill: none;
53
}
64

7-
.node {
8-
stroke: white;
9-
stroke-width: 1.5;
10-
}
11-
125
.text {
136
font-family: Inter, Helvetica, Arial, sans-serif;
147
text-anchor: middle;

packages/ott-vis/provisioning/dashboards/topology-dash.json

+15-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
},
3535
"id": 1,
3636
"options": {
37+
"topology": {
38+
"baseNodeRadius": 20,
39+
"clientNodeRadius": 8
40+
},
3741
"tree": {
3842
"b2mLinkStyle": "smooth",
3943
"b2mSpacing": 300,
@@ -51,13 +55,22 @@
5155
"uid": "P8AFEECD30EDC727B"
5256
},
5357
"refId": "A"
58+
},
59+
{
60+
"datasource": {
61+
"type": "ott-vis-datasource",
62+
"uid": "P8AFEECD30EDC727B"
63+
},
64+
"hide": false,
65+
"refId": "B",
66+
"stream": true
5467
}
5568
],
5669
"title": "Panel Title",
5770
"type": "ott-vis-panel"
5871
}
5972
],
60-
"refresh": "",
73+
"refresh": "5s",
6174
"schemaVersion": 38,
6275
"style": "dark",
6376
"tags": [],
@@ -72,6 +85,6 @@
7285
"timezone": "",
7386
"title": "Just TopologyView",
7487
"uid": "e3771ad0-95f0-4be2-932d-259323694d10",
75-
"version": 2,
88+
"version": 3,
7689
"weekStart": ""
7790
}

0 commit comments

Comments
 (0)