feat: migrate to apache echarts (#4)

Co-authored-by: terraputix <terraputix@mailbox.org>
Reviewed-on: useweb/ombrella#4
This commit is contained in:
2026-02-16 00:23:08 +01:00
co-authored by terraputix
parent 6b48dd6764
commit 4e3ccf1c06
23 changed files with 2373 additions and 1776 deletions
@@ -0,0 +1,118 @@
<!--
ChartContainer.svelte — Consistent chart layout wrapper
Provides a container with:
- Consistent padding and spacing
- Loading overlay with spinner
- Fade transitions
- Responsive min-height calculation
- Slot for chart content
Usage:
<ChartContainer loading={!chartsReady} chartCount={3}>
{#each charts as chart}
<EChart option={chart.option} />
{/each}
</ChartContainer>
-->
<script lang="ts">
import { fade } from 'svelte/transition';
import type { Snippet } from 'svelte';
// ─── Props ──────────────────────────────────────────────────────────────────
interface Props {
/** Whether the charts are still loading */
loading?: boolean;
/** Number of charts being rendered (used for min-height calculation) */
chartCount?: number;
/** Height per individual chart in pixels (default: 300) */
chartHeight?: number;
/** Extra vertical padding in pixels added to the total min-height (default: 2) */
extraPadding?: number;
/** Optional CSS class for the outer wrapper */
class?: string;
/** Slot content (charts go here) */
children?: Snippet;
}
let {
loading = true,
chartCount = 1,
chartHeight = 300,
extraPadding = 2,
class: className = '',
children
}: Props = $props();
// ─── Computed ───────────────────────────────────────────────────────────────
let minHeight = $derived(chartHeight * chartCount + extraPadding);
</script>
<div
class="chart-container relative {className}"
style:min-height="{minHeight}px"
>
<!-- Chart content area -->
<div
class="chart-content"
in:fade={{ duration: 300 }}
out:fade={{ duration: 300 }}
>
{#if children}
{@render children()}
{/if}
</div>
<!-- Loading overlay -->
<div
class="loading-overlay absolute inset-0 z-30 flex items-center justify-center rounded-lg bg-accent transition-opacity duration-300"
class:pointer-events-none={!loading}
class:opacity-0={!loading}
class:opacity-100={loading}
>
<div class="flex flex-col items-center gap-3">
<svg
class="lucide lucide-loader-circle animate-spin text-muted-foreground"
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
<span class="sr-only">Loading charts...</span>
</div>
</div>
</div>
<style>
.chart-container {
/* Negative horizontal margin on mobile to use full width, reset on md+ */
margin-left: -1.5rem;
margin-right: -1.5rem;
}
@media (min-width: 768px) {
.chart-container {
margin-left: 0;
margin-right: 0;
}
}
.chart-content {
width: 100%;
}
/* Smooth transition for the loading overlay */
.loading-overlay {
will-change: opacity;
}
</style>