Improvements to the cool graph

This commit is contained in:
Andre Henriques 2025-04-09 19:29:16 +01:00
parent 8a38d407c4
commit 842fb23275
2 changed files with 106 additions and 41 deletions

View File

@ -4,6 +4,7 @@
import type { PrevType } from './types';
import { type EventsStat } from '../utils';
import CoolGraphNode from './CoolGraphNode.svelte';
import type { Node } from '../../flow/types';
let {
events
@ -30,9 +31,6 @@
return Object.values(appState);
});
let figHeight = $state(0);
let figWidth = $state(0);
type Pos = {
x: number;
y: number;
@ -163,7 +161,7 @@
return levels;
});
const levelSize = $derived(Math.floor(figWidth / posByDepth.length));
let hovering: Node | undefined = $state();
</script>
{#if statusStore.nodesR && posByDepth.length > 0}
@ -184,9 +182,16 @@
{#snippet children(canvasX, canvasY)}
{#each Object.keys(posByDepth[depth]) as nodeId}
{@const cur = posByDepth[depth][nodeId]}
{@const node = statusStore.nodesR[nodeId === 'null' ? (null as any) : nodeId]}
{@const node = statusStore.nodesR[nodeId]}
{#if node}
<CoolGraphNode {canvasX} {canvasY} {node} from={cur.from} count={cur.count} />
<CoolGraphNode
bind:hovering
{canvasX}
{canvasY}
{node}
from={cur.from}
count={cur.count}
/>
{/if}
{/each}
{/snippet}

View File

@ -10,13 +10,15 @@
canvasX,
canvasY,
count,
from
from,
hovering = $bindable()
}: {
node: Node;
canvasX: FuncBase;
canvasY: FuncBase;
from: PrevType;
count: number;
hovering: Node | undefined;
} = $props();
let fromText = $derived(
@ -31,37 +33,42 @@
to: string;
percentage: number;
rand: number;
randX: number;
randY: number;
randXStart: number;
randXEnd: number;
color: string;
};
let particles: Particle[] = $state([]);
let particlesState: Record<string, number> = $state({});
let particlesState: Record<string, Particle[]> = $state({});
let particleCount = 0;
let MAX_PARTICLES = 200;
let MAX_PARTICLES = 400;
function spwanParticle(key: string) {
if (particles.length > MAX_PARTICLES) return;
if (from.cur[key] <= particlesState[key]) {
if (particleCount > MAX_PARTICLES) return;
if (from.cur[key] <= (particlesState[key] ?? []).length) {
return;
}
particles.push({
const toAdd: Particle = {
percentage: 0,
to: key,
rand: Math.random(),
randX: Math.random(),
randY: Math.random(),
randXStart: Math.floor(Math.random() * 100),
randXEnd: Math.floor(Math.random() * 100),
color: [
'oklch(68.5% 0.169 237.323)',
'oklch(62.3% 0.214 259.815)',
'oklch(62.7% 0.265 303.9)'
][Math.floor(Math.random() * 3)]
});
particlesState[key] = (particlesState[key] ?? 0) + 1;
};
if (particlesState[key]) {
particlesState[key].push(toAdd);
} else {
particlesState[key] = [toAdd];
}
particleCount++;
}
const MAX_ROUNDS = 40;
const MAX_ROUNDS = 200;
let timeout: number | undefined = undefined;
function t(k: number) {
@ -72,39 +79,64 @@
for (const key of Object.keys(from.cur)) {
spwanParticle(key);
}
timeout = setTimeout(() => t(k + 1), Math.random() * 1500) as unknown as number;
timeout = setTimeout(() => t(k + 1), Math.random() * 1000) as unknown as number;
}
$effect(() => {
if (timeout !== undefined) clearTimeout(timeout);
// this is used for making this effect block dependednt on form
// eslint-disable-next-line
const _ = from;
particles = [];
particlesState = {};
timeout = setTimeout(() => t(0), 10) as unknown as number;
return () => {
if (timeout !== undefined) clearTimeout(timeout);
};
});
let showColor = $derived(hovering === undefined || hovering === node);
</script>
<div
class="shadow-sm shadow-violet-500 border rounded-full min-w-[50px] min-h-[50px] grid place-items-center bg-white z-40 absolute"
style="left: {canvasX(node.x + node.width / 2)}px; top: {canvasY(node.y - node.height / 2)}px;"
title={`${node?.name ?? ''}: ${count}\n${fromText}`}
onmouseenter={() => {
hovering = node;
}}
onmouseleave={() => {
hovering = undefined;
}}
role="figure"
>
<span class={`bi bi-${node?.icon ?? ''}`}> </span>
</div>
{#each particles as particle}
{@const to = statusStore.nodesR[particle.to]}
{@const f_x = to.x + to.width / 2 + particle.randX}
{@const f_y = to.y - to.height / 2 + particle.randY}
{@const x = (p: number) => f_x + (node.x + node.width / 2 - f_x) * p}
{@const y = (p: number) => f_y + (node.y - node.height / 2 - f_y) * p}
{#each Object.keys(particlesState) as key}
{@const size = 20}
{@const to = statusStore.nodesR[key]}
{@const f_x = node.x + node.width / 2}
{@const f_y = node.y - node.height / 2}
{@const t_x = to.x + to.width / 2}
{@const t_y = to.y - to.height / 2}
{@const diff = Math.sqrt((f_x - t_x) ** 2 + (f_y - t_y) ** 2)}
{@const theta = Math.atan2(f_x - t_x, f_y - t_y)}
<div
class="animateMove absolute w-2 h-2 text-black rounded-full shadow-lg z-20"
style="--start-x: {canvasX(x(0)) + 25}px; --start-y: {canvasY(y(0)) + 25}px; --end-x: {canvasX(
x(1)
) + 25}px; --end-y: {canvasY(y(1)) + 25}px; background: {particle.color};"
></div>
class="absolute z-30"
style="height: {(25 / 2) *
diff}px; width: {size}px; transform-origin: top center; transform: translate({canvasX(f_x) +
25 -
size / 2}px, {canvasY(f_y) + 25 + size / 8}px) rotate({theta}rad);"
>
{#each particlesState[key] as particle}
<div
class="animateMove absolute w-2 h-2 text-black rounded-full shadow-lg"
style="--start-x: {particle.randXStart}%; --end-x: {particle.randXEnd}%; background: {!showColor
? '#18181818'
: particle.color};"
></div>
{/each}
</div>
{/each}
{#each Object.keys(from.cur) as link}
@ -117,9 +149,11 @@
{@const diff = Math.sqrt((f_x - t_x) ** 2 + (f_y - t_y) ** 2)}
{@const theta = Math.atan2(f_x - t_x, f_y - t_y)}
<div
class="absolute bg-gradient-to-t from-blue-400/70 to-blue-200/70 z-10"
style="height: {(25 / 2) *
diff}px; width: {size}px; transform-origin: top center; transform: translate({canvasX(f_x) +
class="absolute {showColor
? 'bg-gradient-to-t from-blue-400/70 to-blue-200/70'
: 'bg-gray-800/20'} z-10"
style="height: {(25 / 2) * diff}px;
width: {size}px; transform-origin: top center; transform: translate({canvasX(f_x) +
25 -
size / 2}px, {canvasY(f_y) + 25 + size / 8}px) rotate({theta}rad);"
></div>
@ -136,7 +170,9 @@
{@const diff = Math.sqrt((f_x - t_x) ** 2 + (f_y - t_y) ** 2)}
{@const theta = Math.atan2(f_x - t_x, f_y - t_y)}
<div
class="absolute bg-gradient-to-t from-violet-400/70 to-violet-200/70"
class="absolute {showColor
? 'bg-gradient-to-t from-violet-400/70 to-violet-200/70'
: 'bg-gray-600/20'}"
style="height: {(25 / 2) *
diff}px; width: {size}px; transform-origin: top center; transform: translate({canvasX(f_x) +
25 -
@ -156,7 +192,31 @@
{@const diff = Math.sqrt((f_x - t_x) ** 2 + (f_y - t_y) ** 2)}
{@const theta = Math.atan2(f_x - t_x, f_y - t_y)}
<div
class="absolute bg-gradient-to-t from-violet-400/50 to-violet-200/50"
class="absolute {showColor
? 'bg-gradient-to-t from-violet-400/50 to-violet-200/50'
: 'bg-gray-400/20'}"
style="height: {(25 / 2) *
diff}px; width: {size}px; transform-origin: top center; transform: translate({canvasX(f_x) +
25 -
size / 2}px, {canvasY(f_y) + 25 - size / 4}px) rotate({theta}rad); z-index: 2"
></div>
{/each}
{/if}
{#if from.prev && from.prev.prev && from.prev.prev.prev}
{#each Object.keys(from.prev.prev.prev.cur) as link}
{@const size = 2.5}
{@const to = statusStore.nodesR[link]}
{@const f_x = node.x + node.width / 2}
{@const f_y = node.y - node.height / 2}
{@const t_x = to.x + to.width / 2}
{@const t_y = to.y - to.height / 2}
{@const diff = Math.sqrt((f_x - t_x) ** 2 + (f_y - t_y) ** 2)}
{@const theta = Math.atan2(f_x - t_x, f_y - t_y)}
<div
class="absolute {showColor
? 'bg-gradient-to-t from-violet-200/50 to-violet-100/50'
: 'bg-gray-400/20'}"
style="height: {(25 / 2) *
diff}px; width: {size}px; transform-origin: top center; transform: translate({canvasX(f_x) +
25 -
@ -167,15 +227,15 @@
<style>
.animateMove {
animation: animateMoves 5s ease-in-out infinite;
animation: animateMoves 5s ease-in infinite;
}
@keyframes animateMoves {
0% {
top: var(--start-y);
top: 100%;
left: var(--start-x);
}
100% {
top: var(--end-y);
top: 0%;
left: var(--end-x);
}
}