Spaces:
Runtime error
Runtime error
Upload 4 files
Browse files- CameraPage.html +43 -0
- ReportPage.html +65 -0
- cameraPage.js +173 -0
- reportPage.js +112 -0
CameraPage.html
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="id">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Pendeteksi Tingkat Stres</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
</head>
|
| 9 |
+
<body class="bg-gray-100 font-sans">
|
| 10 |
+
|
| 11 |
+
<div class="max-w-2xl mx-auto p-6">
|
| 12 |
+
|
| 13 |
+
<h1 class="text-3xl font-bold text-center text-gray-800 mb-8">Pendeteksi Tingkat Stres</h1>
|
| 14 |
+
|
| 15 |
+
<div class="bg-white p-6 rounded-lg shadow-md">
|
| 16 |
+
<label for="time-input" class="block text-lg font-semibold text-gray-700 mb-2">Masukkan Waktu Pengamatan (detik):</label>
|
| 17 |
+
<input id="time-input" type="number" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Contoh: 10" min="1">
|
| 18 |
+
<button id="start-btn" class="mt-4 w-full py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Mulai Deteksi</button>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<div class="mt-6 text-center">
|
| 22 |
+
<p id="countdown" class="text-2xl font-bold text-gray-800">00:00</p>
|
| 23 |
+
</div>
|
| 24 |
+
|
| 25 |
+
<div class="mt-8">
|
| 26 |
+
<video id="video" width="100%" height="auto" class="border-2 border-gray-300 rounded-lg" autoplay></video>
|
| 27 |
+
</div>
|
| 28 |
+
|
| 29 |
+
<div class="mt-6 text-center">
|
| 30 |
+
<p id="status" class="text-lg text-gray-700">Tunggu untuk memulai...</p>
|
| 31 |
+
<p id="stress-level" class="text-xl font-bold text-red-600"></p>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<div class="mt-6 text-center hidden" id="show-result-btn">
|
| 35 |
+
<a href="ReportPage.html" class="w-full py-2 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">Show Result</a>
|
| 36 |
+
</div>
|
| 37 |
+
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<script src="cameraPage.js"></script>
|
| 41 |
+
|
| 42 |
+
</body>
|
| 43 |
+
</html>
|
ReportPage.html
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="id">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Laporan Deteksi Stres</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 9 |
+
</head>
|
| 10 |
+
<body class="bg-gray-100 font-sans">
|
| 11 |
+
|
| 12 |
+
<div class="max-w-4xl mx-auto p-6">
|
| 13 |
+
|
| 14 |
+
<h1 class="text-3xl font-bold text-center text-gray-800 mb-8">Laporan Analisis Tingkat Stres</h1>
|
| 15 |
+
|
| 16 |
+
<div class="bg-white p-6 rounded-lg shadow-md mb-6">
|
| 17 |
+
<h2 class="text-xl font-semibold text-gray-700 mb-4">Ringkasan Analisis</h2>
|
| 18 |
+
|
| 19 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 20 |
+
<div>
|
| 21 |
+
<p class="text-gray-600">Emosi Dominan:</p>
|
| 22 |
+
<p id="dominant-emotion" class="text-2xl font-bold text-blue-600"></p>
|
| 23 |
+
|
| 24 |
+
<p class="text-gray-600 mt-4">Rata-rata Tingkat Stres:</p>
|
| 25 |
+
<p id="avg-stress" class="text-2xl font-bold"></p>
|
| 26 |
+
|
| 27 |
+
<p class="text-gray-600 mt-4">Total Frame Dianalisis:</p>
|
| 28 |
+
<p id="total-frames" class="text-2xl font-bold text-gray-800"></p>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<div>
|
| 32 |
+
<p class="text-gray-600">Tingkat Stres Tertinggi:</p>
|
| 33 |
+
<p id="max-stress" class="text-2xl font-bold text-red-600"></p>
|
| 34 |
+
|
| 35 |
+
<p class="text-gray-600 mt-4">Tingkat Stres Terendah:</p>
|
| 36 |
+
<p id="min-stress" class="text-2xl font-bold text-green-600"></p>
|
| 37 |
+
|
| 38 |
+
<p class="text-gray-600 mt-4">Durasi Analisis:</p>
|
| 39 |
+
<p id="duration" class="text-2xl font-bold text-gray-800"></p>
|
| 40 |
+
</div>
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
<div class="bg-white p-6 rounded-lg shadow-md mb-6">
|
| 45 |
+
<h2 class="text-xl font-semibold text-gray-700 mb-4">Distribusi Emosi</h2>
|
| 46 |
+
<div class="w-full h-64">
|
| 47 |
+
<canvas id="emotion-chart"></canvas>
|
| 48 |
+
</div>
|
| 49 |
+
</div>
|
| 50 |
+
|
| 51 |
+
<div class="bg-white p-6 rounded-lg shadow-md mb-6">
|
| 52 |
+
<h2 class="text-xl font-semibold text-gray-700 mb-4">Interpretasi Tingkat Stres</h2>
|
| 53 |
+
<div id="stress-interpretation" class="p-4 rounded-lg"></div>
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<div class="mt-6 text-center">
|
| 57 |
+
<a href="CameraPage.html" class="py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Kembali ke Kamera</a>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<script src="reportPage.js"></script>
|
| 63 |
+
|
| 64 |
+
</body>
|
| 65 |
+
</html>
|
cameraPage.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const timeInput = document.getElementById('time-input');
|
| 2 |
+
const startBtn = document.getElementById('start-btn');
|
| 3 |
+
const videoElement = document.getElementById('video');
|
| 4 |
+
const statusElement = document.getElementById('status');
|
| 5 |
+
const stressLevelElement = document.getElementById('stress-level');
|
| 6 |
+
const countdownElement = document.getElementById('countdown');
|
| 7 |
+
const showResultBtn = document.getElementById('show-result-btn');
|
| 8 |
+
|
| 9 |
+
let stream;
|
| 10 |
+
let interval;
|
| 11 |
+
let countdownInterval;
|
| 12 |
+
let totalTime;
|
| 13 |
+
let frameCount = 0;
|
| 14 |
+
let countdownTime;
|
| 15 |
+
let sessionId = Date.now().toString();
|
| 16 |
+
|
| 17 |
+
async function startCamera() {
|
| 18 |
+
try {
|
| 19 |
+
stream = await navigator.mediaDevices.getUserMedia({
|
| 20 |
+
video: true,
|
| 21 |
+
});
|
| 22 |
+
videoElement.srcObject = stream;
|
| 23 |
+
} catch (err) {
|
| 24 |
+
console.error("Error accessing the camera", err);
|
| 25 |
+
statusElement.textContent = "Gagal mengakses kamera.";
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
function stopCamera() {
|
| 30 |
+
if (stream) {
|
| 31 |
+
let tracks = stream.getTracks();
|
| 32 |
+
tracks.forEach(track => track.stop());
|
| 33 |
+
videoElement.srcObject = null;
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
async function sendFrameToBackend(dataUrl) {
|
| 38 |
+
try {
|
| 39 |
+
console.log("Preparing to send frame to backend...");
|
| 40 |
+
|
| 41 |
+
const formData = new FormData();
|
| 42 |
+
formData.append('image', dataUrl);
|
| 43 |
+
formData.append('sessionId', sessionId);
|
| 44 |
+
|
| 45 |
+
console.log("Sending request to backend...");
|
| 46 |
+
|
| 47 |
+
const controller = new AbortController();
|
| 48 |
+
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
| 49 |
+
|
| 50 |
+
const apiResponse = await fetch('https://PatriciaWening-emoapi.hf.space/api/deteksi-emosi', {
|
| 51 |
+
method: 'POST',
|
| 52 |
+
body: formData,
|
| 53 |
+
signal: controller.signal
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
clearTimeout(timeoutId);
|
| 57 |
+
|
| 58 |
+
if (!apiResponse.ok) {
|
| 59 |
+
const errorText = await apiResponse.text();
|
| 60 |
+
console.error(`Server returned error ${apiResponse.status}: ${errorText}`);
|
| 61 |
+
throw new Error(`HTTP error! Status: ${apiResponse.status}`);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
const data = await apiResponse.json();
|
| 65 |
+
console.log("API response:", data);
|
| 66 |
+
|
| 67 |
+
if (data.faceDetected) {
|
| 68 |
+
drawFaceRectangle(data.faceRegion);
|
| 69 |
+
updateStressLevel(data.stressLevel, data.emotion);
|
| 70 |
+
statusElement.textContent = `Emosi terdeteksi: ${data.emotion}, Tingkat stres: ${data.stressLevel}%`;
|
| 71 |
+
} else {
|
| 72 |
+
statusElement.textContent = data.error || "Wajah tidak terdeteksi, mohon cek posisi kamera.";
|
| 73 |
+
}
|
| 74 |
+
} catch (error) {
|
| 75 |
+
console.error('Error sending frame to backend:', error);
|
| 76 |
+
statusElement.textContent = "Kesalahan saat memproses data: " + error.message;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
function drawFaceRectangle(region) {
|
| 81 |
+
const canvas = document.createElement('canvas');
|
| 82 |
+
canvas.width = videoElement.videoWidth;
|
| 83 |
+
canvas.height = videoElement.videoHeight;
|
| 84 |
+
const context = canvas.getContext('2d');
|
| 85 |
+
|
| 86 |
+
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
| 87 |
+
|
| 88 |
+
context.strokeStyle = '#00FF00';
|
| 89 |
+
context.lineWidth = 3;
|
| 90 |
+
context.strokeRect(region.x, region.y, region.width, region.height);
|
| 91 |
+
|
| 92 |
+
const dataURL = canvas.toDataURL('image/png');
|
| 93 |
+
const tempImg = document.createElement('img');
|
| 94 |
+
tempImg.src = dataURL;
|
| 95 |
+
tempImg.style.width = '100%';
|
| 96 |
+
tempImg.style.height = 'auto';
|
| 97 |
+
tempImg.style.border = '2px solid #ccc';
|
| 98 |
+
tempImg.style.borderRadius = '8px';
|
| 99 |
+
|
| 100 |
+
const videoParent = videoElement.parentNode;
|
| 101 |
+
videoParent.insertBefore(tempImg, videoElement);
|
| 102 |
+
videoElement.style.display = 'none';
|
| 103 |
+
|
| 104 |
+
setTimeout(() => {
|
| 105 |
+
tempImg.remove();
|
| 106 |
+
videoElement.style.display = 'block';
|
| 107 |
+
}, 200);
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
function updateStressLevel(stressLevel, emotion) {
|
| 111 |
+
stressLevelElement.textContent = `Tingkat Stres: ${stressLevel} (${emotion})`;
|
| 112 |
+
|
| 113 |
+
if (stressLevel >= 80) {
|
| 114 |
+
stressLevelElement.classList.remove('text-green-600', 'text-yellow-600');
|
| 115 |
+
stressLevelElement.classList.add('text-red-600');
|
| 116 |
+
} else if (stressLevel >= 50) {
|
| 117 |
+
stressLevelElement.classList.remove('text-green-600', 'text-red-600');
|
| 118 |
+
stressLevelElement.classList.add('text-yellow-600');
|
| 119 |
+
} else {
|
| 120 |
+
stressLevelElement.classList.remove('text-yellow-600', 'text-red-600');
|
| 121 |
+
stressLevelElement.classList.add('text-green-600');
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
localStorage.setItem('currentSessionId', sessionId);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
async function captureAndAnalyze() {
|
| 128 |
+
const canvas = document.createElement('canvas');
|
| 129 |
+
const context = canvas.getContext('2d');
|
| 130 |
+
canvas.width = videoElement.videoWidth;
|
| 131 |
+
canvas.height = videoElement.videoHeight;
|
| 132 |
+
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
| 133 |
+
|
| 134 |
+
const frame = canvas.toDataURL('image/jpeg');
|
| 135 |
+
sendFrameToBackend(frame);
|
| 136 |
+
|
| 137 |
+
frameCount++;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
function updateCountdown() {
|
| 141 |
+
const minutes = Math.floor(countdownTime / 60);
|
| 142 |
+
const seconds = countdownTime % 60;
|
| 143 |
+
countdownElement.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
| 144 |
+
countdownTime--;
|
| 145 |
+
|
| 146 |
+
if (countdownTime < 0) {
|
| 147 |
+
clearInterval(countdownInterval);
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
function startDetection() {
|
| 152 |
+
totalTime = parseInt(timeInput.value) * 1000;
|
| 153 |
+
if (isNaN(totalTime) || totalTime <= 0) {
|
| 154 |
+
statusElement.textContent = "Waktu input tidak valid.";
|
| 155 |
+
return;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
countdownTime = totalTime / 1000;
|
| 159 |
+
countdownInterval = setInterval(updateCountdown, 1000);
|
| 160 |
+
|
| 161 |
+
statusElement.textContent = `Kamera aktif selama ${totalTime / 1000} detik.`;
|
| 162 |
+
startCamera();
|
| 163 |
+
interval = setInterval(captureAndAnalyze, 1000);
|
| 164 |
+
|
| 165 |
+
setTimeout(() => {
|
| 166 |
+
clearInterval(interval);
|
| 167 |
+
stopCamera();
|
| 168 |
+
statusElement.textContent = "Proses selesai!";
|
| 169 |
+
showResultBtn.classList.remove('hidden');
|
| 170 |
+
}, totalTime);
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
startBtn.addEventListener('click', startDetection);
|
reportPage.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const sessionId = localStorage.getItem('currentSessionId');
|
| 2 |
+
|
| 3 |
+
async function fetchReportData() {
|
| 4 |
+
try {
|
| 5 |
+
if (!sessionId) {
|
| 6 |
+
document.getElementById('dominant-emotion').textContent = "Tidak ada data";
|
| 7 |
+
return;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
const response = await fetch(`http://localhost:8080/api/session-report/${sessionId}`);
|
| 11 |
+
const data = await response.json();
|
| 12 |
+
|
| 13 |
+
if (data.error) {
|
| 14 |
+
document.getElementById('dominant-emotion').textContent = data.error;
|
| 15 |
+
return;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
document.getElementById('dominant-emotion').textContent = capitalizeFirstLetter(data.dominantEmotion);
|
| 19 |
+
document.getElementById('avg-stress').textContent = data.averageStressLevel;
|
| 20 |
+
setStressLevelColor('avg-stress', data.averageStressLevel);
|
| 21 |
+
document.getElementById('total-frames').textContent = data.totalFrames;
|
| 22 |
+
document.getElementById('max-stress').textContent = data.maxStressLevel;
|
| 23 |
+
document.getElementById('min-stress').textContent = data.minStressLevel;
|
| 24 |
+
document.getElementById('duration').textContent = `${Math.round(data.totalFrames / 1)} detik`;
|
| 25 |
+
|
| 26 |
+
setStressInterpretation(data.averageStressLevel);
|
| 27 |
+
|
| 28 |
+
createEmotionChart(data.emotionCounts);
|
| 29 |
+
} catch (error) {
|
| 30 |
+
console.error('Error fetching report data:', error);
|
| 31 |
+
document.getElementById('dominant-emotion').textContent = "Error mengambil data laporan";
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
function capitalizeFirstLetter(string) {
|
| 36 |
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
function setStressLevelColor(elementId, stressLevel) {
|
| 40 |
+
const element = document.getElementById(elementId);
|
| 41 |
+
|
| 42 |
+
if (stressLevel >= 80) {
|
| 43 |
+
element.classList.add('text-red-600');
|
| 44 |
+
} else if (stressLevel >= 50) {
|
| 45 |
+
element.classList.add('text-yellow-600');
|
| 46 |
+
} else {
|
| 47 |
+
element.classList.add('text-green-600');
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function setStressInterpretation(stressLevel) {
|
| 52 |
+
const interpretationElement = document.getElementById('stress-interpretation');
|
| 53 |
+
let message, bgColor, textColor;
|
| 54 |
+
|
| 55 |
+
if (stressLevel >= 80) {
|
| 56 |
+
message = "Tingkat stres tinggi. Rekomendasi: Istirahat dan konsultasi dengan profesional kesehatan mental.";
|
| 57 |
+
bgColor = "bg-red-100";
|
| 58 |
+
textColor = "text-red-800";
|
| 59 |
+
} else if (stressLevel >= 50) {
|
| 60 |
+
message = "Tingkat stres sedang. Rekomendasi: Pertimbangkan teknik relaksasi dan manajemen stres.";
|
| 61 |
+
bgColor = "bg-yellow-100";
|
| 62 |
+
textColor = "text-yellow-800";
|
| 63 |
+
} else {
|
| 64 |
+
message = "Tingkat stres rendah. Tetap jaga kesehatan fisik dan mental.";
|
| 65 |
+
bgColor = "bg-green-100";
|
| 66 |
+
textColor = "text-green-800";
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
interpretationElement.classList.add(bgColor, textColor);
|
| 70 |
+
interpretationElement.textContent = message;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
function createEmotionChart(emotionCounts) {
|
| 74 |
+
const ctx = document.getElementById('emotion-chart').getContext('2d');
|
| 75 |
+
|
| 76 |
+
const labels = Object.keys(emotionCounts).map(emotion => capitalizeFirstLetter(emotion));
|
| 77 |
+
const data = Object.values(emotionCounts);
|
| 78 |
+
|
| 79 |
+
const backgroundColors = [
|
| 80 |
+
'rgba(255, 99, 132, 0.7)',
|
| 81 |
+
'rgba(75, 192, 192, 0.7)',
|
| 82 |
+
'rgba(255, 205, 86, 0.7)',
|
| 83 |
+
'rgba(54, 162, 235, 0.7)',
|
| 84 |
+
'rgba(153, 102, 255, 0.7)',
|
| 85 |
+
'rgba(201, 203, 207, 0.7)',
|
| 86 |
+
'rgba(255, 159, 64, 0.7)'
|
| 87 |
+
];
|
| 88 |
+
|
| 89 |
+
new Chart(ctx, {
|
| 90 |
+
type: 'doughnut',
|
| 91 |
+
data: {
|
| 92 |
+
labels: labels,
|
| 93 |
+
datasets: [{
|
| 94 |
+
data: data,
|
| 95 |
+
backgroundColor: backgroundColors.slice(0, labels.length),
|
| 96 |
+
borderColor: 'white',
|
| 97 |
+
borderWidth: 2
|
| 98 |
+
}]
|
| 99 |
+
},
|
| 100 |
+
options: {
|
| 101 |
+
responsive: true,
|
| 102 |
+
maintainAspectRatio: false,
|
| 103 |
+
plugins: {
|
| 104 |
+
legend: {
|
| 105 |
+
position: 'right'
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
});
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
window.addEventListener('DOMContentLoaded', fetchReportData);
|