2024-10-11 03:45:32 +00:00
|
|
|
import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
|
|
|
|
|
import { Chart, registerables } from 'chart.js';
|
2024-10-16 10:13:13 +00:00
|
|
|
import { SensorService } from '../../../../cores/services/sensor.service';
|
2024-10-25 03:55:52 +00:00
|
|
|
import { ApiResponse, ParameterSensor } from '../../../../cores/interface/sensor-data';
|
2024-09-27 03:34:34 +00:00
|
|
|
import { CommonModule } from '@angular/common';
|
2024-10-11 03:45:32 +00:00
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
|
|
|
|
|
|
|
Chart.register(...registerables);
|
2024-10-25 03:55:52 +00:00
|
|
|
|
2024-10-11 03:45:32 +00:00
|
|
|
const parameterColors: { [key: string]: string } = {
|
2024-10-23 01:29:27 +00:00
|
|
|
vicitemperature: '#8F5A62',
|
|
|
|
|
vicihumidity: '#16423C',
|
|
|
|
|
viciluminosity: '#DF9B55',
|
2024-10-25 03:55:52 +00:00
|
|
|
soiltemperature: '#FF6347',
|
|
|
|
|
soilhumidity: '#00BFFF',
|
|
|
|
|
soilconductivity: '#A52A2A',
|
|
|
|
|
soilph: '#228B22',
|
|
|
|
|
soilnitrogen: '#FEDC56',
|
|
|
|
|
soilphosphorus: '#B80F0A',
|
|
|
|
|
soilpotassium: '#6F2DA8',
|
2024-10-11 03:45:32 +00:00
|
|
|
};
|
|
|
|
|
|
2024-09-17 06:50:34 +00:00
|
|
|
@Component({
|
|
|
|
|
selector: 'app-graph',
|
|
|
|
|
standalone: true,
|
2024-10-11 03:45:32 +00:00
|
|
|
imports: [CommonModule, FormsModule],
|
2024-09-17 06:50:34 +00:00
|
|
|
templateUrl: './graph.component.html',
|
2024-09-27 03:34:34 +00:00
|
|
|
styleUrls: ['./graph.component.scss']
|
2024-09-17 06:50:34 +00:00
|
|
|
})
|
2024-10-11 03:45:32 +00:00
|
|
|
export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
|
2024-10-25 03:55:52 +00:00
|
|
|
@ViewChild('myChartDHT', { static: false }) dhtChartElement!: ElementRef<HTMLCanvasElement>;
|
|
|
|
|
@ViewChild('myChartNPK1', { static: false }) npk1ChartElement!: ElementRef<HTMLCanvasElement>;
|
|
|
|
|
@ViewChild('myChartNPK2', { static: false }) npk2ChartElement!: ElementRef<HTMLCanvasElement>;
|
|
|
|
|
|
|
|
|
|
isLoadingDHT: boolean = true;
|
|
|
|
|
isLoadingNPK1: boolean = true;
|
|
|
|
|
isLoadingNPK2: boolean = true;
|
|
|
|
|
|
|
|
|
|
isNoDataDHT: boolean = false;
|
|
|
|
|
isNoDataNPK1: boolean = false;
|
|
|
|
|
isNoDataNPK2: boolean = false;
|
|
|
|
|
|
|
|
|
|
allNoData: boolean = false;
|
2024-09-17 06:50:34 +00:00
|
|
|
|
2024-09-27 03:34:34 +00:00
|
|
|
sensorParameters: { [key: string]: string[] } = {
|
2024-10-23 01:29:27 +00:00
|
|
|
dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'],
|
|
|
|
|
npk1: ['soiltemperature', 'soilhumidity', 'soilconductivity', 'soilph', 'soilnitrogen', 'soilphosphorus', 'soilpotassium'],
|
|
|
|
|
npk2: ['soiltemperature', 'soilhumidity', 'soilconductivity', 'soilph', 'soilnitrogen', 'soilphosphorus', 'soilpotassium'],
|
2024-09-27 03:34:34 +00:00
|
|
|
};
|
2024-09-17 06:50:34 +00:00
|
|
|
|
2024-10-11 03:45:32 +00:00
|
|
|
parameterDisplayNames: { [key: string]: string } = {
|
2024-10-23 01:29:27 +00:00
|
|
|
vicitemperature: 'Temperature (°C)',
|
|
|
|
|
vicihumidity: 'Humidity (%)',
|
|
|
|
|
viciluminosity: 'Luminosity (lux)',
|
|
|
|
|
soiltemperature: 'Soil Temperature (°C)',
|
|
|
|
|
soilhumidity: 'Soil Humidity (%)',
|
|
|
|
|
soilconductivity: 'Conductivity (μS/cm)',
|
|
|
|
|
soilph: 'pH',
|
2024-10-25 03:55:52 +00:00
|
|
|
soilnitrogen: 'Nitrogen (mg/l)',
|
|
|
|
|
soilphosphorus: 'Phosphorus (mg/l)',
|
2024-10-29 07:46:02 +00:00
|
|
|
soilpotassium: 'Kalium (mg/l)',
|
2024-09-27 03:34:34 +00:00
|
|
|
};
|
2024-09-17 06:50:34 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
charts: { [key: string]: Chart | undefined } = {
|
|
|
|
|
dht: undefined,
|
|
|
|
|
npk1: undefined,
|
|
|
|
|
npk2: undefined,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
selectedNPK1: string = 'npk';
|
|
|
|
|
selectedNPK2: string = 'npk';
|
|
|
|
|
|
2024-10-11 03:45:32 +00:00
|
|
|
private resizeListener!: () => void;
|
2024-09-17 06:50:34 +00:00
|
|
|
|
2024-10-16 10:13:13 +00:00
|
|
|
constructor(private sensorService: SensorService) {}
|
2024-09-17 06:50:34 +00:00
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
2024-10-11 03:45:32 +00:00
|
|
|
this.resizeListener = this.onResize.bind(this);
|
|
|
|
|
window.addEventListener('resize', this.resizeListener);
|
2024-10-25 03:55:52 +00:00
|
|
|
this.updateCharts();
|
2024-09-17 06:50:34 +00:00
|
|
|
}
|
2024-10-11 03:45:32 +00:00
|
|
|
|
2024-10-08 03:07:28 +00:00
|
|
|
ngAfterViewInit(): void {
|
2024-10-25 03:55:52 +00:00
|
|
|
this.onResize();
|
2024-10-08 03:07:28 +00:00
|
|
|
}
|
2024-09-17 06:50:34 +00:00
|
|
|
|
2024-10-11 03:45:32 +00:00
|
|
|
ngOnDestroy(): void {
|
|
|
|
|
window.removeEventListener('resize', this.resizeListener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onResize(): void {
|
2024-10-25 03:55:52 +00:00
|
|
|
Object.values(this.charts).forEach(chart => {
|
|
|
|
|
if (chart) {
|
|
|
|
|
chart.destroy();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.updateCharts();
|
2024-10-11 03:45:32 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
updateCharts(): void {
|
|
|
|
|
this.isLoadingDHT = this.isLoadingNPK1 = this.isLoadingNPK2 = true;
|
|
|
|
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const year = today.getFullYear();
|
|
|
|
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
const day = String(today.getDate()).padStart(2, '0');
|
|
|
|
|
|
|
|
|
|
const startEnd = `${year}-${month}-${day}`;
|
|
|
|
|
// const startEnd = '2024-10-24';
|
|
|
|
|
const timeRange = 'HOURLY';
|
|
|
|
|
|
|
|
|
|
// Fetch data for DHT
|
|
|
|
|
this.sensorService.getSensorData('dht', '', startEnd, timeRange).subscribe({
|
|
|
|
|
next: (response) => {
|
|
|
|
|
if (response.statusCode === 200 && response.data.dht?.length > 0) {
|
|
|
|
|
this.createChart(this.dhtChartElement.nativeElement, response, 'dht', 'npk');
|
|
|
|
|
this.isNoDataDHT = false;
|
|
|
|
|
this.allNoData = false;
|
|
|
|
|
} else {
|
|
|
|
|
this.isNoDataDHT = true;
|
|
|
|
|
}
|
|
|
|
|
this.isLoadingDHT = false;
|
|
|
|
|
},
|
|
|
|
|
error: () => {
|
|
|
|
|
this.isLoadingDHT = false;
|
|
|
|
|
this.isNoDataDHT = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Fetch data for NPK1
|
|
|
|
|
this.sensorService.getSensorData('npk1', '', startEnd, timeRange).subscribe({
|
|
|
|
|
next: (response) => {
|
|
|
|
|
if (response.statusCode === 200 && response.data.npk1?.length > 0) {
|
|
|
|
|
this.createChart(this.npk1ChartElement.nativeElement, response, 'npk1', this.selectedNPK1);
|
|
|
|
|
this.isNoDataNPK1 = false;
|
|
|
|
|
this.allNoData = false;
|
|
|
|
|
} else {
|
|
|
|
|
this.isNoDataNPK1 = true;
|
|
|
|
|
}
|
|
|
|
|
this.isLoadingNPK1 = false;
|
|
|
|
|
},
|
|
|
|
|
error: () => {
|
|
|
|
|
this.isLoadingNPK1 = false;
|
|
|
|
|
this.isNoDataNPK1 = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-10-11 03:45:32 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
// Fetch data for NPK2
|
|
|
|
|
this.sensorService.getSensorData('npk2', '', startEnd, timeRange).subscribe({
|
|
|
|
|
next: (response) => {
|
|
|
|
|
if (response.statusCode === 200 && response.data.npk2?.length > 0) {
|
|
|
|
|
this.createChart(this.npk2ChartElement.nativeElement, response, 'npk2', this.selectedNPK2);
|
|
|
|
|
this.isNoDataNPK2 = false;
|
|
|
|
|
this.allNoData = false;
|
|
|
|
|
} else {
|
|
|
|
|
this.isNoDataNPK2 = true;
|
|
|
|
|
}
|
|
|
|
|
this.isLoadingNPK2 = false;
|
|
|
|
|
},
|
|
|
|
|
error: () => {
|
|
|
|
|
this.isLoadingNPK2 = false;
|
|
|
|
|
this.isNoDataNPK2 = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-10-11 03:45:32 +00:00
|
|
|
}
|
2024-10-25 03:55:52 +00:00
|
|
|
|
|
|
|
|
createChart(canvas: HTMLCanvasElement, response: ApiResponse, sensor: string, selectedOption: string): void {
|
2024-09-17 06:50:34 +00:00
|
|
|
const ctx = canvas.getContext('2d');
|
2024-10-25 03:55:52 +00:00
|
|
|
const parameters = this.sensorParameters[sensor];
|
2024-09-17 06:50:34 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
if (!ctx) {
|
|
|
|
|
console.error('Failed to get canvas context for sensor:', sensor);
|
|
|
|
|
return;
|
2024-09-17 06:50:34 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
let datasets: any[] = [];
|
|
|
|
|
|
|
|
|
|
if (sensor === 'dht') {
|
|
|
|
|
// Handle DHT parameters directly
|
|
|
|
|
datasets = ['vicitemperature', 'vicihumidity', 'viciluminosity'].map(parameter => {
|
|
|
|
|
const { data, labels } = this.getDataFromResponse(response, sensor, parameter);
|
|
|
|
|
|
|
|
|
|
if (data.length === 0) {
|
|
|
|
|
console.warn(`No data found for parameter: ${parameter}`);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const displayName = this.parameterDisplayNames[parameter] || parameter;
|
|
|
|
|
const borderColor = parameterColors[parameter] || '#000000';
|
|
|
|
|
const backgroundColor = `${borderColor}4D`;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
label: displayName,
|
|
|
|
|
data,
|
|
|
|
|
borderColor,
|
|
|
|
|
borderWidth: 1.5,
|
|
|
|
|
fill: true,
|
|
|
|
|
backgroundColor,
|
|
|
|
|
tension: 0.5,
|
|
|
|
|
pointRadius: 0,
|
|
|
|
|
pointHoverRadius: 0,
|
|
|
|
|
};
|
|
|
|
|
}).filter(dataset => dataset !== null);
|
|
|
|
|
} else {
|
|
|
|
|
// Handle NPK1 and NPK2 as before
|
|
|
|
|
datasets = parameters.map(parameter => {
|
|
|
|
|
const { data, labels } = this.getDataFromResponse(response, sensor, parameter);
|
|
|
|
|
|
|
|
|
|
if (data.length === 0) {
|
|
|
|
|
console.warn(`No data found for parameter: ${parameter}`);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const displayName = this.parameterDisplayNames[parameter] || parameter;
|
|
|
|
|
const borderColor = parameterColors[parameter] || '#000000';
|
|
|
|
|
const backgroundColor = `${borderColor}4D`;
|
2024-10-11 03:45:32 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
// Filter datasets based on the selected option
|
|
|
|
|
if (selectedOption === 'npk' && ['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter)) {
|
|
|
|
|
return {
|
2024-10-11 03:45:32 +00:00
|
|
|
label: displayName,
|
2024-09-17 06:50:34 +00:00
|
|
|
data,
|
|
|
|
|
borderColor,
|
|
|
|
|
borderWidth: 1.5,
|
2024-09-27 06:53:18 +00:00
|
|
|
fill: true,
|
2024-10-11 03:45:32 +00:00
|
|
|
backgroundColor,
|
2024-09-27 03:34:34 +00:00
|
|
|
tension: 0.5,
|
2024-09-27 06:53:18 +00:00
|
|
|
pointRadius: 0,
|
|
|
|
|
pointHoverRadius: 0,
|
2024-10-25 03:55:52 +00:00
|
|
|
};
|
|
|
|
|
} else if (selectedOption === 'others' && !['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter)) {
|
|
|
|
|
return {
|
|
|
|
|
label: displayName,
|
|
|
|
|
data,
|
|
|
|
|
borderColor,
|
|
|
|
|
borderWidth: 1.5,
|
|
|
|
|
fill: true,
|
|
|
|
|
backgroundColor,
|
|
|
|
|
tension: 0.5,
|
|
|
|
|
pointRadius: 0,
|
|
|
|
|
pointHoverRadius: 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}).filter(dataset => dataset !== null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (datasets.length === 0) {
|
|
|
|
|
console.warn('No valid datasets to render for sensor:', sensor);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.charts[sensor]) {
|
|
|
|
|
this.charts[sensor]?.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.charts[sensor] = new Chart(ctx, {
|
|
|
|
|
type: 'line',
|
|
|
|
|
data: {
|
|
|
|
|
labels: this.getLabels(response, sensor),
|
|
|
|
|
datasets,
|
|
|
|
|
},
|
|
|
|
|
options: {
|
|
|
|
|
responsive: true,
|
|
|
|
|
maintainAspectRatio: false,
|
|
|
|
|
plugins: {
|
|
|
|
|
tooltip: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
mode: 'nearest',
|
|
|
|
|
intersect: false,
|
|
|
|
|
callbacks: {
|
|
|
|
|
label: (tooltipItem) => {
|
|
|
|
|
const paramLabel = tooltipItem.dataset.label;
|
|
|
|
|
const value = tooltipItem.formattedValue;
|
|
|
|
|
return `${paramLabel}: ${value}`;
|
2024-10-11 03:45:32 +00:00
|
|
|
}
|
2024-10-25 03:55:52 +00:00
|
|
|
}
|
2024-09-27 03:34:34 +00:00
|
|
|
},
|
2024-10-25 03:55:52 +00:00
|
|
|
legend: { display: true }
|
|
|
|
|
},
|
|
|
|
|
scales: {
|
|
|
|
|
x: { grid: { display: false }, beginAtZero: true },
|
|
|
|
|
y: { grid: { display: false }, beginAtZero: true }
|
2024-09-17 06:50:34 +00:00
|
|
|
}
|
2024-10-25 03:55:52 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-10-11 03:45:32 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
getDataFromResponse(response: ApiResponse, sensor: string, parameter: string): { data: number[], labels: string[] } {
|
|
|
|
|
const sensorData = response.data[sensor as keyof typeof response.data];
|
2024-10-11 03:45:32 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
const data: number[] = [];
|
|
|
|
|
const labels: string[] = [];
|
2024-09-27 03:34:34 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
if (sensorData) {
|
|
|
|
|
sensorData.forEach(item => {
|
|
|
|
|
data.push(item[parameter as keyof ParameterSensor] ?? 0);
|
|
|
|
|
labels.push(`${item.hour}.00`);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
console.error('No data found for sensor:', sensor);
|
2024-10-11 03:45:32 +00:00
|
|
|
}
|
2024-09-17 06:50:34 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
return { data, labels };
|
|
|
|
|
}
|
2024-10-11 03:45:32 +00:00
|
|
|
|
2024-10-25 03:55:52 +00:00
|
|
|
getLabels(response: ApiResponse, sensor: string): string[] {
|
|
|
|
|
const sensorData = response.data[sensor as keyof typeof response.data];
|
|
|
|
|
return sensorData ? sensorData.map(item => `${item.hour}.00`) : [];
|
2024-09-17 06:50:34 +00:00
|
|
|
}
|
|
|
|
|
}
|