feat(dashboard): integration API in data graph and data latest

This commit is contained in:
Desy Ayurianti 2024-10-11 10:45:32 +07:00
parent 59e89aee72
commit 581ca5f35c
6 changed files with 265 additions and 204 deletions

View File

@ -1,8 +1,13 @@
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes'; import { routes } from './app.routes';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient()
]
}; };

View File

@ -1,8 +1,50 @@
import { Timestamp } from "rxjs"; export interface ParameterSensor {
hour: number;
//for DHT sensor
vicitemperature_avg?: number;
vicihumidity_avg?: number;
viciluminosity_avg?: number;
// for NPK1 & NPK2 sensor
soiltemperature_avg?: number;
soilhumidity_avg?: number;
soilconductivity_avg?: number;
soilph_avg?: number;
soilnitrogen_avg?: number;
soilphosphorus_avg?: number;
soilpotassium_avg?: number;
}
export interface ApiResponse {
data: {
dht?: ParameterSensor[];
npk1?: ParameterSensor[];
npk2?: ParameterSensor[];
};
statusCode: number;
message: string;
}
export interface DHTSensor {
lightIntensity: number;
temperature: number;
humidity: number;
}
export interface NPKSensor {
temperature: number;
moisture: number;
conductivity: number;
ph: number;
nitrogen: number;
phosphorus: number;
potassium: number;
}
export interface SensorData { export interface SensorData {
id: Timestamp<number>; dht: DHTSensor;
humidity: number; npk1: NPKSensor;
temperature: number; npk2: NPKSensor;
soil_moisture: number;
} }

View File

@ -1,16 +1,29 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ApiResponse } from '../interface/sensor-data';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ApiService { export class ApiService {
private apiURL = 'https://jx027dj4-5000.asse.devtunnels.ms/api/sensor-data'; private baseUrl = 'https://jx027dj4-3333.asse.devtunnels.ms/api/sensor/';
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
get(){ getSensorData(sensor: string, metric: string, startDate: string, timeRange: string): Observable<ApiResponse> {
return this.http.get(this.apiURL); const url = `${this.baseUrl}getData`;
const params = new HttpParams()
.set('range[start]', startDate)
.set('range[time_range]', timeRange)
.set('sensor', sensor)
.set('metric', metric);
return this.http.get<ApiResponse>(url, { params });
} }
getLatestData(): Observable<ApiResponse> {
const url = `${this.baseUrl}getLatest`;
return this.http.get<ApiResponse>(url);
}
} }

View File

@ -4,43 +4,27 @@ import { SidebarComponent } from './layouts/sidebar/sidebar.component';
import { GaugeChartComponent } from './page/gauge-chart/gauge-chart.component'; import { GaugeChartComponent } from './page/gauge-chart/gauge-chart.component';
import { GraphComponent } from './page/graph/graph.component'; import { GraphComponent } from './page/graph/graph.component';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ApiService } from '../../cores/services/api.service';
import { SensorData } from '../../cores/interface/sensor-data';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
standalone: true, standalone: true,
imports: [RouterOutlet, SidebarComponent, GaugeChartComponent, GraphComponent, CommonModule], imports: [RouterOutlet, SidebarComponent, GaugeChartComponent, CommonModule, GraphComponent],
templateUrl: './dashboard.component.html', templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'] styleUrls: ['./dashboard.component.scss']
}) })
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {
isLoaded: boolean = false; isLoaded: boolean = false;
selectedButton: string = ''; selectedButton: string = '';
sensorData: any = { sensorData: SensorData = {
dht: { dht: { lightIntensity: 0, temperature: 0, humidity: 0 },
lightIntensity: '3539', npk1: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 },
temperature: '27', npk2: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 }
humidity: '80'
},
npk1: {
temperature: '28',
moisture: '99.9',
conductivity: '1.5',
ph: '8.51',
nitrogen: '500',
phosphorus: '500',
potassium: '500'
},
npk2: {
temperature: '28.6',
moisture: '99.8',
conductivity: '1.4',
ph: '8.55',
nitrogen: '510',
phosphorus: '512',
potassium: '500'
}
}; };
constructor(private apiService: ApiService) {}
ngOnInit(): void { ngOnInit(): void {
this.selectedButton = 'dht'; this.selectedButton = 'dht';
this.loadData(); this.loadData();
@ -53,8 +37,48 @@ export class DashboardComponent implements OnInit {
loadData(): void { loadData(): void {
this.isLoaded = true; this.isLoaded = true;
setTimeout(() => {
this.apiService.getLatestData().subscribe(
(response) => {
const data = response.data;
if (data.dht && data.dht.length > 0) {
this.sensorData.dht = {
lightIntensity: data.dht[0].viciluminosity_avg ?? 0,
temperature: data.dht[0].vicitemperature_avg ?? 0,
humidity: data.dht[0].vicihumidity_avg ?? 0
};
}
if (data.npk1 && data.npk1.length > 0) {
this.sensorData.npk1 = {
temperature: data.npk1[0].soiltemperature_avg ?? 0,
moisture: data.npk1[0].soilhumidity_avg ?? 0,
conductivity: data.npk1[0].soilconductivity_avg ?? 0,
ph: data.npk1[0].soilph_avg ?? 0,
nitrogen: data.npk1[0].soilnitrogen_avg ?? 0,
phosphorus: data.npk1[0].soilphosphorus_avg ?? 0,
potassium: data.npk1[0].soilpotassium_avg ?? 0
};
}
if (data.npk2 && data.npk2.length > 0) {
this.sensorData.npk2 = {
temperature: data.npk2[0].soiltemperature_avg ?? 0,
moisture: data.npk2[0].soilhumidity_avg ?? 0,
conductivity: data.npk2[0].soilconductivity_avg ?? 0,
ph: data.npk2[0].soilph_avg ?? 0,
nitrogen: data.npk2[0].soilnitrogen_avg ?? 0,
phosphorus: data.npk2[0].soilphosphorus_avg ?? 0,
potassium: data.npk2[0].soilpotassium_avg ?? 0
};
}
this.isLoaded = false; this.isLoaded = false;
}, 1000); },
(error) => {
console.error('Error fetching sensor data:', error);
this.isLoaded = false;
}
);
} }
} }

View File

@ -4,8 +4,9 @@
<option value="npk1">NPK 1</option> <option value="npk1">NPK 1</option>
<option value="npk2">NPK 2</option> <option value="npk2">NPK 2</option>
</select> </select>
<select class="form-select" id="parameterSelect" (change)="updateChart()">
<option *ngFor="let parameter of parameters" [value]="parameter">{{ parameter }}</option> <select class="form-select" id="parameterSelect" (change)="updateChart()" [(ngModel)]="selectedParameter">
<option *ngFor="let parameter of parameters" [value]="getParameterKey(parameter)">{{ parameter }}</option>
</select> </select>
</div> </div>
@ -14,5 +15,3 @@
</div> </div>
<canvas #myChart id="myChart"></canvas> <canvas #myChart id="myChart"></canvas>

View File

@ -1,81 +1,107 @@
import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { Chart, registerables } from 'chart.js';
import { ApiService } from '../../../../cores/services/api.service';
import { ApiResponse, ParameterSensor} from '../../../../cores/interface/sensor-data';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, OnInit, ElementRef, ViewChild } from '@angular/core'; import { FormsModule } from '@angular/forms';
import { Chart, LineController, LineElement, PointElement, LinearScale, Title, CategoryScale, Tooltip, Filler } from 'chart.js';
Chart.register(...registerables);
const parameterColors: { [key: string]: string } = {
vicitemperature_avg: '#8F5A62',
vicihumidity_avg: '#16423C',
viciluminosity_avg: '#DF9B55',
soiltemperature_avg: '#8F5A62',
soilhumidity_avg: '#54909c',
soilconductivity_avg: '#661311',
soilph_avg: '#664735',
soilnitrogen_avg: '#3a6635',
soilphosphorus_avg: '#3f3566',
soilpotassium_avg: '#5f3566',
};
@Component({ @Component({
selector: 'app-graph', selector: 'app-graph',
standalone: true, standalone: true,
imports: [CommonModule], imports: [CommonModule, FormsModule],
templateUrl: './graph.component.html', templateUrl: './graph.component.html',
styleUrls: ['./graph.component.scss'] styleUrls: ['./graph.component.scss']
}) })
export class GraphComponent implements OnInit { export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('myChart', { static: false }) chartElement!: ElementRef<HTMLCanvasElement>; @ViewChild('myChart', { static: false }) chartElement!: ElementRef<HTMLCanvasElement>;
selectedSensor: string | null = null; selectedSensor: string = '';
selectedParameter: string = '';
parameters: string[] = []; parameters: string[] = [];
isLoading: boolean = true; isLoading: boolean = true;
sensorParameters: { [key: string]: string[] } = { sensorParameters: { [key: string]: string[] } = {
dht: ['Temperature', 'Humidity', 'Light'], dht: ['vicitemperature_avg', 'vicihumidity_avg', 'viciluminosity_avg'],
npk1: ['Temperature', 'Moisture', 'Conductivity', 'ph', 'Nitrogen', 'Phosphorus', 'Potassium'], npk1: ['soiltemperature_avg', 'soilhumidity_avg', 'soilconductivity_avg', 'soilph_avg', 'soilnitrogen_avg', 'soilphosphorus_avg', 'soilpotassium_avg'],
npk2: ['Temperature', 'Moisture', 'Conductivity', 'ph', 'Nitrogen', 'Phosphorus', 'Potassium'] npk2: ['soiltemperature_avg', 'soilhumidity_avg', 'soilconductivity_avg', 'soilph_avg', 'soilnitrogen_avg', 'soilphosphorus_avg', 'soilpotassium_avg'],
}; };
dhtData = { parameterDisplayNames: { [key: string]: string } = {
humidity: [65, 59, 80, 81, 56, 55, 40, 41, 60, 65, 59, 80, 81, 56, 55, 40, 41, 60, 65, 59, 80, 81, 56, 55], vicitemperature_avg: 'Temperature (°C)',
temperature: [22, 23, 25, 24, 26, 27, 28, 29, 25, 23, 22, 24, 26, 27, 28, 29, 29, 28, 28, 25, 25, 25, 26, 27], vicihumidity_avg: 'Humidity (%)',
light: [200, 220, 230, 210, 250, 240, 260, 265, 270, 200, 220, 230, 210, 250, 240, 260, 265, 270, 275, 280, 285, 290, 295, 300] viciluminosity_avg: 'Luminosity (lx)',
soiltemperature_avg: 'Soil Temperature (°C)',
soilhumidity_avg: 'Soil Humidity (%)',
soilconductivity_avg: 'Conductivity (mS/cm)',
soilph_avg: 'pH',
soilnitrogen_avg: 'Nitrogen (PPM)',
soilphosphorus_avg: 'Phosphorus (PPM)',
soilpotassium_avg: 'Potassium (PPM)',
}; };
npk1Data = { chart: Chart | undefined;
temperature: [23, 24, 26, 27, 28, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 23, 24, 26, 27, 28, 29, 28, 27], labelsHourly: string[] = [];
moisture: [30, 35, 32, 36, 34, 37, 33, 31, 32, 30, 35, 32, 36, 34, 37, 33, 31, 32, 30, 35, 32, 36, 34, 37], private resizeListener!: () => void;
conductivity: [500, 550, 600, 520, 510, 530, 540, 580, 590, 500, 550, 600, 520, 510, 530, 540, 580, 590, 500, 550, 600, 520, 510, 530],
ph: [6.5, 6.7, 6.8, 7.0, 7.1, 6.9, 6.6, 6.8, 6.9, 6.5, 6.7, 6.8, 7.0, 7.1, 6.9, 6.6, 6.8, 6.9, 6.5, 6.7, 6.8, 7.0, 7.1, 6.9],
nitrogen: [300, 310, 320, 330, 340, 350, 355, 360, 365, 300, 310, 320, 330, 340, 350, 355, 360, 365, 300, 310, 320, 330, 340, 350],
phosphorus: [200, 210, 215, 220, 225, 220, 215, 210, 200, 210, 215, 220, 225, 220, 215, 210, 200, 210, 215, 220, 225, 220, 215, 210],
potassium: [180, 190, 200, 205, 210, 215, 220, 225, 230, 180, 190, 200, 205, 210, 215, 220, 225, 230, 180, 190, 200, 205, 210, 215],
};
npk2Data = { constructor(private sensorService: ApiService) {}
temperature: [24, 25, 27, 28, 29, 28, 26, 25, 24, 23, 22, 21, 20, 19, 23, 24, 26, 27, 28, 29, 28, 26, 25, 24],
moisture: [31, 36, 33, 38, 35, 37, 34, 32, 33, 31, 36, 33, 38, 35, 37, 34, 32, 33, 31, 36, 33, 38, 35, 37],
conductivity: [520, 530, 540, 510, 550, 570, 580, 590, 600, 520, 530, 540, 510, 550, 570, 580, 590, 600, 520, 530, 540, 510, 550, 570],
ph: [6.6, 6.9, 7.1, 6.7, 6.8, 6.9, 6.6, 6.8, 6.9, 6.6, 6.9, 7.1, 6.7, 6.8, 6.9, 6.6, 6.8, 6.9, 6.6, 6.9, 7.1, 6.7, 6.8, 6.9],
nitrogen: [320, 330, 340, 350, 360, 400, 410, 420, 430, 320, 330, 340, 350, 360, 400, 410, 420, 430, 320, 330, 340, 350, 360, 400],
phosphorus: [210, 220, 225, 230, 235, 240, 245, 250, 255, 210, 220, 225, 230, 235, 240, 245, 250, 255, 210, 220, 225, 230, 235, 240],
potassium: [190, 195, 205, 210, 215, 220, 225, 230, 235, 190, 195, 205, 210, 215, 220, 225, 230, 235, 190, 195, 205, 210, 215, 220]
};
chart: any;
labelHourly = [
'01.00', '02.00', '03.00', '04.00', '05.00', '06.00', '07.00', '08.00',
'09.00', '10.00', '11.00', '12.00', '13.00', '14.00', '15.00', '16.00',
'17.00', '18.00', '19.00', '20.00', '21.00', '22.00', '23.00', '24.00'
];
ngOnInit(): void { ngOnInit(): void {
Chart.register(LineController, LineElement, PointElement, LinearScale, Title, CategoryScale, Tooltip, Filler);
this.selectedSensor = 'dht'; this.selectedSensor = 'dht';
this.parameters = this.sensorParameters[this.selectedSensor]; this.updateParameters();
this.selectedParameter = 'vicitemperature_avg';
this.updateChart();
this.resizeListener = this.onResize.bind(this);
window.addEventListener('resize', this.resizeListener);
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
this.isLoading = true; this.updateChart();
setTimeout(() => { }
this.createChart(this.dhtData.temperature, 'Temperature', '#8F5A62', this.labelHourly);
this.isLoading = false;
}, 2000);
window.addEventListener('resize', () => { ngOnDestroy(): void {
window.removeEventListener('resize', this.resizeListener);
}
onResize(): void {
if (this.chart) { if (this.chart) {
this.chart.destroy(); this.chart.destroy();
this.createChart(this.dhtData.temperature, 'Temperature', '#8F5A62', this.labelHourly); this.updateChart();
} }
}
getDataFromResponse(response: ApiResponse, sensor: string, parameter: string): { data: number[], labels: string[] } {
const sensorData = response.data[sensor as keyof typeof response.data];
const data: number[] = [];
const labels: string[] = [];
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);
}
return { data, labels };
} }
createChart(data: number[], label: string, borderColor: string, labels: string[]): void { createChart(data: number[], parameter: string, labels: string[]): void {
const canvas = this.chartElement.nativeElement; const canvas = this.chartElement.nativeElement;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
@ -84,17 +110,21 @@ export class GraphComponent implements OnInit {
} }
if (ctx) { if (ctx) {
const displayName = this.parameterDisplayNames[parameter] || parameter;
const borderColor = parameterColors[parameter] || '#000000';
const backgroundColor = `${borderColor}4D`;
this.chart = new Chart(ctx, { this.chart = new Chart(ctx, {
type: 'line', type: 'line',
data: { data: {
labels: labels, labels: labels,
datasets: [{ datasets: [{
label, label: displayName,
data, data,
borderColor, borderColor,
borderWidth: 1.5, borderWidth: 1.5,
fill: true, fill: true,
backgroundColor: borderColor + '4D', backgroundColor,
tension: 0.5, tension: 0.5,
pointRadius: 0, pointRadius: 0,
pointHoverRadius: 0, pointHoverRadius: 0,
@ -110,124 +140,72 @@ export class GraphComponent implements OnInit {
mode: 'nearest', mode: 'nearest',
intersect: false, intersect: false,
callbacks: { callbacks: {
label: (tooltipItem: any) => { label: (tooltipItem) => {
const datasetLabel = tooltipItem.dataset.label.toLowerCase(); const paramLabel = displayName;
const formattedValue = tooltipItem.formattedValue; const value = tooltipItem.formattedValue;
return `${paramLabel}: ${value}`;
let unit = '';
switch (datasetLabel) {
case 'temperature':
unit = '°C';
break;
case 'light':
unit = 'lux';
break;
case 'moisture':
case 'humidity':
unit = '%';
break;
case 'conductivity':
unit = 'mS/cm';
break;
case 'nitrogen':
case 'phosphorus':
case 'potassium':
unit = 'NPM';
break;
default:
unit = '';
} }
return `${tooltipItem.dataset.label}: ${formattedValue} ${unit}`;
},
} }
}, },
legend: { legend: { display: false }
display: false
}
}, },
scales: { scales: {
x: { x: { grid: { display: false }, beginAtZero: true },
grid:{ y: { grid: { display: false }, beginAtZero: true }
display: false
},
beginAtZero: true
},
y: {
grid:{
display: false
},
beginAtZero: true
}
} }
} }
}); });
} }
} }
getParameterKey(displayName: string): string {
return Object.keys(this.parameterDisplayNames).find(key => this.parameterDisplayNames[key] === displayName) || '';
}
onSensorChange(event: Event): void { onSensorChange(event: Event): void {
const selectElement = event.target as HTMLSelectElement; const select = event.target as HTMLSelectElement;
this.selectedSensor = selectElement.value; this.selectedSensor = select.value;
this.parameters = this.sensorParameters[this.selectedSensor]; this.updateParameters();
if (this.selectedSensor === 'dht') {
const defaultParameter = this.parameters[0]; this.selectedParameter = 'vicitemperature_avg';
(document.getElementById('parameterSelect') as HTMLSelectElement).value = defaultParameter; } else if (this.selectedSensor === 'npk1' || this.selectedSensor === 'npk2') {
this.selectedParameter = 'soiltemperature_avg';
}
this.updateChart(); this.updateChart();
} }
updateParameters(): void {
this.parameters = this.sensorParameters[this.selectedSensor].map(param => this.parameterDisplayNames[param]);
this.selectedParameter = this.parameters[1];
}
updateChart(): void { updateChart(): void {
this.isLoading = true; this.isLoading = true;
setTimeout(() => {
const parameter = (document.getElementById('parameterSelect') as HTMLSelectElement).value.toLowerCase();
let newData: number[] | undefined; const today = new Date();
let newLabel: string = ''; const year = today.getFullYear();
let newColor: string = ''; const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const humidity = '#16423C'; const startDate = `${year}-${month}-${day}`;
const temperature = '#8F5A62'; const timeRange = 'HOURLY';
const light = '#DF9B55';
const moisture = '#54909c';
const conductivity = '#661311';
const ph = '#664735';
const nitrogen = '#3a6635';
const phosphorus = '#3f3566';
const potassium = '#5f3566';
switch (this.selectedSensor) { this.sensorService.getSensorData(this.selectedSensor, this.selectedParameter, startDate, timeRange).subscribe(
case 'dht': (response: ApiResponse) => {
newData = this.dhtData[parameter as keyof typeof this.dhtData]; if (response.statusCode === 200) {
newLabel = parameter.charAt(0).toUpperCase() + parameter.slice(1); const { data, labels } = this.getDataFromResponse(response, this.selectedSensor, this.selectedParameter);
newColor = (parameter === 'humidity') ? humidity : this.createChart(data, this.selectedParameter, labels);
(parameter === 'temperature') ? temperature : light; } else {
break; console.error('Error fetching data:', response.message);
case 'npk1':
newData = this.npk1Data[parameter as keyof typeof this.npk1Data];
newLabel = parameter.charAt(0).toUpperCase() + parameter.slice(1);
newColor = (parameter === 'temperature') ? temperature :
(parameter === 'moisture') ? moisture :
(parameter === 'conductivity') ? conductivity :
(parameter === 'ph') ? ph :
(parameter === 'nitrogen') ? nitrogen :
(parameter === 'phosphorus') ? phosphorus : potassium;
break;
case 'npk2':
newData = this.npk2Data[parameter as keyof typeof this.npk2Data];
newLabel = parameter.charAt(0).toUpperCase() + parameter.slice(1);
newColor = (parameter === 'temperature') ? temperature :
(parameter === 'moisture') ? moisture :
(parameter === 'conductivity') ? conductivity :
(parameter === 'ph') ? ph :
(parameter === 'nitrogen') ? nitrogen :
(parameter === 'phosphorus') ? phosphorus : potassium;
break;
default:
console.error('Sensor tidak ditemukan');
return;
} }
this.createChart(newData, newLabel, newColor, this.labelHourly);
this.isLoading = false; this.isLoading = false;
}, 2000); },
(error) => {
console.error('API Error:', error);
this.isLoading = false;
}
);
} }
} }