Public, read-only page over the ned-collector energy schema: latest-hour mix and per-source utilization, generation vs consumption with CO2 intensity (day/week/month/12 months/all years), NED forecast, gas storage by year and per site, and gas-fired power share vs storage fill. Incomplete hours (sources not summing to the NED total) are left out and partial days/months/years are marked.
105 lines
2.5 KiB
Svelte
105 lines
2.5 KiB
Svelte
<script lang="ts" module>
|
|
export type ChartTheme = {
|
|
text: string;
|
|
muted: string;
|
|
border: string;
|
|
surface: string;
|
|
dark: boolean;
|
|
};
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import type { ECharts, EChartsCoreOption } from 'echarts/core';
|
|
|
|
// Generic ECharts wrapper for the energy tracker: same lazy-loading and theme handling as
|
|
// the CS price chart, but the caller builds the option from the current theme colours.
|
|
let {
|
|
option,
|
|
label,
|
|
height = 'min(52vh, 440px)'
|
|
}: {
|
|
option: (theme: ChartTheme) => EChartsCoreOption;
|
|
label: string;
|
|
height?: string;
|
|
} = $props();
|
|
let element: HTMLDivElement;
|
|
let chart: ECharts | undefined;
|
|
let ready = $state(false);
|
|
|
|
function update() {
|
|
if (!chart) return;
|
|
const style = getComputedStyle(document.documentElement);
|
|
const read = (name: string) => style.getPropertyValue(name).trim();
|
|
chart.setOption(
|
|
option({
|
|
text: read('--site-text'),
|
|
muted: read('--site-muted'),
|
|
border: read('--site-border'),
|
|
surface: read('--site-surface'),
|
|
dark: read('--site-bg') === '#000'
|
|
}),
|
|
true
|
|
);
|
|
}
|
|
|
|
onMount(() => {
|
|
let disposed = false;
|
|
let resize: ResizeObserver | undefined;
|
|
let theme: MutationObserver | undefined;
|
|
import('echarts/core').then(async (echarts) => {
|
|
const [
|
|
{ LineChart, BarChart, PieChart },
|
|
{ GridComponent, TooltipComponent, LegendComponent, DataZoomComponent, MarkAreaComponent },
|
|
{ CanvasRenderer }
|
|
] = await Promise.all([
|
|
import('echarts/charts'),
|
|
import('echarts/components'),
|
|
import('echarts/renderers')
|
|
]);
|
|
if (disposed) return;
|
|
echarts.use([
|
|
LineChart,
|
|
BarChart,
|
|
PieChart,
|
|
GridComponent,
|
|
TooltipComponent,
|
|
LegendComponent,
|
|
DataZoomComponent,
|
|
MarkAreaComponent,
|
|
CanvasRenderer
|
|
]);
|
|
chart = echarts.init(element, undefined, { renderer: 'canvas' });
|
|
ready = true;
|
|
update();
|
|
resize = new ResizeObserver(() => chart?.resize());
|
|
resize.observe(element);
|
|
theme = new MutationObserver(update);
|
|
theme.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ['data-theme']
|
|
});
|
|
});
|
|
return () => {
|
|
disposed = true;
|
|
resize?.disconnect();
|
|
theme?.disconnect();
|
|
chart?.dispose();
|
|
};
|
|
});
|
|
$effect(() => {
|
|
// Re-render when the option builder (and therefore its inputs) changes.
|
|
void option;
|
|
if (ready) update();
|
|
});
|
|
</script>
|
|
|
|
<div class="chart" style:height bind:this={element} role="img" aria-label={label}></div>
|
|
|
|
<style>
|
|
.chart {
|
|
width: 100%;
|
|
min-height: 260px;
|
|
}
|
|
</style>
|