Skip to main content

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 units
    • borewellGraphData - Historical graph data (3M/6M/1Y)
    • levelGraphData - Today's hourly graph data
    • params - 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 pixels
    • maxWidth - Tank width in pixels
    • text - Current level reading
    • groundText - "Ground Level" label
    • waterText - "Water Level" label
    • percentage - 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);
}, []);


Last Updated: February 2026 Module Location: libs/monitoring/src/pages/groundWaterLevel/