Ground Water Level Monitoring
Monitor ground water levels and borewell depths with animated tank visualization, multi-period trend graphs (Today, 3 Months, 6 Months, 1 Year), and historical analysis.
Overview
The Ground Water Level Monitoring feature tracks ground water table depths and borewell water levels with visual tank representations, switchable time-period graphs, and comparative analysis across multiple time ranges.
Location: libs/monitoring/src/pages/groundWaterLevel/
Route: /monitoring/groundwater/:categoryId
Permission Required: WATER_MONITORING
Key Features
1. Ground Water Tank Visualization
Specialized ground water tank display showing:
Tank Components:
- Ground Level - Surface reference line (0 ft)
- Water Level - Current depth below ground (e.g., 32 ft)
- Inverted Fill - Fills from bottom as water level rises
- Numeric Reading - Current depth with SI unit
- Animated Wave - Realistic water wave at measured depth
Visual Logic:
- Deeper levels = Lower water in tank visualization
- Shallower levels = Higher water in tank visualization
- Percentage calculation:
100 - ((currentLevel - 32) / 7)
2. Multi-Period Graph Analysis
Switchable graph views with button toggle:
Available Periods:
- Today - Hourly line graph for current day (24 hours)
- 3 Months - Monthly bar graph for last 3 months
- 6 Months - Monthly bar graph for last 6 months
- 1 Year - Monthly bar graph for last 12 months
Graph Types:
- Line Graph - For "Today" view (hourly granular data)
- Bar Graph - For 3M/6M/1Y views (monthly aggregated data)
3. Real-Time Updates
Auto-Refresh:
- Page auto-refreshes at configured intervals
- Loads today's data for all units on mount
- Fetches granular data based on selected period
4. Online/Offline Status
Each borewell displays:
- Online: Green "Online" badge
- Offline: "Last Update" timestamp with reduced opacity
Architecture
Data Flow
Key Components
1. GroundWaterLevelScreenDataProvider
- Purpose: State management for ground water monitoring
- Location:
libs/monitoring/src/dataProvider/GroundWaterLevelDataProvider.jsx - State Variables:
groundWaterData- Current water levels for all unitsborewellGraphData- Historical graph data (3M/6M/1Y)levelGraphData- Today's hourly graph dataparams- Query parameters (date, category, type)
Context Value:
{
groundWaterData: {
data: {
subCategories: [
{
id: 'SUB_CAT_1',
displayName: 'Borewells',
units: [
{
unitId: 'UNIT_1',
displayName: 'Borewell 1',
value1: 35.2, // Current depth (ft)
online: true,
lastUpdatedAt: '25/02/2026 10:30 AM'
}
]
}
]
}
},
borewellGraphData: [
{ x: 'Nov', y: 33.5 },
{ x: 'Dec', y: 34.2 },
{ x: 'Jan', y: 35.0 }
],
levelGraphData: [
{ x: '00:00', y: 35.0 },
{ x: '01:00', y: 35.1 },
// ... hourly data for today
],
setParams: (params) => {},
getGroundWaterGraphData: (startDate, unitId, pastNumberOfMonths) => {},
getGranularUnitData: (unitId, date1) => {}
}
2. GroundWaterLevelPage
- Purpose: Main ground water monitoring page
- Location:
libs/monitoring/src/pages/groundWaterLevel/GroundWaterLevelPage.jsx - Features:
- Fixed header with title
- Displays all subcategories
- Renders unit tanks with graphs
- Manages graph period selection
3. BuildUnitsView
- Purpose: Render individual borewell units
- Features:
- Two-column layout (tank | graph)
- Unit name and online status
- Ground water tank visualization
- Switchable period graph
4. GraphMenu
- Purpose: Period selection buttons and graph display
- Features:
- Button toggle for Today/3M/6M/1Y
- Conditional graph rendering (line vs bar)
- Auto-fetch data on period change
5. GroundWaterTank Component
- Purpose: Specialized tank for ground water visualization
- Location:
libs/components/src/waterTank/GroundWaterTank.jsx - Props:
maxCapacity- Maximum depth (calculated)maxHeight- Tank height in pixelsmaxWidth- Tank width in pixelstext- Current level readinggroundText- "Ground Level" labelwaterText- "Water Level" labelpercentage- Fill percentage (inverted)siUnit- Unit label (ft, m)waveColor- Water color (#64B5F6 blue)
6. Graph Components
- GroundWaterGraph - Bar chart for 3M/6M/1Y data
- GroundWaterLineGraph - Line chart for Today data
- Location:
libs/components/src/barGraph/
API Integration
Get Category Data (Current Levels)
GET /categoryData
Params: {
category: 'GROUND_WATER_LEVEL',
type: 'HOUR',
date1: '25/02/2026'
}
Response: {
siUnit: 'ft',
subCategories: [
{
id: 'SUB_CAT_1',
displayName: 'Borewells',
units: [
{
unitId: 'UNIT_1',
displayName: 'Borewell 1',
value1: 35.2,
online: true,
lastUpdatedAt: '25/02/2026 10:30 AM'
},
{
unitId: 'UNIT_2',
displayName: 'Borewell 2',
value1: 42.8,
online: false,
lastUpdatedAt: '24/02/2026 08:15 PM'
}
]
}
]
}
Get Ground Water Graph Data (3M/6M/1Y)
GET /getGroundWaterGraphData
Params: {
startDate: '25/02/2026',
unitId: 'UNIT_1',
pastNumberOfMonths: 3 // or 6, 12
}
Response: {
graphData: [
{ x: 'Nov', y: 33.5 },
{ x: 'Dec', y: 34.2 },
{ x: 'Jan', y: 35.0 }
]
}
Get Granular Unit Data (Today)
GET /granular/unit
Params: {
unitId: 'UNIT_1',
date1: '25/02/2026',
summation: false
}
Response: {
lineGraph1: [
{ x: '00:00', y: 35.0 },
{ x: '01:00', y: 35.1 },
{ x: '02:00', y: 35.0 },
// ... hourly readings
]
}
Graph Type Enums
Location: libs/shared/src/enums/
export const GroundWaterGraphType = {
today: 'today',
threeMonths: '3months',
sixMonths: '6months',
oneYear: '1year'
};
export const GraphTypeDisplayName = {
[GroundWaterGraphType.today]: 'Today',
[GroundWaterGraphType.threeMonths]: '3 Months',
[GroundWaterGraphType.sixMonths]: '6 Months',
[GroundWaterGraphType.oneYear]: '1 Year'
};
export const GraphTypeIntValue = {
[GroundWaterGraphType.today]: 0,
[GroundWaterGraphType.threeMonths]: 3,
[GroundWaterGraphType.sixMonths]: 6,
[GroundWaterGraphType.oneYear]: 12
};
Usage Examples
1. Initialize Ground Water Monitoring
import { GroundWaterLevelScreenDataProvider } from '@aquagen-mf-webapp/monitoring';
function GroundWaterApp() {
const { categoryId } = useParams();
return (
<GroundWaterLevelScreenDataProvider categoryId={categoryId}>
<GroundWaterContent />
</GroundWaterLevelScreenDataProvider>
);
}
2. Access Ground Water Data
import { useContext } from 'react';
import { GroundWaterLevelDataContext } from '@aquagen-mf-webapp/monitoring';
function GroundWaterWidget() {
const groundWaterStore = useContext(GroundWaterLevelDataContext);
const { groundWaterData } = groundWaterStore;
return (
<div>
{groundWaterData?.data?.subCategories?.map(category => (
<div key={category.id}>
<h3>{category.displayName}</h3>
{category.units.map(unit => (
<div key={unit.unitId}>
<h4>{unit.displayName}</h4>
<p>Depth: {unit.value1?.toFixed(2)} ft</p>
<p>Status: {unit.online ? 'Online' : 'Offline'}</p>
</div>
))}
</div>
))}
</div>
);
}
3. Switch Graph Period
import { useState, useEffect } from 'react';
import { GroundWaterGraphType, GraphTypeIntValue } from '@aquagen-mf-webapp/shared/enums';
function GraphMenu({ unitId }) {
const groundWaterStore = useContext(GroundWaterLevelDataContext);
const [selectedMonth, setSelectedMonth] = useState(GroundWaterGraphType.today);
useEffect(() => {
const startDate = moment().format('DD/MM/YYYY');
if (selectedMonth === GroundWaterGraphType.today) {
// Fetch hourly data for today
groundWaterStore.getGranularUnitData(unitId, startDate);
} else {
// Fetch monthly data for 3M/6M/1Y
groundWaterStore.getGroundWaterGraphData(
startDate,
unitId,
GraphTypeIntValue[selectedMonth]
);
}
}, [selectedMonth]);
return (
<div>
{/* Period selection buttons */}
{Object.entries(GroundWaterGraphType).map(([key, value]) => (
<button
key={key}
onClick={() => setSelectedMonth(value)}
style={{
borderColor: selectedMonth === value ? '#00374A' : '#CBCBCB',
color: selectedMonth === value ? '#00374A' : 'black'
}}
>
{GraphTypeDisplayName[value]}
</button>
))}
{/* Conditional graph rendering */}
{selectedMonth === GroundWaterGraphType.today ? (
<GroundWaterLineGraph data={groundWaterStore.levelGraphData || []} />
) : (
<GroundWaterGraph
selectedMonth={selectedMonth}
data={groundWaterStore.borewellGraphData || []}
/>
)}
</div>
);
}
4. Calculate Tank Capacity
// Calculate dynamic capacity based on current level
function calculateCapacity(value) {
if (value < 200) {
return 200; // Minimum capacity
}
// Round up to nearest 500
return Math.ceil(value / 500) * 500 === value
? Math.ceil((value + 1) / 500) * 500
: Math.ceil(value / 500) * 500;
}
// Usage
const maxCapacity = calculateCapacity((unit.value1 ?? 0) + 100);
5. Calculate Fill Percentage (Inverted)
// Ground water tanks fill from bottom, deeper = less fill
function calculateGroundWaterPercentage(currentLevel) {
// Assuming ground level is 32 ft and range is 7 ft
const groundLevel = 32;
const range = 7;
// Inverted: 100% at 32 ft (shallowest), 0% at 39 ft (deepest)
const percentage = 100 - ((currentLevel - groundLevel) / range);
return Math.max(0, Math.min(100, percentage));
}
// Usage
const fillPercentage = calculateGroundWaterPercentage(unit.value1 ?? 200);
Ground Water Tank Visualization
<GroundWaterTank
maxCapacity={calculateCapacity((unit.value1 ?? 0) + 100)}
maxHeight={190}
maxWidth={200}
text={`${unit.value1?.toFixed(2) ?? '--'} ${siUnit}`}
groundText="Ground Level"
waterText="Water Level"
percentage={100 - ((unit.value1 ?? 200) - 32) / 7}
siUnit={siUnit}
style={{ borderColor: 'black' }}
waveColor="#64B5F6" // Blue water
/>
Visual Structure:
Graph Rendering Logic
Conditional Graph Display
{/* Show Line Graph for Today */}
<If condition={selectedMonth === GroundWaterGraphType.today}>
<GroundWaterLineGraph
selectedMonth={selectedMonth}
data={groundWaterStore?.levelGraphData || []}
/>
</If>
{/* Show Bar Graph for 3M/6M/1Y */}
<If condition={selectedMonth !== GroundWaterGraphType.today}>
<GroundWaterGraph
selectedMonth={selectedMonth}
data={groundWaterStore?.borewellGraphData || []}
/>
</If>
Period Button Styling
const buttonStyle = {
margin: '2px',
backgroundColor: 'white',
border: '1px solid #CBCBCB',
borderRadius: '2px',
fontSize: '12px',
minWidth: 'auto',
color: 'black',
marginRight: '8px',
textTransform: 'capitalize',
'&.Mui-selected': {
borderColor: '#00374A',
color: '#00374A'
}
};
<Button
sx={{
...buttonStyle,
borderColor: selectedMonth === v ? '#00374A' : '#CBCBCB',
color: selectedMonth === v ? '#00374A' : 'black'
}}
onClick={() => setSelectedMonth(v)}
>
{GraphTypeDisplayName[v]}
</Button>
Auto-Fetch on Mount
// Fetch today's data for all units when component mounts
useEffect(() => {
units.forEach((unit) => {
if (unit?.unitId) {
groundWaterStore.getGranularUnitData(
unit.unitId,
moment(new Date()).format('DD/MM/YYYY')
);
}
});
}, [units]);
Auto-Refresh
useEffect(() => {
init(); // Initial load
const interval = setInterval(() => {
init(); // Background refresh
}, constants.refreshDuration);
return () => clearInterval(interval);
}, []);
Responsive Layout
Desktop (md+):
- Two-column grid layout
- Left: Tank visualization (33% width)
- Right: Graph area (67% width)
- Vertical divider between columns
Mobile (xs-sm):
- Stacked vertical layout
- Horizontal divider between tank and graph
- Full-width components
Grid Configuration:
<Grid container columnSpacing={1}>
<Grid item size={{ xs: 12, md: 4 }}>
{/* Tank visualization */}
</Grid>
<Divider
orientation="vertical"
flexItem
sx={{ display: { xs: 'none', md: 'block' } }}
/>
<Grid item size={{ xs: 12, md: 7 }}>
{/* Graph area */}
</Grid>
</Grid>
Best Practices
1. Handle Missing Data
// Safe access to nested properties
const currentLevel = unit.value1?.toFixed(2) ?? '--';
const siUnit = categoryData?.siUnit || '';
2. Default to Today View
const [selectedMonth, setSelectedMonth] = useState(GroundWaterGraphType.today);
3. Show Offline Status Clearly
<If condition={unit.online}>
<OnlineView />
</If>
<IfNot condition={unit.online}>
<Typography fontSize={12}>
Last Update: {unit.lastUpdatedAt}
</Typography>
</IfNot>
{/* Reduce opacity for offline units */}
<Box sx={{ opacity: unit.online ? 1 : 0.5 }}>
{/* Tank and graph */}
</Box>
4. Dynamic Capacity Calculation
Always calculate capacity dynamically to accommodate varying water levels:
const maxCapacity = calculateCapacity((unit.value1 ?? 0) + 100);
Troubleshooting
Graph Not Displaying
Cause: Missing graph data or incorrect period selected Solution: Check that API returned data and period button is selected
const graphData = selectedMonth === GroundWaterGraphType.today
? groundWaterStore?.levelGraphData
: groundWaterStore?.borewellGraphData;
if (!graphData || graphData.length === 0) {
return <NoDataMessage />;
}
Tank Percentage Incorrect
Cause: Inverted calculation logic not applied Solution: Use inverted formula for ground water
// Correct (inverted)
percentage = 100 - ((currentLevel - groundLevel) / range);
// Incorrect (normal tank)
percentage = (currentLevel / maxCapacity) * 100;
Period Button Not Updating
Cause: State not triggering API call
Solution: Add selectedMonth to useEffect dependencies
useEffect(() => {
// Fetch data
}, [selectedMonth]); // Add dependency
Analytics Integration
import { AnalyticsService } from '@aquagen-mf-webapp/shared/services';
import { AnalyticEvents } from '@aquagen-mf-webapp/shared/enums';
useEffect(() => {
AnalyticsService.sendEvent(AnalyticEvents.PAGE_VIEW, {}, true);
}, []);
Related Documentation
- Water Flow Monitoring - Flow consumption tracking
- Water Quality Monitoring - Quality parameter monitoring
- Water Stock Levels - Storage tank monitoring
- API & Services - API integration
- Components - GroundWaterTank component reference
- Enums & Configuration - Graph type enums
Last Updated: February 2026
Module Location: libs/monitoring/src/pages/groundWaterLevel/