diff --git a/app/Http/Controllers/CuranmorController.php b/app/Http/Controllers/CuranmorController.php index 4fc8e06..c53b0d9 100644 --- a/app/Http/Controllers/CuranmorController.php +++ b/app/Http/Controllers/CuranmorController.php @@ -72,6 +72,9 @@ public function store(Request $request) $hasil = $service->hitungKMeansCuranmor(); file_put_contents(storage_path('app/public/hasil_kmeans_curanmor.json'), json_encode($hasil)); + $serviceSSECuranmor = new KMeansService(); + $serviceSSECuranmor->SSEElbowCuranmor(); + // =====CODE TAMBAH SEBELUMNYA========= // $validateData = $request->validate([ // 'kecamatan_id' =>'required|max:255|exists:kecamatans,id|unique:curanmors,kecamatan_id', @@ -144,6 +147,9 @@ public function update(Request $request, Curanmor $curanmor) // simpan hasil ke file json file_put_contents(storage_path('app/public/hasil_kmeans_curanmor.json'), json_encode($hasil)); + + $serviceSSECuranmor = new KMeansService(); + $serviceSSECuranmor->SSEElbowCuranmor(); return redirect('/dashboard/curanmor')->with('succes', 'Data Kecamatan Berhasil Diubah'); } catch (\Exception $e) { @@ -168,6 +174,9 @@ public function destroy($curanmor) // Hapus data $hapus->delete(); + $serviceSSECuranmor = new KMeansService(); + $serviceSSECuranmor->SSEElbowCuranmor(); + return redirect('/dashboard/curanmor')->with('succes', 'Data Curanmor Berhasil Dihapus'); } catch (\Exception $e) { return redirect('/dashboard/curanmor')->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); diff --git a/app/Http/Controllers/CurasController.php b/app/Http/Controllers/CurasController.php index d809395..91c148c 100644 --- a/app/Http/Controllers/CurasController.php +++ b/app/Http/Controllers/CurasController.php @@ -77,6 +77,9 @@ public function store(Request $request) // simpan hasil ke file json file_put_contents(storage_path('app/public/hasil_kmeans_curas.json'), json_encode($hasil)); + + $serviceSSECuras = new KMeansService(); + $serviceSSECuras->SSEElbowCuras(); return redirect('/dashboard/curas')->with('succes', 'Data curas berhasil ditambahkan.'); }catch (\Exception $e){ @@ -147,6 +150,9 @@ public function update(Request $request, $id) // simpan hasil ke file json file_put_contents(storage_path('app/public/hasil_kmeans_curas.json'), json_encode($hasil)); + $serviceSSECuras = new KMeansService(); + $serviceSSECuras->SSEElbowCuras(); + return redirect('/dashboard/curas')->with('succes', 'Data Kecamatan Berhasil Diubah'); } catch (\Exception $e) { return redirect('/dashboard/curas')->with('error', 'Data Kecamatan Gagal Diubah: ' . $e->getMessage()); diff --git a/app/Http/Controllers/DetailCuranmorController.php b/app/Http/Controllers/DetailCuranmorController.php index e7e228e..598e83c 100644 --- a/app/Http/Controllers/DetailCuranmorController.php +++ b/app/Http/Controllers/DetailCuranmorController.php @@ -86,6 +86,9 @@ public function destroy($id) // simpan hasil ke file json file_put_contents(storage_path('app/public/hasil_kmeans_curanmor.json'), json_encode($hasil)); + $serviceSSECuranmor = new KMeansService(); + $serviceSSECuranmor->SSEElbowCuranmor(); + return redirect('/dashboard/detail-curanmor')->with('succes', 'Data berhasil dihapus dan curanmor diperbarui.'); } catch (\Exception $e) { return redirect('/dashboard/detail-curanmor')->with('error', 'Terjadi kesalahan Ketika Menghapus Data : ' . $e->getMessage()); diff --git a/app/Http/Controllers/DetailCurasController.php b/app/Http/Controllers/DetailCurasController.php index 31aced7..0928731 100644 --- a/app/Http/Controllers/DetailCurasController.php +++ b/app/Http/Controllers/DetailCurasController.php @@ -89,6 +89,9 @@ public function destroy($id) // simpan hasil ke file json file_put_contents(storage_path('app/public/hasil_kmeans_curas.json'), json_encode($hasil)); + $serviceSSECuras = new KMeansService(); + $serviceSSECuras->SSEElbowCuras(); + return redirect('/dashboard/detail-curas')->with('succes', 'Data berhasil dihapus dan curas diperbarui.'); } catch (\Exception $e) { return redirect('/dashboard/detail-curas')->with('error', 'Terjadi kesalahan Ketika Menghapus Data : ' . $e->getMessage()); diff --git a/app/Http/Controllers/KmeansController.php b/app/Http/Controllers/KmeansController.php index 3828c3a..52f9b65 100644 --- a/app/Http/Controllers/KmeansController.php +++ b/app/Http/Controllers/KmeansController.php @@ -148,21 +148,23 @@ public function KMeansCuranmor() foreach ($data as $item) { $jarak = []; - + foreach ($centroids as $idx => $centroid) { - $dist = abs($item->jumlah_curanmor - $centroid['jumlah_curanmor']); + // Menggunakan Euclidean distance standar (akar kuadrat dari selisih kuadrat) + $dist = sqrt(pow($item->jumlah_curanmor - $centroid['jumlah_curanmor'], 2)); $jarak["C" . ($idx + 1)] = $dist; } - + $iterasi[$i][] = array_merge(['kecamatan_id' => $item->kecamatan_id], $jarak); - + $minIndex = array_keys($jarak, min($jarak))[0]; // e.g. "jarakC2" $clusterNumber = (int) str_replace("C", "", $minIndex); - + $clustered[$clusterNumber][] = $item; $item->temp_klaster = $clusterNumber; $currentAssignment[$item->id] = $clusterNumber; } + // ✨ Cek konvergensi: jika assignment sekarang == sebelumnya, break if ($currentAssignment === $prevAssignment) { diff --git a/app/Services/KMeansService.php b/app/Services/KMeansService.php index 2c1fa8a..3e2b2a2 100644 --- a/app/Services/KMeansService.php +++ b/app/Services/KMeansService.php @@ -221,41 +221,44 @@ public function hitungKMeansCuranmor() public function SSEElbowCuras() { $data = Curas::select('id', 'jumlah_curas')->get(); - $maxK = 10; + $maxK = 4; $maxIterasi = 100; $elbowData = []; - + for ($k = 1; $k <= $maxK; $k++) { // Inisialisasi centroid awal secara acak $centroids = $data->unique('jumlah_curas')->shuffle()->take($k)->values()->map(function ($item) { return ['jumlah_curas' => $item->jumlah_curas]; }); - + + // Simpan centroid awal sebagai array angka + $centroidAwal = $centroids->pluck('jumlah_curas')->toArray(); + $prevAssignment = []; - + for ($iter = 0; $iter < $maxIterasi; $iter++) { $clustered = []; $currentAssignment = []; - + foreach ($data as $item) { $jarak = []; - + foreach ($centroids as $idx => $centroid) { $dist = abs($item->jumlah_curas - $centroid['jumlah_curas']); $jarak[$idx] = $dist; } - + $minIndex = array_keys($jarak, min($jarak))[0]; $clustered[$minIndex][] = $item; $currentAssignment[$item->id] = $minIndex; } - + if ($currentAssignment === $prevAssignment) { break; } - + $prevAssignment = $currentAssignment; - + // Update centroid foreach ($clustered as $key => $group) { $avg = collect($group)->avg('jumlah_curas'); @@ -266,7 +269,7 @@ public function SSEElbowCuras() }); } } - + // Hitung SSE untuk k saat ini $sse = 0; foreach ($clustered as $key => $group) { @@ -275,20 +278,21 @@ public function SSEElbowCuras() $sse += pow($item->jumlah_curas - $centroidVal, 2); } } - + $elbowData[] = [ 'k' => $k, - 'sse' => $sse + 'sse' => $sse, + 'centroid_awal' => $centroidAwal ]; } - + // Simpan ke file file_put_contents( storage_path('app/public/sse_elbow_curas.json'), json_encode($elbowData, JSON_PRETTY_PRINT) ); - } + public function SSEElbowCuranmor() { @@ -296,40 +300,41 @@ public function SSEElbowCuranmor() $maxK = 10; $maxIterasi = 100; $elbowData = []; - + for ($k = 1; $k <= $maxK; $k++) { - - srand(time()); // Inisialisasi centroid awal secara acak $centroids = $data->unique('jumlah_curanmor')->shuffle()->take($k)->values()->map(function ($item) { return ['jumlah_curanmor' => $item->jumlah_curanmor]; }); - + + // Simpan centroid awal sebagai array angka + $centroidAwal = $centroids->pluck('jumlah_curanmor')->toArray(); + $prevAssignment = []; - + for ($iter = 0; $iter < $maxIterasi; $iter++) { $clustered = []; $currentAssignment = []; - + foreach ($data as $item) { $jarak = []; - + foreach ($centroids as $idx => $centroid) { $dist = abs($item->jumlah_curanmor - $centroid['jumlah_curanmor']); $jarak[$idx] = $dist; } - + $minIndex = array_keys($jarak, min($jarak))[0]; $clustered[$minIndex][] = $item; $currentAssignment[$item->id] = $minIndex; } - + if ($currentAssignment === $prevAssignment) { break; } - + $prevAssignment = $currentAssignment; - + // Update centroid foreach ($clustered as $key => $group) { $avg = collect($group)->avg('jumlah_curanmor'); @@ -340,7 +345,7 @@ public function SSEElbowCuranmor() }); } } - + // Hitung SSE untuk k saat ini $sse = 0; foreach ($clustered as $key => $group) { @@ -349,19 +354,19 @@ public function SSEElbowCuranmor() $sse += pow($item->jumlah_curanmor - $centroidVal, 2); } } - + $elbowData[] = [ 'k' => $k, - 'sse' => $sse + 'sse' => $sse, + 'centroid_awal' => $centroidAwal ]; } - + // Simpan ke file file_put_contents( storage_path('app/public/sse_elbow_curanmor.json'), json_encode($elbowData, JSON_PRETTY_PRINT) ); - } } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 3ea5911..c031bcf 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -25,11 +25,9 @@ public function run(): void CuranmorSeeder::class, ]); - $serviceKMeansCuras = new KMeansService(); - $hasilKMeansCuras = $serviceKMeansCuras->SSEElbowCuras(); - - $serviceKMeansCuras = new KMeansService(); - $hasilKMeansCuras = $serviceKMeansCuras->SSEElbowCuranmor(); + $serviceKMeans = new KMeansService(); + $serviceKMeans->SSEElbowCuranmor(); + $serviceKMeans->SSEElbowCuras(); $serviceKMeansCuras = new KMeansService(); $hasilKMeansCuras = $serviceKMeansCuras->hitungKMeansCuras(); diff --git a/public/assets/js/chart-custom.js b/public/assets/js/chart-custom.js index d84d92c..647d587 100644 --- a/public/assets/js/chart-custom.js +++ b/public/assets/js/chart-custom.js @@ -5193,136 +5193,64 @@ if (jQuery("#editor").length) { apexChartUpdate(chart, e.detail) }) } - if(jQuery('#layout1-chart-2').length){ + + if (jQuery('#layout1-chart-2').length) { am4core.ready(function() { - - // Themes begin - am4core.useTheme(am4themes_animated); - // Themes end - - // Create chart instance - var chart = am4core.create("layout1-chart-2", am4charts.XYChart); - chart.colors.list = [ - am4core.color("#32BDEA"), - am4core.color("#32BDEA"), - am4core.color("#32BDEA"), - am4core.color("#32BDEA"), - am4core.color("#32BDEA"), - am4core.color("#32BDEA"), - am4core.color("#32BDEA"), - am4core.color("#32BDEA"), - am4core.color("#32BDEA") - ]; - chart.scrollbarX = new am4core.Scrollbar(); - - // Add data - chart.data = [{ - "country": "Jan", - "visits": 3025 - }, { - "country": "Feb", - "visits": 1882 - }, { - "country": "Mar", - "visits": 1809 - }, { - "country": "Apr", - "visits": 1322 - }, { - "country": "May", - "visits": 1122 - }, { - "country": "Jun", - "visits": 1114 - }, { - "country": "Jul", - "visits": 984 - }, { - "country": "Aug", - "visits": 711 - }]; - - prepareParetoData(); - - function prepareParetoData(){ - var total = 0; - - for(var i = 0; i < chart.data.length; i++){ - var value = chart.data[i].visits; - total += value; - } - - var sum = 0; - for(var i = 0; i < chart.data.length; i++){ - var value = chart.data[i].visits; - sum += value; - chart.data[i].pareto = sum / total * 100; - } - } - - // Create axes - var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis()); - categoryAxis.dataFields.category = "country"; - categoryAxis.renderer.grid.template.location = 0; - categoryAxis.renderer.minGridDistance = 60; - categoryAxis.tooltip.disabled = true; - - var valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); - valueAxis.renderer.minWidth = 50; - valueAxis.min = 0; - valueAxis.cursorTooltipEnabled = false; - - // Create series - var series = chart.series.push(new am4charts.ColumnSeries()); - series.sequencedInterpolation = true; - series.dataFields.valueY = "visits"; - series.dataFields.categoryX = "country"; - series.tooltipText = "[{categoryX}: bold]{valueY}[/]"; - series.columns.template.strokeWidth = 0; - - series.tooltip.pointerOrientation = "vertical"; - - series.columns.template.column.cornerRadiusTopLeft = 10; - series.columns.template.column.cornerRadiusTopRight = 10; - series.columns.template.column.fillOpacity = 0.8; - - // on hover, make corner radiuses bigger - var hoverState = series.columns.template.column.states.create("hover"); - hoverState.properties.cornerRadiusTopLeft = 0; - hoverState.properties.cornerRadiusTopRight = 0; - hoverState.properties.fillOpacity = 1; - - series.columns.template.adapter.add("fill", function(fill, target) { - return chart.colors.getIndex(target.dataItem.index); - }) - - - var paretoValueAxis = chart.yAxes.push(new am4charts.ValueAxis()); - paretoValueAxis.renderer.opposite = true; - paretoValueAxis.min = 0; - paretoValueAxis.max = 100; - paretoValueAxis.strictMinMax = true; - paretoValueAxis.renderer.grid.template.disabled = true; - paretoValueAxis.numberFormatter = new am4core.NumberFormatter(); - paretoValueAxis.numberFormatter.numberFormat = "#'%'" - paretoValueAxis.cursorTooltipEnabled = false; - - var paretoSeries = chart.series.push(new am4charts.LineSeries()) - paretoSeries.dataFields.valueY = "pareto"; - paretoSeries.dataFields.categoryX = "country"; - paretoSeries.yAxis = paretoValueAxis; - paretoSeries.tooltipText = "pareto: {valueY.formatNumber('#.0')}%[/]"; - paretoSeries.bullets.push(new am4charts.CircleBullet()); - paretoSeries.strokeWidth = 2; - paretoSeries.stroke = new am4core.InterfaceColorSet().getFor("alternativeBackground"); - paretoSeries.strokeOpacity = 0.5; - - // Cursor - chart.cursor = new am4charts.XYCursor(); - chart.cursor.behavior = "panX"; - - }); // end am4core.ready() + // Theme + am4core.useTheme(am4themes_animated); + + // Chart instance + var chart = am4core.create("layout1-chart-2", am4charts.XYChart); + + // Load data via Ajax + fetch("/storage/sse_elbow_curanmor.json") + .then(response => response.json()) + .then(data => { + // Format data untuk chart + chart.data = data.map(item => ({ + k: item.k, + sse: item.sse, + centroid_awal: item.centroid_awal.join(", ") // Menggabungkan nilai centroid_awal menjadi string + })); + + // X Axis (kategori K) + let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis()); + categoryAxis.dataFields.category = "k"; + categoryAxis.renderer.grid.template.location = 0; + categoryAxis.renderer.minGridDistance = 30; + categoryAxis.title.text = "Jumlah Klaster (K)"; + + // Y Axis (nilai SSE) + let valueAxis = chart.yAxes.push(new am4charts.ValueAxis()); + valueAxis.title.text = "Nilai SSE"; + + // Line Series + let lineSeries = chart.series.push(new am4charts.LineSeries()); + lineSeries.dataFields.valueY = "sse"; + lineSeries.dataFields.categoryX = "k"; + lineSeries.name = "SSE"; + lineSeries.strokeWidth = 2; + lineSeries.tooltipText = "K={categoryX}\nSSE={valueY}\nCentroid Awal: {centroid_awal}"; + lineSeries.tensionX = 1; // untuk garis agak lengkung (opsional) + + // Bullets pada titik data + let bullet = lineSeries.bullets.push(new am4charts.CircleBullet()); + bullet.circle.radius = 4; + + // Cursor + chart.cursor = new am4charts.XYCursor(); + chart.cursor.behavior = "panX"; + chart.cursor.lineX.disabled = false; + chart.cursor.lineY.disabled = false; + + // Scrollbar (opsional) + chart.scrollbarX = new am4core.Scrollbar(); + }); + }); } + + + if (jQuery("#layout1-chart-3").length) { options = { series: [{ diff --git a/resources/views/admin/dashboardAdmin.blade.php b/resources/views/admin/dashboardAdmin.blade.php index a3ae6c3..cf96ac3 100644 --- a/resources/views/admin/dashboardAdmin.blade.php +++ b/resources/views/admin/dashboardAdmin.blade.php @@ -103,6 +103,31 @@ +
+
+
+
+

Revenue Vs Cost

+
+
+ +
+
+
+
+
+
+