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

View File

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