mirror of
https://github.com/umami-software/umami.git
synced 2026-02-15 10:05:36 +01:00
Refactored journey rendering.
This commit is contained in:
parent
b97ac23432
commit
2204ffa9c3
2 changed files with 182 additions and 148 deletions
|
|
@ -104,10 +104,6 @@
|
||||||
background: var(--primary400);
|
background: var(--primary400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item.active .count {
|
|
||||||
background: var(--primary600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.behind {
|
.behind {
|
||||||
color: var(--base400);
|
color: var(--base400);
|
||||||
}
|
}
|
||||||
|
|
@ -130,11 +126,15 @@
|
||||||
background: var(--base200);
|
background: var(--base200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected .count {
|
.item.selected .count {
|
||||||
color: var(--base50);
|
color: var(--base50);
|
||||||
background: var(--base800);
|
background: var(--base800);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item.selected.active .count {
|
||||||
|
background: var(--primary600);
|
||||||
|
}
|
||||||
|
|
||||||
.line {
|
.line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
|
||||||
|
|
@ -28,28 +28,29 @@ export default function JourneyView() {
|
||||||
|
|
||||||
const selectedPaths = selectedNode?.paths ?? [];
|
const selectedPaths = selectedNode?.paths ?? [];
|
||||||
const activePaths = activeNode?.paths ?? [];
|
const activePaths = activeNode?.paths ?? [];
|
||||||
|
const columns = [];
|
||||||
|
|
||||||
return Array(Number(parameters.steps))
|
for (let columnIndex = 0; columnIndex < +parameters.steps; columnIndex++) {
|
||||||
.fill(undefined)
|
const nodes = {};
|
||||||
.map((nodes = {}, index) => {
|
|
||||||
data.forEach(({ items, count }) => {
|
data.forEach(({ items, count }: any, nodeIndex: any) => {
|
||||||
const name = items[index];
|
const name = items[columnIndex];
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
const selected = !!selectedPaths.find(path => path.items[index] === name);
|
const selected = !!selectedPaths.find(path => path.items[columnIndex] === name);
|
||||||
const active = selected && !!activePaths.find(path => path.items[index] === name);
|
const active = selected && !!activePaths.find(path => path.items[columnIndex] === name);
|
||||||
|
|
||||||
if (!nodes[name]) {
|
if (!nodes[name]) {
|
||||||
const paths = data.filter((d, i) => {
|
const paths = data.filter((d, i) => {
|
||||||
return i !== index && d.items[index] === name;
|
return i !== columnIndex && d.items[columnIndex] === name;
|
||||||
});
|
});
|
||||||
|
|
||||||
const from =
|
const from =
|
||||||
index > 0 &&
|
columnIndex > 0 &&
|
||||||
selected &&
|
selected &&
|
||||||
paths.reduce((obj, path) => {
|
paths.reduce((obj, path) => {
|
||||||
const { items, count } = path;
|
const { items, count } = path;
|
||||||
const name = items[index - 1];
|
const name = items[columnIndex - 1];
|
||||||
|
|
||||||
if (!obj[name]) {
|
if (!obj[name]) {
|
||||||
obj[name] = { name, count };
|
obj[name] = { name, count };
|
||||||
|
|
@ -63,76 +64,34 @@ export default function JourneyView() {
|
||||||
nodes[name] = {
|
nodes[name] = {
|
||||||
name,
|
name,
|
||||||
count,
|
count,
|
||||||
total: count,
|
totalCount: count,
|
||||||
columnIndex: index,
|
nodeIndex,
|
||||||
|
columnIndex,
|
||||||
selected,
|
selected,
|
||||||
active,
|
active,
|
||||||
paths,
|
paths,
|
||||||
from: objectToArray(from),
|
from: objectToArray(from),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
nodes[name].total += count;
|
nodes[name].totalCount += count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const nodesArray = objectToArray(nodes).sort(firstBy('total', -1));
|
columns.push({
|
||||||
|
nodes: objectToArray(nodes).sort(firstBy('total', -1)),
|
||||||
return {
|
|
||||||
nodes: nodesArray,
|
|
||||||
visitors: nodesArray.reduce((sum, { selected, total }) => {
|
|
||||||
if (!selectedNode || (selectedNode && selected)) {
|
|
||||||
sum += total;
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}, 0),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}, [data, selectedNode, activeNode]);
|
|
||||||
|
|
||||||
const handleClick = (name: string, index: number, paths: any[]) => {
|
|
||||||
if (name !== selectedNode?.name || index !== selectedNode?.index) {
|
|
||||||
setSelectedNode({ name, index, paths });
|
|
||||||
} else {
|
|
||||||
setSelectedNode(null);
|
|
||||||
setActiveNode(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
columns.forEach((column, columnIndex) => {
|
||||||
<div className={styles.container}>
|
const nodes = column.nodes.map((node, nodeIndex) => {
|
||||||
<div className={styles.view}>
|
const { from, totalCount } = node;
|
||||||
{columns.map((column, columnIndex) => {
|
|
||||||
const previousTotal = columns[columnIndex - 1]?.visitors ?? 0;
|
|
||||||
const dropOff =
|
|
||||||
previousTotal > 0 ? ((column.visitors - previousTotal) / previousTotal) * 100 : 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={columnIndex}
|
|
||||||
className={classNames(styles.column, { [styles.active]: activeNode })}
|
|
||||||
>
|
|
||||||
<div className={styles.header}>
|
|
||||||
<div className={styles.num}>{columnIndex + 1}</div>
|
|
||||||
<div className={styles.stats}>
|
|
||||||
<div className={styles.visitors}>
|
|
||||||
{column.visitors} {formatMessage(labels.visitors)}
|
|
||||||
</div>
|
|
||||||
{columnIndex > 0 && <div className={styles.dropoff}>{`${~~dropOff}%`}</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.nodes}>
|
|
||||||
{column.nodes.map(({ name, total, selected, active, paths, from }, nodeIndex) => {
|
|
||||||
const previousNodes = columns[columnIndex - 1]?.nodes;
|
const previousNodes = columns[columnIndex - 1]?.nodes;
|
||||||
let selectedCount = from?.length ? 0 : total;
|
let selectedCount = from?.length ? 0 : totalCount;
|
||||||
let activeCount = selectedCount;
|
let activeCount = selectedCount;
|
||||||
|
|
||||||
const lines = from?.reduce((arr, { name, count }: any) => {
|
const lines = from?.reduce((arr: any[][], { name, count }: any) => {
|
||||||
const fromIndex = previousNodes.findIndex(node => {
|
const fromIndex = previousNodes.findIndex((node: { name: any; selected: any }) => {
|
||||||
return node.name === name && node.selected;
|
return node.name === name && node.selected;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -152,6 +111,80 @@ export default function JourneyView() {
|
||||||
return arr;
|
return arr;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
return { ...node, selectedCount, activeCount, lines };
|
||||||
|
});
|
||||||
|
|
||||||
|
const visitorCount = nodes.reduce(
|
||||||
|
(sum: number, { selected, selectedCount, active, activeCount, totalCount }) => {
|
||||||
|
if (!selectedNode) {
|
||||||
|
sum += totalCount;
|
||||||
|
} else if (!activeNode && selected) {
|
||||||
|
sum += selectedCount;
|
||||||
|
} else if (active) {
|
||||||
|
sum += activeCount;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const previousTotal = columns[columnIndex - 1]?.visitorCount ?? 0;
|
||||||
|
const dropOff =
|
||||||
|
previousTotal > 0 ? ((visitorCount - previousTotal) / previousTotal) * 100 : 0;
|
||||||
|
|
||||||
|
Object.assign(column, { nodes, visitorCount, dropOff });
|
||||||
|
});
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}, [data, selectedNode, activeNode]);
|
||||||
|
|
||||||
|
const handleClick = (name: string, index: number, paths: any[]) => {
|
||||||
|
if (name !== selectedNode?.name || index !== selectedNode?.index) {
|
||||||
|
setSelectedNode({ name, index, paths });
|
||||||
|
} else {
|
||||||
|
setSelectedNode(null);
|
||||||
|
}
|
||||||
|
setActiveNode(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.view}>
|
||||||
|
{columns.map((column, columnIndex) => {
|
||||||
|
const dropOffPercent = `${~~column.dropOff}%`;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={columnIndex}
|
||||||
|
className={classNames(styles.column, {
|
||||||
|
[styles.selected]: selectedNode,
|
||||||
|
[styles.active]: activeNode,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div className={styles.num}>{columnIndex + 1}</div>
|
||||||
|
<div className={styles.stats}>
|
||||||
|
<div className={styles.visitors}>
|
||||||
|
{column.visitorCount} {formatMessage(labels.visitors)}
|
||||||
|
</div>
|
||||||
|
{columnIndex > 0 && <div className={styles.dropoff}>{dropOffPercent}</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.nodes}>
|
||||||
|
{column.nodes.map(
|
||||||
|
({
|
||||||
|
name,
|
||||||
|
totalCount,
|
||||||
|
selected,
|
||||||
|
active,
|
||||||
|
paths,
|
||||||
|
activeCount,
|
||||||
|
selectedCount,
|
||||||
|
lines,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={name}
|
key={name}
|
||||||
|
|
@ -164,9 +197,9 @@ export default function JourneyView() {
|
||||||
onMouseLeave={() => selected && setActiveNode(null)}
|
onMouseLeave={() => selected && setActiveNode(null)}
|
||||||
>
|
>
|
||||||
<div className={styles.name}>{name}</div>
|
<div className={styles.name}>{name}</div>
|
||||||
<TooltipPopup label="hi" disabled={!selected}>
|
<TooltipPopup label={dropOffPercent} disabled={!selected}>
|
||||||
<div className={styles.count}>
|
<div className={styles.count}>
|
||||||
{selected ? (active ? activeCount : selectedCount) : total}
|
{selected ? (active ? activeCount : selectedCount) : totalCount}
|
||||||
</div>
|
</div>
|
||||||
</TooltipPopup>
|
</TooltipPopup>
|
||||||
{columnIndex < columns.length &&
|
{columnIndex < columns.length &&
|
||||||
|
|
@ -210,7 +243,8 @@ export default function JourneyView() {
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
},
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue