mirror of
https://github.com/umami-software/umami.git
synced 2026-02-09 07:07:17 +01:00
Refactored line rendering.
This commit is contained in:
parent
d5e14fb5a8
commit
8362d883d1
2 changed files with 151 additions and 156 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
--journey-line-color: var(--base600);
|
--journey-line-color: var(--base600);
|
||||||
--journey-active-color: var(--primary400);
|
--journey-active-color: var(--primary400);
|
||||||
|
--journey-faded-color: var(--base300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.view {
|
.view {
|
||||||
|
|
@ -71,16 +72,21 @@
|
||||||
background: var(--base100);
|
background: var(--base100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.item.selected {
|
||||||
color: var(--base75);
|
color: var(--base75);
|
||||||
background: var(--base900);
|
background: var(--base900);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item.active {
|
.item.active {
|
||||||
|
color: var(--light50);
|
||||||
background: var(--primary400);
|
background: var(--primary400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item.active .count {
|
||||||
|
background: var(--primary600);
|
||||||
|
}
|
||||||
|
|
||||||
.behind {
|
.behind {
|
||||||
color: var(--base400);
|
color: var(--base400);
|
||||||
}
|
}
|
||||||
|
|
@ -110,119 +116,136 @@
|
||||||
|
|
||||||
.line {
|
.line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
left: -100px;
|
left: -100px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.line.flat:before {
|
.line.up {
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 97px;
|
|
||||||
left: 3px;
|
|
||||||
border-top: 3px solid var(--journey-line-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.line.fromUp:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -30px;
|
|
||||||
left: 50px;
|
|
||||||
margin-top: 3px;
|
|
||||||
margin-left: -3px;
|
|
||||||
border: 0;
|
|
||||||
border-bottom-left-radius: 100%;
|
|
||||||
border-left: 3px solid var(--journey-line-color);
|
|
||||||
border-bottom: 3px solid var(--journey-line-color);
|
|
||||||
width: 50px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line.fromDown:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 50px;
|
|
||||||
margin-left: -3px;
|
|
||||||
border: 0;
|
|
||||||
border-top-left-radius: 100%;
|
|
||||||
border-left: 3px solid var(--journey-line-color);
|
|
||||||
border-top: 3px solid var(--journey-line-color);
|
|
||||||
width: 50px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line.fromUp:after,
|
|
||||||
.line.fromDown:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
right: -6px;
|
|
||||||
margin: 0;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
border-radius: 100%;
|
|
||||||
border: 3px solid var(--journey-line-color);
|
|
||||||
background: var(--base50);
|
|
||||||
width: 13px;
|
|
||||||
height: 13px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.line.toUp:before {
|
.line.down {
|
||||||
content: '';
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -30px;
|
}
|
||||||
margin-top: 3px;
|
|
||||||
right: -350px;
|
.start {
|
||||||
border: 0;
|
left: 0;
|
||||||
border-bottom-right-radius: 100%;
|
|
||||||
border-right: 3px solid var(--journey-line-color);
|
|
||||||
border-bottom: 3px solid var(--journey-line-color);
|
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.line.toDown:before {
|
.mid {
|
||||||
content: '';
|
top: 60px;
|
||||||
position: absolute;
|
width: 50px;
|
||||||
top: 0;
|
border-right: 3px solid var(--journey-line-color);
|
||||||
right: -350px;
|
}
|
||||||
|
|
||||||
|
.end {
|
||||||
|
right: 0;
|
||||||
|
width: 50px;
|
||||||
|
height: 30px;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.up .start {
|
||||||
|
top: 30px;
|
||||||
border-top-right-radius: 100%;
|
border-top-right-radius: 100%;
|
||||||
border-right: 3px solid var(--journey-line-color);
|
|
||||||
border-top: 3px solid var(--journey-line-color);
|
border-top: 3px solid var(--journey-line-color);
|
||||||
width: 50px;
|
border-right: 3px solid var(--journey-line-color);
|
||||||
height: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.line.toUp:after,
|
.up .end {
|
||||||
.line.toDown:after {
|
width: 53px;
|
||||||
|
bottom: 27px;
|
||||||
|
right: 0;
|
||||||
|
border-bottom-left-radius: 100%;
|
||||||
|
border-bottom: 3px solid var(--journey-line-color);
|
||||||
|
border-left: 3px solid var(--journey-line-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.down .start {
|
||||||
|
bottom: 27px;
|
||||||
|
border-bottom-right-radius: 100%;
|
||||||
|
border-bottom: 3px solid var(--journey-line-color);
|
||||||
|
border-right: 3px solid var(--journey-line-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.down .end {
|
||||||
|
width: 53px;
|
||||||
|
top: 30px;
|
||||||
|
right: 0;
|
||||||
|
border-top-left-radius: 100%;
|
||||||
|
border-top: 3px solid var(--journey-line-color);
|
||||||
|
border-left: 3px solid var(--journey-line-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flat .start {
|
||||||
|
top: 30px;
|
||||||
|
width: 50px;
|
||||||
|
border-top: 3px solid var(--journey-line-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flat .end {
|
||||||
|
right: 0;
|
||||||
|
bottom: 27px;
|
||||||
|
width: 50px;
|
||||||
|
border-bottom: 3px solid var(--journey-line-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.start:before,
|
||||||
|
.end:before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -6px;
|
|
||||||
right: -306px;
|
|
||||||
margin: 0;
|
|
||||||
bottom: 0;
|
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
border: 3px solid var(--journey-line-color);
|
border: 3px solid var(--journey-line-color);
|
||||||
background: var(--base50);
|
background: var(--light50);
|
||||||
width: 13px;
|
width: 13px;
|
||||||
height: 13px;
|
height: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar {
|
.line:not(.active) .start:before,
|
||||||
position: absolute;
|
.line:not(.active) .end:before {
|
||||||
left: -100px;
|
display: none;
|
||||||
width: 50px;
|
|
||||||
height: 20px;
|
|
||||||
border-right: 3px solid var(--journey-line-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item.active .bar,
|
.up .start:before,
|
||||||
.item.active .line:before,
|
.flat .start:before {
|
||||||
.item.active .line:after {
|
left: -8px;
|
||||||
|
top: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.up .end:before,
|
||||||
|
.flat .end:before {
|
||||||
|
right: -8px;
|
||||||
|
bottom: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.down .start:before {
|
||||||
|
left: -8px;
|
||||||
|
bottom: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.down .end:before {
|
||||||
|
right: -8px;
|
||||||
|
top: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line.active .segment,
|
||||||
|
.line.active .segment:before {
|
||||||
border-color: var(--journey-active-color);
|
border-color: var(--journey-active-color);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item.active .count {
|
.column.active .line:not(.active) .segment {
|
||||||
background: var(--primary600);
|
border-color: var(--journey-faded-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.column.active .line:not(.active) .segment:before {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import styles from './JourneyView.module.css';
|
||||||
|
|
||||||
const NODE_HEIGHT = 60;
|
const NODE_HEIGHT = 60;
|
||||||
const NODE_GAP = 10;
|
const NODE_GAP = 10;
|
||||||
const BAR_OFFSET = 3;
|
const LINE_WIDTH = 3;
|
||||||
|
|
||||||
export default function JourneyView() {
|
export default function JourneyView() {
|
||||||
const [selectedNode, setSelectedNode] = useState(null);
|
const [selectedNode, setSelectedNode] = useState(null);
|
||||||
|
|
@ -49,20 +49,6 @@ export default function JourneyView() {
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
const to =
|
|
||||||
selected &&
|
|
||||||
paths.reduce((obj, path) => {
|
|
||||||
const { items, count } = path;
|
|
||||||
const name = items[index + 1];
|
|
||||||
if (name) {
|
|
||||||
if (!obj[name]) {
|
|
||||||
obj[name] = { name, count: +count };
|
|
||||||
} else {
|
|
||||||
obj[name].count += +count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
column[name] = {
|
column[name] = {
|
||||||
name,
|
name,
|
||||||
|
|
@ -71,7 +57,6 @@ export default function JourneyView() {
|
||||||
selected,
|
selected,
|
||||||
paths,
|
paths,
|
||||||
from: objectToArray(from),
|
from: objectToArray(from),
|
||||||
to: objectToArray(to),
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
column[name].total += +count;
|
column[name].total += +count;
|
||||||
|
|
@ -103,65 +88,30 @@ export default function JourneyView() {
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.view}>
|
<div className={styles.view}>
|
||||||
{columns.map((column, columnIndex) => {
|
{columns.map((column, columnIndex) => {
|
||||||
const current = columnIndex === selectedNode?.index;
|
|
||||||
const behind = columnIndex <= selectedNode?.index - 1;
|
|
||||||
const ahead = columnIndex > selectedNode?.index;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={columnIndex}
|
key={columnIndex}
|
||||||
className={classNames(styles.column, {
|
className={classNames(styles.column, { [styles.active]: activeNode })}
|
||||||
[styles.current]: current,
|
|
||||||
[styles.behind]: behind,
|
|
||||||
[styles.ahead]: ahead,
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.num}>{columnIndex + 1}</div>
|
<div className={styles.num}>{columnIndex + 1}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.nodes}>
|
<div className={styles.nodes}>
|
||||||
{column.nodes.map(({ name, total, selected, paths, from, to }, nodeIndex) => {
|
{column.nodes.map(({ name, total, selected, paths, from }, nodeIndex) => {
|
||||||
const active =
|
const active =
|
||||||
selected && activeNode?.paths.find(path => path.items[columnIndex] === name);
|
selected && activeNode?.paths.find(path => path.items[columnIndex] === name);
|
||||||
const bars = [];
|
|
||||||
const lines = from?.reduce(
|
|
||||||
(obj: { flat: boolean; fromUp: boolean; fromDown: boolean }, { name }: any) => {
|
|
||||||
const fromIndex = columns[columnIndex - 1]?.nodes.findIndex(node => {
|
|
||||||
return node.name === name && node.selected;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (fromIndex > -1) {
|
const lines = from?.reduce((arr, { name }: any) => {
|
||||||
if (nodeIndex === fromIndex) {
|
const fromIndex = columns[columnIndex - 1]?.nodes.findIndex(node => {
|
||||||
obj.flat = true;
|
|
||||||
} else if (nodeIndex > fromIndex) {
|
|
||||||
obj.fromUp = true;
|
|
||||||
bars.push([fromIndex, nodeIndex, 1]);
|
|
||||||
} else if (nodeIndex < fromIndex) {
|
|
||||||
obj.fromDown = true;
|
|
||||||
bars.push([nodeIndex, fromIndex, 0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
to?.reduce((obj: { toUp: boolean; toDown: boolean }, { name }: any) => {
|
|
||||||
const toIndex = columns[columnIndex + 1]?.nodes.findIndex(node => {
|
|
||||||
return node.name === name && node.selected;
|
return node.name === name && node.selected;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (toIndex > -1) {
|
if (fromIndex > -1) {
|
||||||
if (nodeIndex > toIndex) {
|
arr.push([fromIndex, nodeIndex]);
|
||||||
obj.toUp = true;
|
|
||||||
} else if (nodeIndex < toIndex) {
|
|
||||||
obj.toDown = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return arr;
|
||||||
}, lines);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -176,21 +126,43 @@ export default function JourneyView() {
|
||||||
>
|
>
|
||||||
<div className={styles.name}>{name}</div>
|
<div className={styles.name}>{name}</div>
|
||||||
<div className={styles.count}>{total}</div>
|
<div className={styles.count}>{total}</div>
|
||||||
{Object.keys(lines).map(key => {
|
|
||||||
return <div key={key} className={classNames(styles.line, styles[key])} />;
|
|
||||||
})}
|
|
||||||
{columnIndex < columns.length &&
|
{columnIndex < columns.length &&
|
||||||
bars.map(([a, b, d], i) => {
|
lines.map(([fromIndex, nodeIndex], i) => {
|
||||||
const height = (b - a - 1) * (NODE_HEIGHT + NODE_GAP) + NODE_GAP;
|
const height =
|
||||||
|
(Math.abs(nodeIndex - fromIndex) + 1) * (NODE_HEIGHT + NODE_GAP) -
|
||||||
|
NODE_GAP;
|
||||||
|
const midHeight =
|
||||||
|
(Math.abs(nodeIndex - fromIndex) - 1) * (NODE_HEIGHT + NODE_GAP) +
|
||||||
|
NODE_GAP +
|
||||||
|
LINE_WIDTH;
|
||||||
|
const nodeName = columns[columnIndex - 1]?.nodes[fromIndex].name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={`${fromIndex}${nodeIndex}${i}`}
|
||||||
className={styles.bar}
|
className={classNames(styles.line, {
|
||||||
style={{
|
[styles.active]:
|
||||||
height: height + BAR_OFFSET,
|
active &&
|
||||||
top: d ? -height : NODE_HEIGHT,
|
activeNode?.paths.find(
|
||||||
}}
|
path =>
|
||||||
/>
|
path.items[columnIndex] === name &&
|
||||||
|
path.items[columnIndex - 1] === nodeName,
|
||||||
|
),
|
||||||
|
[styles.up]: fromIndex < nodeIndex,
|
||||||
|
[styles.down]: fromIndex > nodeIndex,
|
||||||
|
[styles.flat]: fromIndex === nodeIndex,
|
||||||
|
})}
|
||||||
|
style={{ height }}
|
||||||
|
>
|
||||||
|
<div className={classNames(styles.segment, styles.start)} />
|
||||||
|
<div
|
||||||
|
className={classNames(styles.segment, styles.mid)}
|
||||||
|
style={{
|
||||||
|
height: midHeight,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className={classNames(styles.segment, styles.end)} />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue