284 lines
10 KiB
HTML
284 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<base target="_top">
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Pontianak Dashboard - Satellite Mode</title>
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
|
|
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
|
|
<style>
|
|
:root {
|
|
--glass-bg: rgba(15, 23, 42, 0.8);
|
|
--glass-border: rgba(255, 255, 255, 0.1);
|
|
--accent-blue: #38bdf8;
|
|
--accent-orange: #fb923c;
|
|
}
|
|
|
|
html, body { height: 100%; margin: 0; padding: 0; font-family: 'Inter', sans-serif; background: #020617; color: white; }
|
|
#map { width: 100%; height: 100vh; }
|
|
|
|
/* Sidebar Statistik */
|
|
.stats-sidebar {
|
|
position: absolute;
|
|
left: 20px;
|
|
top: 20px;
|
|
bottom: 20px;
|
|
width: 320px;
|
|
z-index: 1000;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
pointer-events: none; /* Supaya klik bisa tembus ke peta kecuali di dalam elemen ini */
|
|
}
|
|
|
|
.stats-card {
|
|
background: var(--glass-bg);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
border: 1px solid var(--glass-border);
|
|
border-radius: 16px;
|
|
padding: 20px;
|
|
pointer-events: auto;
|
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.stats-card h2 { margin: 0 0 5px; font-size: 18px; font-weight: 700; color: white; }
|
|
.stats-card p { margin: 0; font-size: 13px; color: #94a3b8; }
|
|
|
|
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 15px; }
|
|
.stat-item { background: rgba(255,255,255,0.05); padding: 12px; border-radius: 10px; }
|
|
.stat-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; color: #64748b; }
|
|
.stat-value { font-size: 15px; font-weight: 600; margin-top: 2px; }
|
|
|
|
/* Peringkat List */
|
|
.rank-list { margin-top: 15px; list-style: none; padding: 0; }
|
|
.rank-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 13px; }
|
|
.rank-item:last-child { border: none; }
|
|
.rank-name { color: #cbd5e1; }
|
|
.rank-val { font-weight: 600; color: var(--accent-blue); }
|
|
|
|
/* Info Hover Panel */
|
|
#hover-info { border-left: 4px solid var(--accent-blue); }
|
|
.hover-name { font-size: 20px; font-weight: 700; color: white; margin-bottom: 4px; }
|
|
.hover-data { font-size: 16px; color: var(--accent-blue); font-weight: 600; }
|
|
|
|
/* Control buttons */
|
|
.layer-toggle {
|
|
position: absolute;
|
|
right: 20px;
|
|
top: 20px;
|
|
background: var(--glass-bg);
|
|
backdrop-filter: blur(8px);
|
|
padding: 5px;
|
|
border-radius: 12px;
|
|
border: 1px solid var(--glass-border);
|
|
display: flex;
|
|
gap: 5px;
|
|
z-index: 1000;
|
|
}
|
|
.btn-mode {
|
|
background: transparent;
|
|
border: none;
|
|
color: #94a3b8;
|
|
padding: 8px 15px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
font-size: 12px;
|
|
transition: 0.2s;
|
|
}
|
|
.btn-mode.active { background: white; color: #0f172a; }
|
|
|
|
/* Legend */
|
|
.legend {
|
|
padding: 15px;
|
|
color: white;
|
|
line-height: 22px;
|
|
font-size: 12px;
|
|
}
|
|
.legend i { width: 14px; height: 14px; float: left; margin-right: 8px; margin-top: 4px; border-radius: 3px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="stats-sidebar">
|
|
<div class="stats-card">
|
|
<h2>Kota Pontianak</h2>
|
|
<p>Ringkasan Data Wilayah</p>
|
|
<div class="stat-grid">
|
|
<div class="stat-item">
|
|
<div class="stat-label">Total Populasi</div>
|
|
<div class="stat-value" id="total-pop">-</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-label">Avg. Kepadatan</div>
|
|
<div class="stat-value" id="avg-dens">-</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top: 20px;">
|
|
<div class="stat-label">Peringkat Kecamatan</div>
|
|
<div id="rank-container" class="rank-list"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-card" id="hover-info">
|
|
<div id="hover-content">
|
|
<div class="stat-label">Detail Kecamatan</div>
|
|
<div style="color:#64748b; margin-top:10px; font-style:italic;">Arahkan kursor ke peta...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="layer-toggle">
|
|
<button class="btn-mode active" id="btn-density" onclick="setMode('density')">Kepadatan</button>
|
|
<button class="btn-mode" id="btn-population" onclick="setMode('population')">Populasi</button>
|
|
</div>
|
|
|
|
<div id="map"></div>
|
|
|
|
<script type="text/javascript" src="pontianak-state.js"></script>
|
|
|
|
<script>
|
|
// ── Inisialisasi Peta ───────────────────────────────────────────────────
|
|
const map = L.map('map', { zoomControl: false }).setView([-0.023868, 109.338493], 12);
|
|
|
|
// LAYER SATELIT (Esri World Imagery)
|
|
const satellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
|
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EBP, and the GIS User Community'
|
|
}).addTo(map);
|
|
|
|
// Tambahan Label Jalan (Transparan) agar lokasi mudah dikenali di satelit
|
|
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png', {
|
|
pane: 'shadowPane', // Menaruh label di atas layer data
|
|
pointerEvents: 'none'
|
|
}).addTo(map);
|
|
|
|
let currentMode = 'density';
|
|
|
|
const modeConfig = {
|
|
density: {
|
|
unit: 'jiwa/km²',
|
|
grades: [0, 1000, 3000, 5000, 7000, 10000],
|
|
getColor: d => d > 10000 ? '#7f1d1d' : d > 7000 ? '#b91c1c' : d > 5000 ? '#ef4444' : d > 3000 ? '#f87171' : d > 1000 ? '#fca5a5' : '#fee2e2'
|
|
},
|
|
population: {
|
|
unit: 'jiwa',
|
|
grades: [0, 40000, 70000, 100000, 130000],
|
|
getColor: d => d > 130000 ? '#0c4a6e' : d > 100000 ? '#0284c7' : d > 70000 ? '#38bdf8' : d > 40000 ? '#bae6fd' : '#f0f9ff'
|
|
}
|
|
};
|
|
|
|
// ── Kalkulasi Statistik Global ──────────────────────────────────────────
|
|
function calculateStats() {
|
|
const features = statesData.features;
|
|
const totalPop = features.reduce((sum, f) => sum + f.properties.population, 0);
|
|
const avgDens = features.reduce((sum, f) => sum + f.properties.density, 0) / features.length;
|
|
|
|
document.getElementById('total-pop').innerText = totalPop.toLocaleString('id-ID');
|
|
document.getElementById('avg-dens').innerText = Math.round(avgDens).toLocaleString('id-ID');
|
|
|
|
updateRanking();
|
|
}
|
|
|
|
function updateRanking() {
|
|
const container = document.getElementById('rank-container');
|
|
const sorted = [...statesData.features].sort((a, b) =>
|
|
b.properties[currentMode] - a.properties[currentMode]
|
|
).slice(0, 5); // Ambil Top 5
|
|
|
|
container.innerHTML = sorted.map(f => `
|
|
<div class="rank-item">
|
|
<span class="rank-name">${f.properties.name}</span>
|
|
<span class="rank-val">${f.properties[currentMode].toLocaleString('id-ID')}</span>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// ── Map Events ──────────────────────────────────────────────────────────
|
|
function getStyle(feature) {
|
|
return {
|
|
weight: 1.5,
|
|
opacity: 1,
|
|
color: 'rgba(255,255,255,0.4)',
|
|
fillOpacity: 0.7,
|
|
fillColor: modeConfig[currentMode].getColor(feature.properties[currentMode])
|
|
};
|
|
}
|
|
|
|
function highlightFeature(e) {
|
|
const layer = e.target;
|
|
const props = layer.feature.properties;
|
|
|
|
layer.setStyle({ weight: 3, color: 'white', fillOpacity: 0.9 });
|
|
layer.bringToFront();
|
|
|
|
// Update panel kiri bawah
|
|
const accent = currentMode === 'density' ? 'var(--accent-orange)' : 'var(--accent-blue)';
|
|
document.getElementById('hover-content').innerHTML = `
|
|
<div class="stat-label">Detail Kecamatan</div>
|
|
<div class="hover-name">${props.name}</div>
|
|
<div class="hover-data" style="color: ${accent}">${props[currentMode].toLocaleString('id-ID')} ${modeConfig[currentMode].unit}</div>
|
|
<div style="font-size:12px; color:#94a3b8; margin-top:5px;">
|
|
${currentMode === 'density' ? 'Populasi: ' + props.population.toLocaleString('id-ID') : 'Kepadatan: ' + props.density.toLocaleString('id-ID')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function resetHighlight(e) {
|
|
geojson.resetStyle(e.target);
|
|
document.getElementById('hover-content').innerHTML = `
|
|
<div class="stat-label">Detail Kecamatan</div>
|
|
<div style="color:#64748b; margin-top:10px; font-style:italic;">Arahkan kursor ke peta...</div>
|
|
`;
|
|
}
|
|
|
|
const geojson = L.geoJson(statesData, {
|
|
style: getStyle,
|
|
onEachFeature: (f, l) => {
|
|
l.on({ mouseover: highlightFeature, mouseout: resetHighlight, click: (e) => map.fitBounds(e.target.getBounds()) });
|
|
}
|
|
}).addTo(map);
|
|
|
|
// ── Legend ──────────────────────────────────────────────────────────────
|
|
const legend = L.control({ position: 'bottomright' });
|
|
legend.onAdd = function () {
|
|
this._div = L.DomUtil.create('div', 'legend stats-card');
|
|
this.update();
|
|
return this._div;
|
|
};
|
|
legend.update = function () {
|
|
const cfg = modeConfig[currentMode];
|
|
let html = `<div class="stat-label" style="margin-bottom:10px">${cfg.unit}</div>`;
|
|
for (let i = 0; i < cfg.grades.length; i++) {
|
|
html += `<i style="background:${cfg.getColor(cfg.grades[i] + 1)}"></i> ` +
|
|
cfg.grades[i].toLocaleString('id-ID') + (cfg.grades[i+1] ? `–${cfg.grades[i+1].toLocaleString('id-ID')}<br>` : '+');
|
|
}
|
|
this._div.innerHTML = html;
|
|
};
|
|
legend.addTo(map);
|
|
|
|
// ── Switch Mode ─────────────────────────────────────────────────────────
|
|
function setMode(mode) {
|
|
currentMode = mode;
|
|
document.getElementById('btn-density').classList.toggle('active', mode === 'density');
|
|
document.getElementById('btn-population').classList.toggle('active', mode === 'population');
|
|
|
|
geojson.setStyle(getStyle);
|
|
legend.update();
|
|
updateRanking();
|
|
}
|
|
|
|
// Jalankan kalkulasi pertama kali
|
|
calculateStats();
|
|
|
|
</script>
|
|
</body>
|
|
</html> |