Water Balance
An interactive flow diagram that accounts for water moving through a site — sources, tanks, treatment, consumption, and recycled loops — rendered with React Flow. It answers "where did the water go, and does it add up?"
- Route:
/water_balance/graph→WaterBalance(libs/waterBalance/src/WaterBalancePage.jsx) - Permission:
WATER_BALANCE(gates the sidebar entry — the route element itself isn't wrapped; see Permissions) - Library:
libs/waterBalance/— standarddataSource → controller → store → componentslayering
The flow at a glance
The diagram wires typed nodes (tanks, pumps, valves, processes, meters) together with typed edges (raw vs recycled flows). A simplified physical flow:
Summary: Solid lines are raw water, dashed lines are recycled water (per the in-app legend). Consumption flows carry animated arrows. The node types and layout come entirely from the backend response — the frontend only maps each type string to a component.
Two views, two granularities
Both are switched client-side from already-fetched data — only a date change triggers a refetch.
| Control | Options | Backend key | Notes |
|---|---|---|---|
View (WaterBalanceGraphType) | Overview (SUMMARY) · Detailed (DETAIL) | summary / diagram | Overview = summary tiles; Detailed = the physical flow diagram |
Granularity (WaterBalanceDateType) | Day (HOUR) · Month (DATE) | — | Drives the date-picker mode; changing it refetches |
Multi-page sites expose a page dropdown (selectedPageId); the default page is
pages.physical. Switching view or page remounts the graph (React Flow
key = pageId_graphType), which resets any open node popups.
Node types — which node is which
The backend gives each node a type string; WaterBalanceGraphComponents.jsx
maps it to a component (nodeTypes):
Detailed view (physical diagram)
type | Component | Represents | Renders & state | On click |
|---|---|---|---|---|
CARD | ConsumptionNode | Consumption / meter reading | Value + label; blinks red when limit reached; greyed offline | Opens the source unit's detail (new tab) |
TANK | WaterTankNode | Storage tank | Animated level + threshold lines; blinks red at threshold; detail only at zoom ≥ 0.75 | Opens the unit's stock page |
OVAL | OvalNode | Source / output label | Value + label (pill) | — |
PROCESS | ProcessNode | Treatment / process step | Value + label (box) | — |
PUMP | PumpNode | Pump | Icon only | — |
VALVE | ValveNode | Valve / flow-meter | Icon only | — |
Overview (summary tiles)
type | Component | Represents | Renders & state | On click |
|---|---|---|---|---|
SUMMARY_DATA | SummaryDataNode | Summary metric tile | Value + unit + label; formula tooltip | — |
SUMMARY_DETAIL | SummaryDetailNode | Grouped metric (many units) | Group value; blinks red if any child hits its limit | Toggles a dropdown of member units (each row opens that unit's page) |
WATER_BALANCE | WaterBalanceNode | Headline balance figure | Value as a percentage | — |
SUMMARY_DIFFERENCE | SummaryDifferenceNode | Difference / reconciliation | Value, no unit (pill) | — |
Every node draws four source + four target Handles
(one per side, hidden via markerStyle), and the graph uses
connectionMode="loose" so edges can attach anywhere.
Edge types — which edge is which
Mapped in WaterBalanceGraphComponents.jsx (edgeTypes):
type | Component | Appearance | Meaning |
|---|---|---|---|
custom | FlowEdge | Free poly-line through backend controlPoints, solid #006183 | Raw-water flow along a custom path |
step | CustomStepEdge | Orthogonal smooth-step, solid | Raw-water flow (grid-routed) |
dashStep | CustomDashEdge | Smooth-step, dashed (12 12) | Recycled-water flow |
- Edges flagged
data.isConsumptionoverlayAnimatedArrow— arrow glyphs that travel along the path (speed scales with distance) to visualize live flow. - In the downloaded image,
custom/stepare swapped to a non-animated step edge (CustomStepEdgeWithoutAnimation) so the still frame is clean;dashStepis kept.
Data & APIs
Water-balance data (the only data endpoint)
dataSource/waterBalance.js → controller/waterBalanceContoller.js → store init():
| Endpoint | GET waterBalance/data (Urls.waterBalanceData) |
| Params | date1 (DD/MM/YYYY), type (HOUR | DATE) — passed as query params |
| Triggered by | initial load · date change · date-type change · a 5-minute auto-refresh, only while viewing today |
Overview↔Detailed and the page dropdown do not refetch — they re-render from the data already in the store.
Response shape (response.data.pages) — a map keyed by page id, always
including a physical page:
{
"data": {
"pages": {
"physical": {
"id": "physical",
"siUnit": "kl",
"summary": { "nodes": [ /* typed nodes */ ], "edges": [ /* typed edges */ ] },
"diagram": { "nodes": [ ... ], "edges": [ ... ] }
}
// …other pages
}
}
}
The graph reads pages[selectedPageId][summary|diagram].nodes / .edges, and the
single siUnit (default kl) is appended to values across the tiles.
Analytics events
| Event | When |
|---|---|
PAGE_VIEW | On page mount |
WATER_BALANCE_DATE_CHANGE | When the date or granularity changes |
(Both no-op when analytics is disabled — the backwards analyticsEnv flag; see
Configuration & Security.)
State (WaterBalanceStore)
A React Context (WaterBalanceDataProvider / WaterBalanceDataContext) exposing
balanceData ({ data: pages, siUnit }), graphType, params, isLoading,
selectedPageId, openInfoPopup, openDownloadPopup, downloadContainerRef, and
fullScreenLoader. See State Management for
the pattern and API Call Flow for how the request is made.
Edge cases & guards
The feature handles several non-obvious states:
- No diagram configured → when data has loaded but the current page+view has
zero nodes,
ContactForDiagramoverlays a static message asking the user to request a diagram via the issue-reporting form. It makes no API/mail call. - Loading → a centered loader renders while
isLoading; the view toggle is disabled and the download popup only mounts after load. - Fetch failure is swallowed → the data source
catchlogs and returnsundefined; the store guards on it, so a failed request simply leaves the graph empty (no error UI) and falls through to the empty/contact state. - Auto-refresh is scoped to "today" → the 5-minute refresh interval is only created when the selected date is the current day.
constantDateoverride → if the app supplies a fixed date, it seeds and keepsparams.date1in sync (used by contexts that pin a date).- Limit / threshold alerts → consumption cards, tanks, and grouped summary tiles turn red and blink when a limit or storage threshold is reached.
- Zoom-gated tank detail → tank threshold lines/values only appear at zoom ≥ 0.75.
- View/page remount → switching view or page forces a fresh React Flow mount, resetting internal node state (e.g. an open summary-detail dropdown).
Export
The download control opens a dialog containing a static, non-interactive copy
of the diagram (pan/zoom/drag/select disabled, fitView, still edges). The image
is produced with html-to-image (toPng) over the popup's container ref
(high pixelRatio, <style> nodes filtered out), then downloaded as a PNG. The
filename encodes the date, the page id, and the logged-in username. A full-screen
loader covers the capture. (The export is an image of the diagram — there is no
data-table export currently wired in.)
Info popup / legend
The info control opens WaterBalanceInfoPopup, whose legend depends on the active
view (infoMenuKey):
- Overview (
SUMMARY_INFO) — combined view, difference, zoom hint (Ctrl + scroll). - Detailed (
DETAIL_INFO) — raw water, recycled water, limit reached, 90% filled, storage tank, no-flow-meter, pump, valve.
React Flow version
The diagram is built on reactflow v11 (the legacy package name; imports come
from 'reactflow', e.g. ReactFlow, Handle, Position, BaseEdge,
getSmoothStepPath, useStore).
Going forward: React Flow v12 ships as the renamed
@xyflow/reactpackage. A future upgrade would swap the import path (reactflow→@xyflow/react) and adopt its minor API changes. Until then, keep new water-balance work on the v11reactflowimports so the graph stays consistent.
Underlying libraries
| Purpose | Library |
|---|---|
| Flow diagram | reactflow (v11) |
| Image export | html-to-image |
| UI / theming | @mui/material (MUI 7), @emotion/react (blink keyframes) |
| Icons | @mui/icons-material, @iconify/react |
| Dates | moment |
| Utilities | lodash |
Exact versions live in package.json.
Code reference
| File | Role |
|---|---|
WaterBalancePage.jsx | Page shell, provider, empty-state wiring |
dataSource/waterBalance.js | The waterBalance/data call |
controller/waterBalanceContoller.js | Fetch orchestration |
store/WaterBalanceStore.js | Context state + refetch/auto-refresh logic |
enums/waterBalanceEnums.js | View + granularity enums |
components/BuildWaterBalanceGraph.jsx | React Flow host, view toggle, page dropdown, controls |
components/WaterBalanceGraphComponents.jsx | nodeTypes / edgeTypes maps |
components/nodes/*, summaryNodes/* | The node components |
components/edges/*, animated/AnimatedArrow.jsx | The edge components |
components/WaterBalanceDownloadPopup.jsx | Export dialog |
components/WaterBalanceInfoPopup.jsx | Legend |
components/ContactForDiagram.jsx | No-diagram fallback |
Related
- Application Routes — the
/water_balance/graphroute - Permissions — the
WATER_BALANCEgate - Water Usage Ratio · Water Monitoring — sibling water features
- API Call Flow · State Management