ui: better drop handling, create list of ids for hover and select
events
This commit is contained in:
parent
c82820dbbb
commit
1062f3c2e2
3 changed files with 72 additions and 33 deletions
|
|
@ -2,41 +2,31 @@ import React from 'react';
|
|||
import type { Data } from '../SankeyChart';
|
||||
|
||||
function DroppedSessionsList({
|
||||
data,
|
||||
colorMap,
|
||||
onHover,
|
||||
dropsByUrl,
|
||||
onLeave,
|
||||
}: {
|
||||
data: Data;
|
||||
colorMap: Map<string, string>;
|
||||
onHover: (dataIndex: any[]) => void;
|
||||
dropsByUrl: Record<string, { drop: number, ids: any[] }> | null;
|
||||
onLeave: () => void;
|
||||
}) {
|
||||
const dropsByUrl: Record<string, number> = {};
|
||||
|
||||
data.links.forEach((link) => {
|
||||
const targetNode = data.nodes.find((node) => node.id === link.target);
|
||||
const sourceNode = data.nodes.find((node) => node.id === link.source);
|
||||
if (!targetNode || !sourceNode) return;
|
||||
|
||||
const isDrop = targetNode.eventType === 'DROP';
|
||||
if (!isDrop) return;
|
||||
|
||||
const sourceUrl = sourceNode.name;
|
||||
console.log(link, sourceUrl, dropsByUrl);
|
||||
if (sourceUrl) {
|
||||
dropsByUrl[sourceUrl] = (dropsByUrl[sourceUrl] || 0) + link.sessionsCount;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(colorMap, dropsByUrl)
|
||||
if (!dropsByUrl) return null;
|
||||
const totalDropSessions = Object.values(dropsByUrl).reduce(
|
||||
(sum, count) => sum + count,
|
||||
(sum, { drop }) => sum + drop,
|
||||
0,
|
||||
);
|
||||
|
||||
const sortedDrops = Object.entries(dropsByUrl)
|
||||
.map(([url, count]) => ({
|
||||
.map(([url, { drop, ids }]) => ({
|
||||
url,
|
||||
count,
|
||||
percentage: Math.round((count / totalDropSessions) * 100),
|
||||
drop,
|
||||
ids,
|
||||
percentage: Math.round((drop / totalDropSessions) * 100),
|
||||
}))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
.sort((a, b) => b.drop - a.drop);
|
||||
return (
|
||||
<div className="bg-white rounded-lg border shadow p-4 h-fit min-w-[210px] w-1/3">
|
||||
<h3 className="text-lg font-medium mb-3">Sessions Drop by Page</h3>
|
||||
|
|
@ -46,6 +36,8 @@ function DroppedSessionsList({
|
|||
<div
|
||||
key={index}
|
||||
className="py-1.5 px-2 hover:bg-gray-50 rounded transition-colors flex justify-between gap-2 relative"
|
||||
onMouseEnter={() => onHover(item.ids)}
|
||||
onMouseLeave={() => onLeave()}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -59,7 +51,7 @@ function DroppedSessionsList({
|
|||
}}
|
||||
/>
|
||||
<span className="truncate flex-1">{item.url}</span>
|
||||
<span className="ml-2"> {item.count}</span>
|
||||
<span className="ml-2"> {item.drop}</span>
|
||||
<span className="text-gray-400">({item.percentage}%)</span>
|
||||
</div>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ const EChartsSunburst = (props: Props) => {
|
|||
const { data, height = 240 } = props;
|
||||
const chartRef = React.useRef<HTMLDListElement>(null);
|
||||
const [colors, setColors] = React.useState<Map<string, string>>(new Map());
|
||||
const [chartInst, setChartInst] = React.useState<echarts.ECharts | null>(null);
|
||||
const [dropsByUrl, setDropsByUrl] = React.useState<any>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!chartRef.current || data.nodes.length === 0 || data.links.length === 0)
|
||||
return;
|
||||
|
||||
const chart = echarts.init(chartRef.current);
|
||||
const { tree, colors } = convertSankeyToSunburst(data);
|
||||
const { tree, colors, dropsByUrl } = convertSankeyToSunburst(data);
|
||||
const singleRoot =
|
||||
data.nodes.reduce((acc, node) => {
|
||||
if (node.depth === 0) {
|
||||
|
|
@ -30,11 +32,12 @@ const EChartsSunburst = (props: Props) => {
|
|||
}
|
||||
return acc;
|
||||
}, 0) === 1;
|
||||
const finalData = singleRoot ? tree.children : [tree]
|
||||
const options = {
|
||||
...defaultOptions,
|
||||
series: {
|
||||
type: 'sunburst',
|
||||
data: singleRoot ? tree.children : [tree],
|
||||
data: finalData,
|
||||
radius: [30, '90%'],
|
||||
itemStyle: {
|
||||
borderRadius: 6,
|
||||
|
|
@ -50,13 +53,18 @@ const EChartsSunburst = (props: Props) => {
|
|||
},
|
||||
},
|
||||
};
|
||||
console.log(finalData)
|
||||
chart.setOption(options);
|
||||
const ro = new ResizeObserver(() => chart.resize());
|
||||
ro.observe(chartRef.current);
|
||||
setColors(colors);
|
||||
setChartInst(chart);
|
||||
setDropsByUrl(dropsByUrl);
|
||||
return () => {
|
||||
chart.dispose();
|
||||
ro.disconnect();
|
||||
setChartInst(null);
|
||||
setDropsByUrl(null);
|
||||
};
|
||||
}, [data, height]);
|
||||
|
||||
|
|
@ -65,6 +73,19 @@ const EChartsSunburst = (props: Props) => {
|
|||
height,
|
||||
flex: 1,
|
||||
};
|
||||
|
||||
const onHover = (dataIndex: any[]) => {
|
||||
chartInst?.dispatchAction({
|
||||
type: 'highlight',
|
||||
dataIndex,
|
||||
})
|
||||
}
|
||||
const onLeave = () => {
|
||||
chartInst?.dispatchAction({
|
||||
type: 'downplay',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -82,7 +103,7 @@ const EChartsSunburst = (props: Props) => {
|
|||
style={containerStyle}
|
||||
className="min-w-[600px] relative"
|
||||
/>
|
||||
<DroppedSessionsList colorMap={colors} data={data} />
|
||||
<DroppedSessionsList dropsByUrl={dropsByUrl} onHover={onHover} onLeave={onLeave} colorMap={colors} data={data} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const colorMap = new Map();
|
|||
export function convertSankeyToSunburst(data: Data): {
|
||||
tree: SunburstChild;
|
||||
colors: Map<string, string>;
|
||||
dropsByUrl: Record<string, { drop: number, ids: any[] }>;
|
||||
} {
|
||||
const dataLinks = data.links.filter((link) => {
|
||||
const sourceNode = data.nodes.find((node) => node.id === link.source);
|
||||
|
|
@ -34,8 +35,26 @@ export function convertSankeyToSunburst(data: Data): {
|
|||
}));
|
||||
|
||||
const nodesById: Record<number, (typeof nodesCopy)[number]> = {};
|
||||
const dropsByUrl: Record<string, { drop: number, ids: any[] }> = {};
|
||||
dataLinks.forEach((link) => {
|
||||
const targetNode = nodesCopy.find((node) => node.id === link.target);
|
||||
const sourceNode = nodesCopy.find((node) => node.id === link.source);
|
||||
if (!targetNode || !sourceNode) return;
|
||||
|
||||
const isDrop = targetNode.eventType === 'DROP';
|
||||
if (!isDrop) return;
|
||||
|
||||
const sourceUrl = sourceNode.name;
|
||||
if (sourceUrl) {
|
||||
if (dropsByUrl[sourceUrl]) {
|
||||
dropsByUrl[sourceUrl].drop = dropsByUrl[sourceUrl].drop + link.sessionsCount;
|
||||
} else {
|
||||
dropsByUrl[sourceUrl] = { drop: link.sessionsCount, ids: [] };
|
||||
}
|
||||
}
|
||||
});
|
||||
nodesCopy.forEach((node) => {
|
||||
nodesById[node.id as number] = node;
|
||||
nodesById[node.id as number] = { ...node, dataIndex: node.id };
|
||||
});
|
||||
|
||||
dataLinks.forEach((link) => {
|
||||
|
|
@ -67,7 +86,6 @@ export function convertSankeyToSunburst(data: Data): {
|
|||
|
||||
const rootNode = nodesById[0];
|
||||
const nameCount: Record<string, number> = {};
|
||||
let dataIndex = 0;
|
||||
function buildSunburstNode(node: SunburstChild): SunburstChild | null {
|
||||
if (!node) return null;
|
||||
// eventType === DROP
|
||||
|
|
@ -76,22 +94,29 @@ export function convertSankeyToSunburst(data: Data): {
|
|||
// colorMap.set('DROP', 'black')
|
||||
return null;
|
||||
}
|
||||
|
||||
let color = colorMap.get(node.name);
|
||||
if (!color) {
|
||||
color = randomColor(colorMap.size);
|
||||
colorMap.set(node.name, color);
|
||||
}
|
||||
let nodeName;
|
||||
if (node.name.includes('feature/sess')) {
|
||||
console.log(node)
|
||||
}
|
||||
if (nameCount[node.name]) {
|
||||
nodeName = `${node.name}_$$$_${nameCount[node.name]++}`;
|
||||
nodeName = `${node.name}_OPENREPLAY_NODE_${nameCount[node.name]++}`;
|
||||
} else {
|
||||
nodeName = node.name;
|
||||
nameCount[node.name] = 1;
|
||||
}
|
||||
if (dropsByUrl[node.name]) {
|
||||
dropsByUrl[node.name].ids.push(node.dataIndex);
|
||||
}
|
||||
const result: SunburstChild = {
|
||||
name: nodeName,
|
||||
value: node.value || 0,
|
||||
dataIndex: dataIndex++,
|
||||
dataIndex: node.dataIndex,
|
||||
itemStyle: {
|
||||
color,
|
||||
},
|
||||
|
|
@ -109,6 +134,7 @@ export function convertSankeyToSunburst(data: Data): {
|
|||
return {
|
||||
tree: buildSunburstNode(rootNode) as SunburstChild,
|
||||
colors: colorMap,
|
||||
dropsByUrl,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +151,7 @@ export function sunburstTooltip(colorMap: Map<string, string>) {
|
|||
if ('name' in params.data) {
|
||||
const color = colorMap.get(params.data.name);
|
||||
const clearName = params.data.name
|
||||
? params.data.name.split('_$$$_')[0]
|
||||
? params.data.name.split('_OPENREPLAY_NODE_')[0]
|
||||
: 'Total';
|
||||
return `
|
||||
<div class="flex flex-col bg-white p-2 rounded shadow border">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue