108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import { ResponsiveSankey } from '@nivo/sankey'
|
|
|
|
export type FlowTotals = {
|
|
pv_to_load_kwh: number
|
|
pv_to_batt_kwh: number
|
|
pv_to_grid_kwh: number
|
|
batt_to_load_kwh: number
|
|
batt_to_grid_kwh: number
|
|
grid_to_load_kwh: number
|
|
grid_to_batt_kwh: number
|
|
}
|
|
|
|
/**
|
|
* d3-sankey vyžaduje acyklický graf. Jeden uzel „Síť“ vede na cyklus
|
|
* Síť→Baterie (nabíjení) a Baterie→Síť (export) → „circular link“.
|
|
* Rozdělení na Import (zdroj) a Export (stok) cyklus odstraní.
|
|
*/
|
|
const NODES = [
|
|
{ id: 'FVE' },
|
|
{ id: 'Import ze sítě' },
|
|
{ id: 'Baterie' },
|
|
{ id: 'Spotřeba' },
|
|
{ id: 'Export do sítě' },
|
|
] as const
|
|
|
|
function buildLinks(t: FlowTotals): { source: string; target: string; value: number }[] {
|
|
const out: { source: string; target: string; value: number }[] = []
|
|
const add = (source: string, target: string, v: number) => {
|
|
if (v > 0.0005) out.push({ source, target, value: v })
|
|
}
|
|
add('FVE', 'Spotřeba', t.pv_to_load_kwh)
|
|
add('FVE', 'Baterie', t.pv_to_batt_kwh)
|
|
add('FVE', 'Export do sítě', t.pv_to_grid_kwh)
|
|
add('Baterie', 'Spotřeba', t.batt_to_load_kwh)
|
|
add('Baterie', 'Export do sítě', t.batt_to_grid_kwh)
|
|
add('Import ze sítě', 'Spotřeba', t.grid_to_load_kwh)
|
|
add('Import ze sítě', 'Baterie', t.grid_to_batt_kwh)
|
|
return out
|
|
}
|
|
|
|
type Props = {
|
|
totals: FlowTotals | null
|
|
}
|
|
|
|
export function EnergyFlowSankey({ totals }: Props) {
|
|
if (!totals) {
|
|
return (
|
|
<div className="flex h-[440px] items-center justify-center text-sm text-slate-500">
|
|
Žádná data
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const links = buildLinks(totals)
|
|
if (links.length === 0) {
|
|
return (
|
|
<div className="flex h-[440px] items-center justify-center text-sm text-slate-500">
|
|
V tomto měsíci nejsou žádné modelované toky (chybí audit / telemetrie).
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="h-[440px] w-full min-h-[320px]">
|
|
<ResponsiveSankey
|
|
data={{ nodes: [...NODES], links }}
|
|
margin={{ top: 24, right: 180, bottom: 24, left: 24 }}
|
|
align="justify"
|
|
sort="input"
|
|
colors={{ scheme: 'set2' }}
|
|
nodeOpacity={1}
|
|
nodeHoverOpacity={1}
|
|
nodeThickness={20}
|
|
nodeSpacing={28}
|
|
nodeBorderWidth={0}
|
|
linkOpacity={0.45}
|
|
linkHoverOpacity={0.75}
|
|
linkContract={2}
|
|
enableLinkGradient
|
|
labelPosition="outside"
|
|
labelOrientation="horizontal"
|
|
labelPadding={12}
|
|
labelTextColor={{ from: 'color', modifiers: [['darker', 1.2]] }}
|
|
theme={{
|
|
background: 'transparent',
|
|
labels: {
|
|
text: {
|
|
fill: '#e2e8f0',
|
|
fontSize: 12,
|
|
fontWeight: 500,
|
|
},
|
|
},
|
|
tooltip: {
|
|
container: {
|
|
background: '#1e293b',
|
|
color: '#f8fafc',
|
|
fontSize: 12,
|
|
borderRadius: 8,
|
|
border: '1px solid #334155',
|
|
},
|
|
},
|
|
}}
|
|
valueFormat={(v) => `${Number(v).toFixed(2)} kWh`}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|