NIM_E31221299/resources/views/sentiment/report.blade.php

335 lines
17 KiB
PHP

@extends('layouts.app')
@section('title', 'Report Hasil Training')
@section('content')
<div class="max-w-6xl mx-auto px-4 py-8 space-y-8">
{{-- Header --}}
<div class="text-center">
<h2 class="text-4xl font-extrabold inline-flex items-center space-x-3">
<i class="fas fa-chart-bar text-indigo-500 animate-bounce"></i>
<span>Report Hasil Training</span>
</h2>
</div>
{{-- Flash Message --}}
@if (session('message'))
<div class="flex items-center justify-center">
<div
class="bg-green-100 border border-green-400 text-green-700 px-6 py-3 rounded-lg shadow-md animate-pulse inline-flex items-center space-x-2">
<i class="fas fa-check-circle"></i>
<span>{{ session('message') }}</span>
<button type="button" class="ml-4 text-green-700 hover:text-green-900" data-bs-dismiss="alert">
<i class="fas fa-times"></i>
</button>
</div>
</div>
@endif
{{-- Filter Form --}}
<form method="GET" action="{{ route('sentiment.report') }}" class="flex items-center justify-center space-x-2">
<label for="runSelect" class="font-medium text-gray-700">Pilih Hasil Training:</label>
<select name="id" id="runSelect" onchange="this.form.submit()"
class="border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-400 transition-shadow">
@foreach ($availableRuns as $run)
<option value="{{ $run }}" {{ $run === $runId ? 'selected' : '' }}>
{{ $run }}
</option>
@endforeach
</select>
</form>
<div class="space-y-8">
{{-- Distribusi Sentimen --}}
@if (!empty($results['distribution']))
<div
class="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transform hover:-translate-y-1 transition">
<div class="bg-indigo-600 text-white px-6 py-4 flex items-center">
<i class="fas fa-chart-pie mr-2 text-xl"></i>
<h3 class="text-lg font-semibold">Distribusi Sentimen</h3>
</div>
<div class="p-6">
<p class="mb-4 text-indigo-700 bg-indigo-50 border-l-4 border-indigo-400 px-4 py-2 rounded">
Grafik ini menunjukkan distribusi jumlah data pada masing-masing kategori sentimen.
</p>
<div class="flex justify-center">
<div class="w-full max-w-md">
<canvas id="sentimentChart" class="rounded-lg shadow-md"></canvas>
</div>
</div>
</div>
</div>
@endif
{{-- TF-IDF Top --}}
@if (!empty($results['tfidf']))
<div
class="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transform hover:-translate-y-1 transition">
<div class="bg-green-600 text-white px-6 py-4 flex items-center">
<i class="fas fa-key mr-2 text-xl"></i>
<h3 class="text-lg font-semibold">TF-IDF Top (50)</h3>
</div>
<div class="p-6">
<p class="mb-4 text-green-700 bg-green-50 border-l-4 border-green-400 px-4 py-2 rounded">
Tabel ini menampilkan 50 kata dengan skor TF-IDF tertinggi.
</p>
<div class="overflow-auto max-h-64">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50 sticky top-0">
<tr>
@foreach (array_keys($results['tfidf'][0]) as $col)
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">
{{ ucwords(str_replace('_', ' ', $col)) }}
</th>
@endforeach
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@foreach ($results['tfidf'] as $row)
<tr class="hover:bg-gray-50 transition">
@foreach ($row as $val)
<td class="px-4 py-2 text-sm text-gray-600">{{ $val }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
{{-- Confusion Matrix --}}
@if (!empty($results['confusion']))
@php
$matrix = $results['confusion'];
$labels = array_keys($matrix[0]);
$labelNames = ['netral', 'positif', 'negatif'];
foreach ($matrix as &$row) {
array_shift($row);
}
array_shift($labels);
unset($row);
$maxVal = max(array_map('max', array_map('array_values', $matrix)));
@endphp
<div
class="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transform hover:-translate-y-1 transition">
<div class="bg-yellow-500 text-white px-6 py-4 flex items-center">
<i class="fas fa-th-large mr-2 text-xl"></i>
<h3 class="text-lg font-semibold">Confusion Matrix</h3>
</div>
<div class="p-6">
<p class="mb-4 text-yellow-700 bg-yellow-50 border-l-4 border-yellow-400 px-4 py-2 rounded">
Confusion Matrix memperlihatkan jumlah prediksi benar & salah untuk tiap kelas.
</p>
<div class="overflow-auto">
<table class="min-w-full text-center">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2">Actual / Predicted </th>
@foreach ($labels as $pred)
<th class="px-4 py-2">{{ ucfirst($pred) }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($matrix as $i => $row)
<tr class="hover:bg-gray-50 transition">
<th class="bg-gray-50 px-4 py-2 font-medium">
{{ ucfirst($labelNames[$i] ?? $i) }}
</th>
@foreach ($labels as $pred)
@php
$val = floatval($row[$pred]);
$ratio = $maxVal > 0 ? $val / $maxVal : 0;
// warna dasar untuk true positive hijau, else biru
$baseColor =
$labelNames[$i] === $pred ? [40, 167, 69] : [59, 130, 246];
// hitung rgba dengan opacity = ratio
$bgColor = "rgba({$baseColor[0]},{$baseColor[1]},{$baseColor[2]},{$ratio})";
$textClass =
$ratio > 0.5 ? 'text-white font-bold' : 'text-gray-800';
@endphp
<td class="px-4 py-2 {{ $val > 0 ? $textClass : 'text-gray-400' }}"
style="{{ $val > 0 ? "background-color: {$bgColor};" : '' }}">
{{ $val > 0 ? $val : '0' }}
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
{{-- Evaluation Full Data --}}
@if (!empty($results['evaluation_full']))
<div
class="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transform hover:-translate-y-1 transition">
<div class="bg-blue-600 text-white px-6 py-4 flex items-center">
<i class="fas fa-chart-line mr-2 text-xl"></i>
<h3 class="text-lg font-semibold">Evaluation Full Data</h3>
</div>
<div class="p-6">
<p class="mb-4 text-blue-700 bg-blue-50 border-l-4 border-blue-400 px-4 py-2 rounded">
Metrik evaluasi setelah dilatih pada seluruh data.
</p>
<div class="overflow-auto max-h-48">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50 sticky top-0">
<tr>
@foreach (array_keys($results['evaluation_full'][0]) as $col)
<th class="px-4 py-2 text-left text-sm font-medium text-gray-700">
{{ ucwords(str_replace('_', ' ', $col)) }}</th>
@endforeach
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@foreach ($results['evaluation_full'] as $row)
<tr class="hover:bg-gray-50 transition">
@foreach ($row as $val)
<td class="px-4 py-2 text-sm text-gray-600">
{{ is_numeric($val) ? number_format($val, 2) : $val }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
@if (!empty($results['evaluation_full_img']))
<div class="mt-4 text-center">
<img src="{{ $results['evaluation_full_img'] }}" class="inline-block rounded-lg shadow-md"
alt="Evaluation Full Data">
</div>
@endif
</div>
</div>
@endif
{{-- Top Features --}}
@if (!empty($results['top_features_img']))
<div
class="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transform hover:-translate-y-1 transition">
<div class="bg-gray-700 text-white px-6 py-4 flex items-center">
<i class="fas fa-star mr-2 text-xl"></i>
<h3 class="text-lg font-semibold">Top Features per Class</h3>
</div>
<div class="p-6 text-center">
<img src="{{ $results['top_features_img'] }}" class="inline-block rounded-lg shadow-md"
alt="Top Features">
</div>
</div>
@endif
{{-- WordClouds --}}
@if (!empty($results['wordclouds']))
<div
class="bg-white rounded-2xl shadow-lg overflow-hidden hover:shadow-2xl transform hover:-translate-y-1 transition">
<div class="bg-black text-white px-6 py-4 flex items-center">
<i class="fas fa-cloud mr-2 text-xl"></i>
<h3 class="text-lg font-semibold">WordCloud per Kelas</h3>
</div>
<div class="p-6 grid grid-cols-1 md:grid-cols-3 gap-6">
@foreach ($results['wordclouds'] as $wc)
<div class="text-center">
<h5 class="mb-2 font-medium">{{ ucfirst($wc['label']) }}</h5>
<img src="{{ $wc['url'] }}" class="inline-block rounded-lg shadow-md"
alt="WC {{ $wc['label'] }}">
</div>
@endforeach
</div>
</div>
@endif
{{-- Action Buttons --}}
<div class="flex justify-between">
<a href="{{ route('sentiment.upload') }}"
class="inline-flex items-center space-x-2 bg-gray-200 hover:bg-gray-300 text-gray-800 px-5 py-2 rounded-lg shadow transition">
<i class="fas fa-upload"></i><span>Upload & Train lagi</span>
</a>
<a href="{{ route('sentiment.infer') }}"
class="inline-flex items-center space-x-2 bg-indigo-600 hover:bg-indigo-700 text-white px-5 py-2 rounded-lg shadow transition">
<i class="fas fa-magic"></i><span>Inferensi</span>
</a>
</div>
</div>
</div>
@endsection
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
<script>
Chart.register(ChartDataLabels);
document.addEventListener('DOMContentLoaded', () => {
// 1. Ambil dan parse angka
const rawCounts = @json(collect($results['distribution'])->pluck('count'));
const counts = rawCounts.map(n => Number(n));
const labels = @json(collect($results['distribution'])->pluck('label'));
const ctx = document.getElementById('sentimentChart').getContext('2d');
new Chart(ctx, {
type: 'pie',
data: {
labels,
datasets: [{
data: counts, // pakai counts yang sudah Number
backgroundColor: ['#4CAF50', '#F44336', '#FFC107'],
borderColor: '#fff',
borderWidth: 2
}]
},
options: {
responsive: true,
animation: {
animateRotate: true,
duration: 1000,
easing: 'easeOutBounce'
},
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 12,
padding: 16,
font: {
size: 12
}
}
},
tooltip: {
callbacks: {
label: ctx => {
const v = ctx.raw,
total = counts.reduce((a, b) => a + b, 0),
p = total > 0 ? (v / total * 100).toFixed(1) : 0;
return `${ctx.label}: ${v} (${p}%)`;
}
}
},
datalabels: {
display: true,
color: '#fff',
font: {
weight: 'bold',
size: 11
},
formatter: (value, ctx) => {
const data = ctx.chart.data.datasets[0].data;
const total = data.reduce((sum, v) => sum + v, 0);
return total > 0 ?
((value / total) * 100).toFixed(1) + '%' :
'';
}
}
}
}
});
});
</script>
<script></script>
@endpush