{"id":35,"date":"2025-08-19T06:03:14","date_gmt":"2025-08-18T22:03:14","guid":{"rendered":"https:\/\/itpmelaka.my\/mdic\/services\/"},"modified":"2026-09-17T14:13:49","modified_gmt":"2026-09-17T06:13:49","slug":"services","status":"publish","type":"page","link":"http:\/\/itpmelaka.my\/mdic\/services\/","title":{"rendered":"Data Terbuka"},"content":{"rendered":"\n\n\n<div style=\"text-align: center; margin: 40px 0 30px 0;\">\n  <h2 style=\"\n    font-size: 32px;\n    font-weight: 700;\n    color: #1f3c88;\n    margin-bottom: 10px;\n    letter-spacing: 0.5px;\n  \">\n    Dashboard Kategori Demografi\n  <\/h2>\n\n  <div style=\"\n    width: 80px;\n    height: 4px;\n    background-color: #1f3c88;\n    margin: 0 auto;\n    border-radius: 4px;\n  \"><\/div>\n<\/div>\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#mypop-wrap, #mypop-wrap * { box-sizing: border-box; }\n\n#mypop-wrap {\n  --bg:#f8fafc; --card:#ffffff; --soft:#eff6ff; --border:#dbeafe;\n  --text:#0f172a; --muted:#64748b; --accent:#2563eb;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%; padding:28px; background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border); border-radius:22px; font-family:Arial,sans-serif; color:var(--text);\n}\n\n#mypop-wrap .dash{max-width:1180px;margin:0 auto;}\n#mypop-wrap .hdr,#mypop-wrap .cc,#mypop-wrap .sc,#mypop-wrap .sbox{background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow);}\n#mypop-wrap .hdr{border-radius:20px;padding:26px;margin-bottom:20px;}\n#mypop-wrap .eyebrow{color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px;}\n#mypop-wrap h2{margin:0;font-size:clamp(26px,4vw,44px);line-height:1.1;font-weight:800;}\n#mypop-wrap .hdesc{color:var(--muted);margin-top:10px;line-height:1.6;}\n\n#mypop-wrap .filter-bar{\n  display:grid;grid-template-columns:repeat(5,1fr);gap:14px;background:#fff;border:1px solid var(--border);\n  border-radius:16px;padding:18px;margin-bottom:20px;box-shadow:var(--shadow);\n}\n\n#mypop-wrap .cg{display:flex;flex-direction:column;gap:7px;}\n#mypop-wrap label{font-size:11px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;}\n\n#mypop-wrap select{\n  width:100%;min-height:44px;padding:10px 40px 10px 12px;border:1.5px solid #bfdbfe;border-radius:10px;\n  background-color:#fff;color:var(--text);font-size:14px;font-weight:600;outline:none;appearance:none;cursor:pointer;\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n  background-repeat:no-repeat;background-position:right 12px center;\n}\n\n#mypop-wrap select:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(37,99,235,.12);}\n\n#mypop-wrap .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;}\n#mypop-wrap .sc{border-radius:16px;padding:18px;border-left:4px solid var(--accent);}\n#mypop-wrap .sl{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;margin-bottom:8px;}\n#mypop-wrap .sv{font-size:25px;font-weight:800;}\n#mypop-wrap .su{color:var(--muted);font-size:12px;margin-top:4px;}\n\n#mypop-wrap .tab-bar{display:flex;justify-content:space-between;gap:10px;background:var(--soft);border:1px solid var(--border);border-radius:999px;padding:8px;margin-bottom:18px;overflow-x:auto;}\n#mypop-wrap .tbtn{flex:1;border:0;background:transparent;padding:12px 18px;border-radius:999px;color:var(--muted);font-weight:800;cursor:pointer;white-space:nowrap;}\n#mypop-wrap .tbtn.active{background:#fff;color:var(--accent);box-shadow:0 5px 14px rgba(15,23,42,.08);}\n\n#mypop-wrap .cc{border-radius:20px;padding:24px;margin-bottom:18px;}\n#mypop-wrap .ct{font-size:18px;font-weight:800;margin-bottom:5px;}\n#mypop-wrap .cs{color:var(--muted);font-size:13px;margin-bottom:18px;}\n#mypop-wrap .two{display:grid;grid-template-columns:1fr 1fr;gap:18px;}\n#mypop-wrap .chart-box{position:relative;height:340px;}\n#mypop-wrap .chart-box-sm{position:relative;height:310px;}\n#mypop-wrap .sbox{border-radius:20px;padding:60px 24px;text-align:center;color:var(--muted);}\n#mypop-wrap .spin{width:38px;height:38px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;margin:0 auto 16px;}\n\n@keyframes spin{to{transform:rotate(360deg);}}\n\n#mypop-wrap .fn{text-align:center;color:var(--muted);font-size:12px;margin-top:12px;}\n#mypop-wrap .fn a{color:var(--accent);font-weight:700;text-decoration:none;}\n\n@media(max-width:1024px){\n  #mypop-wrap .filter-bar{grid-template-columns:repeat(2,1fr);}\n  #mypop-wrap .stats{grid-template-columns:repeat(2,1fr);}\n  #mypop-wrap .two{grid-template-columns:1fr;}\n}\n\n@media(max-width:600px){\n  #mypop-wrap{padding:14px;border-radius:16px;}\n  #mypop-wrap .filter-bar,#mypop-wrap .stats{grid-template-columns:1fr;}\n  #mypop-wrap .tab-bar{justify-content:flex-start;}\n  #mypop-wrap .tbtn{flex:0 0 auto;}\n  #mypop-wrap .chart-box,#mypop-wrap .chart-box-sm{height:280px;}\n}\n<\/style>\n\n<div id=\"mypop-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Melaka District Population<\/div>\n      <h2>Melaka District Population Dashboard<\/h2>\n      <p class=\"hdesc\">Population visualization for Melaka by district, sex, age group, ethnicity, and year.<\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n      <div class=\"cg\">\n        <label>District<\/label>\n        <select id=\"mypop-district\">\n          <option value=\"all\">All<\/option>\n          <option value=\"Alor Gajah\">Alor Gajah<\/option>\n          <option value=\"Jasin\">Jasin<\/option>\n          <option value=\"Melaka Tengah\">Melaka Tengah<\/option>\n        <\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Sex<\/label>\n        <select id=\"mypop-sex\">\n          <option value=\"both\">Both Sexes<\/option>\n          <option value=\"male\">Male<\/option>\n          <option value=\"female\">Female<\/option>\n        <\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Age Group<\/label>\n        <select id=\"mypop-age\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Ethnicity<\/label>\n        <select id=\"mypop-eth\">\n          <option value=\"overall\">All Ethnicities<\/option>\n          <option value=\"bumi_malay\">Malay<\/option>\n          <option value=\"bumi_other\">Other Bumiputera<\/option>\n          <option value=\"chinese\">Chinese<\/option>\n          <option value=\"indian\">Indian<\/option>\n          <option value=\"other_citizen\">Other Citizen<\/option>\n          <option value=\"other_noncitizen\">Non-Citizen<\/option>\n        <\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Year<\/label>\n        <select id=\"mypop-year\"><\/select>\n      <\/div>\n    <\/div>\n\n    <div class=\"stats\" id=\"mypop-stats\" style=\"display:none\">\n      <div class=\"sc\"><div class=\"sl\">Latest Year<\/div><div class=\"sv\" id=\"stat-year\">\u2014<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">Population<\/div><div class=\"sv\" id=\"stat-pop\">\u2014<\/div><div class=\"su\">thousand people<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">Peak Year<\/div><div class=\"sv\" id=\"stat-peak-year\">\u2014<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">Peak Population<\/div><div class=\"sv\" id=\"stat-peak-pop\">\u2014<\/div><div class=\"su\">thousand people<\/div><\/div>\n    <\/div>\n\n    <div class=\"sbox\" id=\"mypop-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Fetching data&#8230;<\/strong>\n      <div>Loading Melaka district population records.<\/div>\n    <\/div>\n\n    <div id=\"mypop-area\" style=\"display:none\">\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Population Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"district\">District Comparison<\/button>\n        <button class=\"tbtn\" data-tab=\"ethnicity\">Ethnicity Share<\/button>\n      <\/div>\n\n      <div id=\"tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Population (Thousands) Trend Over Time<\/div>\n          <div class=\"cs\" id=\"trend-sub\">Annual population trend based on selected filters. Year filter does not affect this chart.<\/div>\n          <div class=\"chart-box\"><canvas id=\"chart-trend\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"tab-district\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Population (Thousands) by District<\/div>\n          <div class=\"cs\" id=\"district-sub\">District comparison based on selected year, sex, age, and ethnicity.<\/div>\n          <div class=\"chart-box\"><canvas id=\"chart-district\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"tab-ethnicity\" style=\"display:none\">\n        <div class=\"two\">\n          <div class=\"cc\">\n            <div class=\"ct\">Ethnicity Share<\/div>\n            <div class=\"cs\" id=\"eth-sub\">Ethnicity breakdown based on selected year, district, sex, and age.<\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"chart-eth-pie\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Ethnicity Trend<\/div>\n            <div class=\"cs\">Population trend by ethnicity group.<\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"chart-eth-line\"><\/canvas><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  function init(){\n    if(typeof Chart === 'undefined'){ setTimeout(init,500); return; }\n\n    var API_LIMIT = 500000;\n    var melakaRows = [];\n    var charts = {};\n    var districts = ['Alor Gajah','Jasin','Melaka Tengah'];\n\n    var ETH_KEYS = ['bumi_malay','bumi_other','chinese','indian','other_citizen','other_noncitizen'];\n\n    var ETH_LABELS = {\n      overall:'All Ethnicities',\n      bumi_malay:'Malay',\n      bumi_other:'Other Bumiputera',\n      chinese:'Chinese',\n      indian:'Indian',\n      other_citizen:'Other Citizen',\n      other_noncitizen:'Non-Citizen'\n    };\n\n    var ETH_COLORS = {\n      bumi_malay:'#2563eb',\n      bumi_other:'#60a5fa',\n      chinese:'#f97316',\n      indian:'#f59e0b',\n      other_citizen:'#6366f1',\n      other_noncitizen:'#ec4899'\n    };\n\n    Chart.defaults.color = '#64748b';\n    Chart.defaults.borderColor = '#dbeafe';\n    Chart.defaults.font.family = 'Arial, sans-serif';\n\n    function el(id){ return document.getElementById(id); }\n    function year(row){ return parseInt(String(row.date || '').slice(0,4),10); }\n    function pop(row){ return parseFloat(row.population) || 0; }\n    function formatPop(value){ return Number(value).toLocaleString('en-MY',{maximumFractionDigits:1}); }\n\n    function normalizeSex(value){\n      value = String(value || '').trim().toLowerCase();\n      if(value === 'overall') return 'both';\n      return value;\n    }\n\n    function sexMatches(rowSex, selectedSex){\n      rowSex = normalizeSex(rowSex);\n      selectedSex = normalizeSex(selectedSex);\n\n      if(selectedSex === 'both'){\n        return rowSex === 'both';\n      }\n\n      return rowSex === selectedSex;\n    }\n\n    function labelText(value){\n      if(value === 'all') return 'All Districts';\n      if(value === 'overall') return 'Overall';\n      if(value === 'both') return 'Both Sexes';\n      if(ETH_LABELS[value]) return ETH_LABELS[value];\n      return value;\n    }\n\n    function destroyChart(name){\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    async function fetchAllData(){\n      var output = [];\n      var offset = 0;\n\n      while(true){\n        var url = 'https:\/\/api.data.gov.my\/data-catalogue?id=population_district&limit=' + API_LIMIT + '&offset=' + offset;\n        var response = await fetch(url);\n\n        if(!response.ok) throw new Error('HTTP ' + response.status);\n\n        var data = await response.json();\n\n        if(!Array.isArray(data) || data.length === 0) break;\n\n        output = output.concat(data);\n\n        if(data.length < API_LIMIT) break;\n\n        offset += API_LIMIT;\n      }\n\n      return output;\n    }\n\n    function getSelected(){\n      return {\n        district: el('mypop-district').value,\n        sex: el('mypop-sex').value,\n        age: el('mypop-age').value,\n        ethnicity: el('mypop-eth').value,\n        year: parseInt(el('mypop-year').value, 10)\n      };\n    }\n\n    function matchBase(row, selected){\n      var districtMatch = selected.district === 'all' || row.district === selected.district;\n\n      return districtMatch &#038;&#038;\n        sexMatches(row.sex, selected.sex) &#038;&#038;\n        row.age === selected.age &#038;&#038;\n        row.ethnicity === selected.ethnicity;\n    }\n\n    function sumByYear(selected){\n      var map = {};\n\n      melakaRows.forEach(function(row){\n        if(matchBase(row, selected)){\n          var y = year(row);\n          map[y] = (map[y] || 0) + pop(row);\n        }\n      });\n\n      return Object.keys(map)\n        .sort(function(a,b){ return Number(a)-Number(b); })\n        .map(function(y){\n          return {year:Number(y), population:map[y]};\n        });\n    }\n\n    function populateAgeFilter(){\n      var ages = Array.from(new Set(melakaRows.map(function(row){ return row.age; }))).sort(function(a,b){\n        if(a === 'overall') return -1;\n        if(b === 'overall') return 1;\n\n        var ai = parseInt(a,10);\n        var bi = parseInt(b,10);\n\n        if(!isNaN(ai) &#038;&#038; !isNaN(bi)) return ai-bi;\n        if(a === '85+') return 1;\n        if(b === '85+') return -1;\n\n        return String(a).localeCompare(String(b));\n      });\n\n      el('mypop-age').innerHTML = ages.map(function(ageValue){\n        return '<option value=\"' + ageValue + '\">' + (ageValue === 'overall' ? 'Overall (All Ages)' : ageValue) + '<\/option>';\n      }).join('');\n    }\n\n    function populateYearFilter(){\n      var years = Array.from(new Set(melakaRows.map(year)))\n        .filter(function(y){ return !isNaN(y); })\n        .sort(function(a,b){ return b-a; });\n\n      el('mypop-year').innerHTML = years.map(function(y){\n        return '<option value=\"' + y + '\">' + y + '<\/option>';\n      }).join('');\n\n      if(years.length){\n        el('mypop-year').value = years[0];\n      }\n    }\n\n    function updateInfoCards(){\n      var selected = getSelected();\n      var data = sumByYear(selected);\n\n      if(!data.length){\n        el('stat-year').textContent = '\u2014';\n        el('stat-pop').textContent = '\u2014';\n        el('stat-peak-year').textContent = '\u2014';\n        el('stat-peak-pop').textContent = '\u2014';\n        el('mypop-stats').style.display = '';\n        return;\n      }\n\n      var latest = data[data.length - 1];\n      var peak = data.reduce(function(a,b){\n        return a.population >= b.population ? a : b;\n      });\n\n      el('stat-year').textContent = latest.year;\n      el('stat-pop').textContent = formatPop(latest.population);\n      el('stat-peak-year').textContent = peak.year;\n      el('stat-peak-pop').textContent = formatPop(peak.population);\n      el('mypop-stats').style.display = '';\n    }\n\n    function buildTrendChart(){\n      var selected = getSelected();\n      var data = sumByYear(selected);\n\n      el('trend-sub').textContent =\n        labelText(selected.district) + ' \u00b7 ' +\n        labelText(selected.sex) + ' \u00b7 Age: ' +\n        labelText(selected.age) + ' \u00b7 Ethnicity: ' +\n        labelText(selected.ethnicity);\n\n      destroyChart('trend');\n\n      charts.trend = new Chart(el('chart-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:data.map(function(d){ return d.year; }),\n          datasets:[{\n            label:\"Population ('000)\",\n            data:data.map(function(d){ return d.population; }),\n            borderColor:'#2563eb',\n            backgroundColor:'rgba(37,99,235,.15)',\n            fill:true,\n            tension:.35,\n            pointRadius:data.length > 25 ? 2 : 4,\n            borderWidth:2.5\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          interaction:{mode:'index',intersect:false},\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Population: ' + formatPop(c.parsed.y) + 'k';\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'},ticks:{maxTicksLimit:10}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return v.toLocaleString();}}}\n          }\n        }\n      });\n    }\n\n    function buildDistrictChart(){\n      var selected = getSelected();\n      var selectedYear = selected.year;\n\n      var data = districts.map(function(district){\n        var total = 0;\n\n        melakaRows.forEach(function(row){\n          if(\n            row.district === district &&\n            sexMatches(row.sex, selected.sex) &&\n            row.age === selected.age &&\n            row.ethnicity === selected.ethnicity &&\n            year(row) === selectedYear\n          ){\n            total += pop(row);\n          }\n        });\n\n        return {district:district, population:total};\n      });\n\n      el('district-sub').textContent =\n        selectedYear + ' \u00b7 All Districts \u00b7 ' +\n        labelText(selected.sex) + ' \u00b7 Age: ' +\n        labelText(selected.age) + ' \u00b7 Ethnicity: ' +\n        labelText(selected.ethnicity);\n\n      destroyChart('district');\n\n      charts.district = new Chart(el('chart-district').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){ return d.district; }),\n          datasets:[{\n            label:\"Population ('000)\",\n            data:data.map(function(d){ return d.population; }),\n            backgroundColor:'rgba(37,99,235,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Population: ' + formatPop(c.parsed.y) + 'k';\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{display:false}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return v.toLocaleString();}}}\n          }\n        }\n      });\n    }\n\n    function buildEthnicityCharts(){\n      var selected = getSelected();\n      var selectedYear = selected.year;\n      var ethList = ETH_KEYS;\n\n      var baseRows = melakaRows.filter(function(row){\n        var districtMatch = selected.district === 'all' || row.district === selected.district;\n\n        return districtMatch &&\n          sexMatches(row.sex, selected.sex) &&\n          row.age === selected.age;\n      });\n\n      var years = Array.from(new Set(baseRows.map(year))).sort(function(a,b){ return a-b; });\n\n      var pieData = ethList.map(function(eth){\n        var total = 0;\n\n        baseRows.forEach(function(row){\n          if(row.ethnicity === eth && year(row) === selectedYear){\n            total += pop(row);\n          }\n        });\n\n        return {key:eth,label:ETH_LABELS[eth],value:total};\n      }).filter(function(item){\n        return item.value > 0;\n      });\n\n      el('eth-sub').textContent =\n        selectedYear + ' \u00b7 ' +\n        labelText(selected.district) + ' \u00b7 ' +\n        labelText(selected.sex) + ' \u00b7 Age: ' +\n        labelText(selected.age) + ' \u00b7 All Ethnicities';\n\n      destroyChart('ethPie');\n\n      charts.ethPie = new Chart(el('chart-eth-pie').getContext('2d'),{\n        type:'doughnut',\n        data:{\n          labels:pieData.map(function(d){ return d.label; }),\n          datasets:[{\n            data:pieData.map(function(d){ return d.value; }),\n            backgroundColor:pieData.map(function(d){ return ETH_COLORS[d.key]; }),\n            borderColor:'#ffffff',\n            borderWidth:3\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{position:'bottom'},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' ' + c.label + ': ' + formatPop(c.parsed) + 'k';\n                }\n              }\n            }\n          }\n        }\n      });\n\n      var lineDatasets = ethList.map(function(eth){\n        return {\n          label:ETH_LABELS[eth],\n          data:years.map(function(y){\n            var total = 0;\n\n            baseRows.forEach(function(row){\n              if(row.ethnicity === eth && year(row) === y){\n                total += pop(row);\n              }\n            });\n\n            return total || null;\n          }),\n          borderColor:ETH_COLORS[eth],\n          backgroundColor:ETH_COLORS[eth] + '30',\n          tension:.35,\n          borderWidth:2,\n          pointRadius:0,\n          spanGaps:true\n        };\n      });\n\n      destroyChart('ethLine');\n\n      charts.ethLine = new Chart(el('chart-eth-line').getContext('2d'),{\n        type:'line',\n        data:{labels:years,datasets:lineDatasets},\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          interaction:{mode:'index',intersect:false},\n          plugins:{\n            legend:{position:'bottom',labels:{boxWidth:10,font:{size:11}}},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' ' + c.dataset.label + ': ' + formatPop(c.parsed.y) + 'k';\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'},ticks:{maxTicksLimit:8}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return v.toLocaleString();}}}\n          }\n        }\n      });\n    }\n\n    function activeTab(){\n      var active = document.querySelector('#mypop-wrap .tbtn.active');\n      return active ? active.getAttribute('data-tab') : 'trend';\n    }\n\n    function redraw(){\n      updateInfoCards();\n\n      if(activeTab() === 'trend') buildTrendChart();\n      if(activeTab() === 'district') buildDistrictChart();\n      if(activeTab() === 'ethnicity') buildEthnicityCharts();\n    }\n\n    document.querySelectorAll('#mypop-wrap .tbtn').forEach(function(btn){\n      btn.addEventListener('click',function(){\n        document.querySelectorAll('#mypop-wrap .tbtn').forEach(function(b){\n          b.classList.remove('active');\n        });\n\n        btn.classList.add('active');\n\n        el('tab-trend').style.display = 'none';\n        el('tab-district').style.display = 'none';\n        el('tab-ethnicity').style.display = 'none';\n\n        el('tab-' + btn.getAttribute('data-tab')).style.display = '';\n\n        redraw();\n      });\n    });\n\n    ['mypop-district','mypop-sex','mypop-age','mypop-eth','mypop-year'].forEach(function(id){\n      el(id).addEventListener('change',redraw);\n    });\n\n    fetchAllData()\n      .then(function(rows){\n        melakaRows = rows.filter(function(row){\n          return String(row.state || '').trim().toLowerCase() === 'melaka';\n        });\n\n        if(!melakaRows.length){\n          throw new Error('No Melaka records found after fetching all API pages.');\n        }\n\n        populateAgeFilter();\n        populateYearFilter();\n\n        el('mypop-loading').style.display = 'none';\n        el('mypop-area').style.display = '';\n\n        redraw();\n      })\n      .catch(function(error){\n        el('mypop-loading').innerHTML =\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">Failed to load data<\/div>' +\n          '<p style=\"font-size:13px;color:#64748b\">' + error.message + '<\/p>';\n      });\n  }\n\n  if(document.readyState === 'loading'){\n    document.addEventListener('DOMContentLoaded',init);\n  }else{\n    init();\n  }\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<div style=\"text-align: center; margin: 40px 0 30px 0;\">\n  <h2 style=\"\n    font-size: 32px;\n    font-weight: 700;\n    color: #1f3c88;\n    margin-bottom: 10px;\n    letter-spacing: 0.5px;\n  \">\n    Dashboard Kategori Isi Rumah\n  <\/h2>\n\n  <div style=\"\n    width: 80px;\n    height: 4px;\n    background-color: #1f3c88;\n    margin: 0 auto;\n    border-radius: 4px;\n  \"><\/div>\n<\/div>\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#hh-wrap, #hh-wrap * { box-sizing: border-box; }\n\n#hh-wrap {\n  --bg:#f8fafc; --card:#ffffff; --soft:#eff6ff; --border:#dbeafe;\n  --text:#0f172a; --muted:#64748b; --accent:#2563eb;\n  --orange:#f97316; --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%; padding:28px; background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border); border-radius:22px; font-family:Arial,sans-serif; color:var(--text);\n}\n\n#hh-wrap .dash{max-width:1180px;margin:0 auto;}\n#hh-wrap .hdr,#hh-wrap .cc,#hh-wrap .sc,#hh-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#hh-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#hh-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#hh-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#hh-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#hh-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(2,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#hh-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#hh-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#hh-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#hh-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#hh-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#hh-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#hh-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#hh-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#hh-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n}\n\n#hh-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#hh-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#hh-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#hh-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#hh-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#hh-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n}\n\n#hh-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#hh-wrap .chart-box{\n  position:relative;\n  height:350px;\n}\n\n#hh-wrap .chart-box-sm{\n  position:relative;\n  height:320px;\n}\n\n#hh-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#hh-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:hh-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes hh-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n  #hh-wrap .filter-bar,\n  #hh-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #hh-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n  #hh-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #hh-wrap .filter-bar,\n  #hh-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #hh-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #hh-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #hh-wrap .chart-box,\n  #hh-wrap .chart-box-sm{\n    height:290px;\n  }\n}\n<\/style>\n\n<div id=\"hh-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Isi Rumah &#038; Tempat Kediaman Melaka<\/div>\n      <h2>Papan Pemuka Isi Rumah dan Tempat Kediaman<\/h2>\n      <p class=\"hdesc\">\n        Visualisasi bilangan isi rumah dan tempat kediaman di Melaka.\n        Perbandingan dengan negeri-negeri lain turut disediakan.\n      <\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"hh-year-filter\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Ukuran<\/label>\n        <select id=\"hh-metric-filter\">\n          <option value=\"both\">Isi Rumah &#038; Tempat Kediaman<\/option>\n          <option value=\"households\">Isi Rumah<\/option>\n          <option value=\"living_quarters\">Tempat Kediaman<\/option>\n        <\/select>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"stats\" id=\"hh-stats\" style=\"display:none\">\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"hh-stat-year\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Isi Rumah<\/div>\n        <div class=\"sv\" id=\"hh-stat-households\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tempat Kediaman<\/div>\n        <div class=\"sv\" id=\"hh-stat-lq\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Perbezaan<\/div>\n        <div class=\"sv\" id=\"hh-stat-diff\">\u2014<\/div>\n        <div class=\"su\">tempat kediaman tolak isi rumah<\/div>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"sbox\" id=\"hh-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod isi rumah dan tempat kediaman.<\/div>\n    <\/div>\n\n    <div id=\"hh-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Perbandingan Negeri<\/button>\n        <button class=\"tbtn\" data-tab=\"share\">Isi Rumah vs Tempat Kediaman<\/button>\n      <\/div>\n\n      <div id=\"hh-tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Isi Rumah dan Tempat Kediaman Melaka<\/div>\n          <div class=\"cs\" id=\"hh-trend-sub\">\n            Trend tahunan bagi Melaka berdasarkan ukuran yang dipilih.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"hh-chart-trend\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"hh-tab-state\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"hh-state-sub\">\n            Perbandingan antara negeri berdasarkan tahun dan ukuran yang dipilih.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"hh-chart-state\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"hh-tab-share\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Isi Rumah vs Tempat Kediaman Melaka<\/div>\n            <div class=\"cs\" id=\"hh-share-sub\">\n              Perbandingan bagi Melaka berdasarkan tahun yang dipilih.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"hh-chart-share\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Jurang Melaka<\/div>\n            <div class=\"cs\">\n              Perbezaan antara bilangan tempat kediaman dan isi rumah di Melaka.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"hh-chart-gap\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=hh_profile_state&limit=300';\n\n    var FIXED_STATE = 'Melaka';\n    var allRows = [];\n    var charts = {};\n\n    Chart.defaults.color = '#64748b';\n    Chart.defaults.borderColor = '#dbeafe';\n    Chart.defaults.font.family = 'Arial, sans-serif';\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(row){\n      return parseInt(String(row.date || '').slice(0,4),10);\n    }\n\n    function num(v){\n      return Number(v) || 0;\n    }\n\n    function fmt(v){\n      return Number(v).toLocaleString('ms-MY');\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n\n    }\n\n    function getSelected(){\n\n      return {\n        state: FIXED_STATE,\n        year: parseInt(el('hh-year-filter').value,10),\n        metric: el('hh-metric-filter').value\n      };\n\n    }\n\n    function getRowsByState(state){\n\n      return allRows\n        .filter(function(row){\n          return row.state === state;\n        })\n        .sort(function(a,b){\n          return yr(a) - yr(b);\n        });\n\n    }\n\n    function getRow(state, year){\n\n      return allRows.find(function(row){\n        return row.state === state && yr(row) === year;\n      });\n\n    }\n\n    function metricLabel(metric){\n\n      if(metric === 'households'){\n        return 'Isi Rumah';\n      }\n\n      if(metric === 'living_quarters'){\n        return 'Tempat Kediaman';\n      }\n\n      return 'Isi Rumah & Tempat Kediaman';\n\n    }\n\n    function populateFilters(){\n\n      var years =\n        Array.from(new Set(allRows.map(yr)))\n          .filter(function(y){\n            return !isNaN(y);\n          })\n          .sort(function(a,b){\n            return b-a;\n          });\n\n      el('hh-year-filter').innerHTML =\n        years.map(function(year){\n          return '<option value=\"' + year + '\">' + year + '<\/option>';\n        }).join('');\n\n      if(years.length){\n        el('hh-year-filter').value = years[0];\n      }\n\n    }\n\n    function updateCards(){\n\n      var selected = getSelected();\n      var row = getRow(FIXED_STATE, selected.year);\n\n      if(!row){\n\n        el('hh-stat-year').textContent = '\u2014';\n        el('hh-stat-households').textContent = '\u2014';\n        el('hh-stat-lq').textContent = '\u2014';\n        el('hh-stat-diff').textContent = '\u2014';\n\n        el('hh-stats').style.display = '';\n\n        return;\n      }\n\n      var households = num(row.households);\n      var lq = num(row.living_quarters);\n\n      el('hh-stat-year').textContent =\n        selected.year;\n\n      el('hh-stat-households').textContent =\n        fmt(households);\n\n      el('hh-stat-lq').textContent =\n        fmt(lq);\n\n      el('hh-stat-diff').textContent =\n        fmt(lq - households);\n\n      el('hh-stats').style.display = '';\n\n    }\n\n    function buildTrendChart(){\n\n      var selected = getSelected();\n      var rows = getRowsByState(FIXED_STATE);\n      var datasets = [];\n\n      if(\n        selected.metric === 'both' ||\n        selected.metric === 'households'\n      ){\n\n        datasets.push({\n          label:'Isi Rumah',\n          data:rows.map(function(row){\n            return num(row.households);\n          }),\n          borderColor:'#2563eb',\n          backgroundColor:'rgba(37,99,235,.15)',\n          fill:selected.metric !== 'both',\n          tension:.35,\n          pointRadius:rows.length > 20 ? 2 : 4,\n          borderWidth:2.5\n        });\n\n      }\n\n      if(\n        selected.metric === 'both' ||\n        selected.metric === 'living_quarters'\n      ){\n\n        datasets.push({\n          label:'Tempat Kediaman',\n          data:rows.map(function(row){\n            return num(row.living_quarters);\n          }),\n          borderColor:'#f97316',\n          backgroundColor:'rgba(249,115,22,.15)',\n          fill:selected.metric !== 'both',\n          tension:.35,\n          pointRadius:rows.length > 20 ? 2 : 4,\n          borderWidth:2.5\n        });\n\n      }\n\n      el('hh-trend-sub').textContent =\n        FIXED_STATE +\n        ' \u00b7 ' +\n        metricLabel(selected.metric);\n\n      destroyChart('trend');\n\n      charts.trend =\n        new Chart(\n          el('hh-chart-trend').getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:rows.map(yr),\n              datasets:datasets\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n                      return ' ' +\n                        c.dataset.label +\n                        ': ' +\n                        fmt(c.parsed.y);\n                    }\n                  }\n                }\n\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    maxTicksLimit:10\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return v.toLocaleString('ms-MY');\n                    }\n                  }\n                }\n\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function buildStateChart(){\n\n      var selected = getSelected();\n\n      var data =\n        allRows\n          .filter(function(row){\n            return yr(row) === selected.year;\n          })\n          .sort(function(a,b){\n            return num(b.households) - num(a.households);\n          });\n\n      el('hh-state-sub').textContent =\n        selected.year +\n        ' \u00b7 Semua Negeri \u00b7 ' +\n        metricLabel(selected.metric);\n\n      destroyChart('state');\n\n      var datasets = [];\n\n      if(selected.metric === 'both'){\n\n        datasets = [\n\n          {\n            label:'Isi Rumah',\n            data:data.map(function(row){\n              return num(row.households);\n            }),\n            backgroundColor:'rgba(37,99,235,.78)',\n            borderRadius:6\n          },\n\n          {\n            label:'Tempat Kediaman',\n            data:data.map(function(row){\n              return num(row.living_quarters);\n            }),\n            backgroundColor:'rgba(249,115,22,.78)',\n            borderRadius:6\n          }\n\n        ];\n\n      }else{\n\n        datasets = [{\n\n          label:\n            selected.metric === 'living_quarters'\n              ? 'Tempat Kediaman'\n              : 'Isi Rumah',\n\n          data:data.map(function(row){\n\n            return selected.metric === 'living_quarters'\n              ? num(row.living_quarters)\n              : num(row.households);\n\n          }),\n\n          backgroundColor:\n            selected.metric === 'living_quarters'\n              ? 'rgba(249,115,22,.78)'\n              : 'rgba(37,99,235,.78)',\n\n          borderRadius:8\n\n        }];\n\n      }\n\n      charts.state =\n        new Chart(\n          el('hh-chart-state').getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:data.map(function(row){\n                return row.state;\n              }),\n\n              datasets:datasets\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:selected.metric === 'both',\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return ' ' +\n                        c.dataset.label +\n                        ': ' +\n                        fmt(c.parsed.y);\n\n                    }\n                  }\n                }\n\n              },\n\n              scales:{\n\n                x:{\n                  stacked:selected.metric === 'both',\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n                    minRotation:0\n                  }\n                },\n\n                y:{\n                  stacked:selected.metric === 'both',\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return v.toLocaleString('ms-MY');\n                    }\n                  }\n                }\n\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function buildShareCharts(){\n\n      var selected = getSelected();\n      var row = getRow(FIXED_STATE, selected.year);\n      var rows = getRowsByState(FIXED_STATE);\n\n      var households =\n        row ? num(row.households) : 0;\n\n      var lq =\n        row ? num(row.living_quarters) : 0;\n\n      el('hh-share-sub').textContent =\n        selected.year +\n        ' \u00b7 ' +\n        FIXED_STATE;\n\n      destroyChart('share');\n\n      charts.share =\n        new Chart(\n          el('hh-chart-share').getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:[\n                'Isi Rumah',\n                'Tempat Kediaman'\n              ],\n\n              datasets:[{\n                data:[\n                  households,\n                  lq\n                ],\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#f97316'\n                ],\n\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed);\n\n                    }\n                  }\n                }\n\n              }\n\n            }\n          }\n        );\n\n      var gapData =\n        rows.map(function(row){\n\n          return {\n            year:yr(row),\n            gap:\n              num(row.living_quarters) -\n              num(row.households)\n          };\n\n        });\n\n      destroyChart('gap');\n\n      charts.gap =\n        new Chart(\n          el('hh-chart-gap').getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                gapData.map(function(d){\n                  return d.year;\n                }),\n\n              datasets:[{\n                label:'Jurang',\n\n                data:\n                  gapData.map(function(d){\n                    return d.gap;\n                  }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:\n                  gapData.length > 20 ? 2 : 4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return ' Jurang: ' +\n                        fmt(c.parsed.y);\n\n                    }\n                  }\n                }\n\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    maxTicksLimit:8\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return v.toLocaleString('ms-MY');\n                    }\n                  }\n                }\n\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#hh-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute('data-tab')\n        : 'trend';\n\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      if(activeTab() === 'trend'){\n        buildTrendChart();\n      }\n\n      if(activeTab() === 'state'){\n        buildStateChart();\n      }\n\n      if(activeTab() === 'share'){\n        buildShareCharts();\n      }\n\n    }\n\n    document\n      .querySelectorAll('#hh-wrap .tbtn')\n      .forEach(function(btn){\n\n        btn.addEventListener('click',function(){\n\n          document\n            .querySelectorAll('#hh-wrap .tbtn')\n            .forEach(function(b){\n              b.classList.remove('active');\n            });\n\n          btn.classList.add('active');\n\n          el('hh-tab-trend').style.display =\n            'none';\n\n          el('hh-tab-state').style.display =\n            'none';\n\n          el('hh-tab-share').style.display =\n            'none';\n\n          el(\n            'hh-tab-' +\n            btn.getAttribute('data-tab')\n          ).style.display = '';\n\n          redraw();\n\n        });\n\n      });\n\n    [\n      'hh-year-filter',\n      'hh-metric-filter'\n    ].forEach(function(id){\n\n      el(id).addEventListener(\n        'change',\n        redraw\n      );\n\n    });\n\n    fetch(API_URL)\n\n      .then(function(response){\n\n        if(!response.ok){\n          throw new Error(\n            'HTTP ' + response.status\n          );\n        }\n\n        return response.json();\n\n      })\n\n      .then(function(data){\n\n        allRows =\n          Array.isArray(data)\n            ? data\n            : [];\n\n        if(!allRows.length){\n\n          throw new Error(\n            'Tiada rekod data ditemui.'\n          );\n\n        }\n\n        populateFilters();\n\n        el('hh-loading').style.display =\n          'none';\n\n        el('hh-area').style.display =\n          '';\n\n        redraw();\n\n      })\n\n      .catch(function(error){\n\n        el('hh-loading').innerHTML =\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">' +\n          'Gagal memuatkan data' +\n          '<\/div>' +\n          '<p style=\"font-size:13px;color:#64748b\">' +\n          error.message +\n          '<\/p>';\n\n      });\n\n  }\n\n  if(document.readyState === 'loading'){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#income-wrap, #income-wrap * { box-sizing:border-box; }\n#income-wrap{\n  --bg:#f8fafc;--card:#fff;--soft:#eff6ff;--border:#dbeafe;\n  --text:#0f172a;--muted:#64748b;--accent:#2563eb;--orange:#f97316;\n  --green:#16a34a;--red:#dc2626;--purple:#7c3aed;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%;padding:28px;background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);border-radius:22px;font-family:Arial,sans-serif;color:var(--text);\n}\n\n#income-wrap .dash{max-width:1180px;margin:0 auto;}\n#income-wrap .hdr,#income-wrap .cc,#income-wrap .sc,#income-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#income-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#income-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#income-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#income-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#income-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#income-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#income-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#income-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#income-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#income-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#income-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#income-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#income-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#income-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n}\n\n#income-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#income-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#income-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#income-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#income-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#income-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n}\n\n#income-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#income-wrap .chart-box{\n  position:relative;\n  height:350px;\n}\n\n#income-wrap .chart-box-sm{\n  position:relative;\n  height:320px;\n}\n\n#income-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#income-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:income-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes income-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n  #income-wrap .filter-bar,\n  #income-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #income-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n  #income-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #income-wrap .filter-bar,\n  #income-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #income-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #income-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #income-wrap .chart-box,\n  #income-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n<div id=\"income-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Pendapatan Isi Rumah Melaka<\/div>\n      <h2>Papan Pemuka Pendapatan Isi Rumah<\/h2>\n      <p class=\"hdesc\">\n        Visualisasi pendapatan kasar bulanan purata dan median isi rumah\n        di Melaka mengikut kawasan Parlimen dan DUN.\n      <\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"income-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Parlimen<\/label>\n        <select id=\"income-parlimen\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>DUN<\/label>\n        <select id=\"income-dun\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Ukuran<\/label>\n        <select id=\"income-metric\">\n          <option value=\"both\">Pendapatan Purata &#038; Median<\/option>\n          <option value=\"income_mean\">Pendapatan Purata<\/option>\n          <option value=\"income_median\">Pendapatan Median<\/option>\n        <\/select>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"stats\" id=\"income-stats\" style=\"display:none\">\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"income-stat-year\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Pendapatan Purata<\/div>\n        <div class=\"sv\" id=\"income-stat-mean\">\u2014<\/div>\n        <div class=\"su\">RM \/ bulan<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Pendapatan Median<\/div>\n        <div class=\"sv\" id=\"income-stat-median\">\u2014<\/div>\n        <div class=\"su\">RM \/ bulan<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Jurang Purata-Median<\/div>\n        <div class=\"sv\" id=\"income-stat-gap\">\u2014<\/div>\n        <div class=\"su\">RM \/ bulan<\/div>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"sbox\" id=\"income-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod pendapatan isi rumah Melaka.<\/div>\n    <\/div>\n\n    <div id=\"income-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend Pendapatan<\/button>\n        <button class=\"tbtn\" data-tab=\"dun\">Perbandingan DUN<\/button>\n        <button class=\"tbtn\" data-tab=\"parlimen\">Parlimen<\/button>\n        <button class=\"tbtn\" data-tab=\"gap\">Jurang Pendapatan<\/button>\n        <button class=\"tbtn\" data-tab=\"topbottom\">Tertinggi &#038; Terendah<\/button>\n        <button class=\"tbtn\" data-tab=\"growth\">Pertumbuhan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <div id=\"income-tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Pendapatan Mengikut Masa<\/div>\n          <div class=\"cs\" id=\"income-trend-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"income-chart-trend\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"income-tab-dun\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Pendapatan Mengikut DUN<\/div>\n          <div class=\"cs\" id=\"income-dun-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"income-chart-dun\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"income-tab-parlimen\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Pendapatan Mengikut Parlimen<\/div>\n          <div class=\"cs\" id=\"income-parlimen-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"income-chart-parlimen\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"income-tab-gap\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Jurang Purata-Median Mengikut DUN<\/div>\n            <div class=\"cs\" id=\"income-gap-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"income-chart-gap\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Jurang Mengikut Masa<\/div>\n            <div class=\"cs\" id=\"income-gap-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"income-chart-gap-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <div id=\"income-tab-topbottom\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">10 DUN dengan Pendapatan Tertinggi<\/div>\n            <div class=\"cs\" id=\"income-top-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"income-chart-top\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">10 DUN dengan Pendapatan Terendah<\/div>\n            <div class=\"cs\" id=\"income-bottom-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"income-chart-bottom\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <div id=\"income-tab-growth\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Kadar Pertumbuhan Pendapatan Mengikut DUN<\/div>\n          <div class=\"cs\" id=\"income-growth-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"income-chart-growth\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"income-tab-state\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Pendapatan Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"income-state-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"income-chart-state\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue?id=hh_income_dun&limit=2000';\n\n    var FIXED_STATE='Melaka';\n    var allRows=[];\n    var melakaRows=[];\n    var charts={};\n\n    Chart.defaults.color='#64748b';\n    Chart.defaults.borderColor='#dbeafe';\n    Chart.defaults.font.family='Arial, sans-serif';\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(r){\n      return parseInt(\n        String(r.date||'').slice(0,4),\n        10\n      );\n    }\n\n    function num(v){\n      return Number(v)||0;\n    }\n\n    function rm(v){\n      return 'RM '+\n        Number(v).toLocaleString(\n          'ms-MY',\n          {maximumFractionDigits:0}\n        );\n    }\n\n    function pct(v){\n      return Number(v).toLocaleString(\n        'ms-MY',\n        {maximumFractionDigits:1}\n      )+'%';\n    }\n\n    function avg(arr){\n      return arr.length\n        ? arr.reduce(function(a,b){\n            return a+b;\n          },0)\/arr.length\n        : 0;\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n\n    }\n\n    function metricLabel(m){\n\n      if(m==='income_mean'){\n        return 'Pendapatan Purata';\n      }\n\n      if(m==='income_median'){\n        return 'Pendapatan Median';\n      }\n\n      return 'Pendapatan Purata & Median';\n\n    }\n\n    function metricValue(row,metric){\n\n      if(metric==='income_median'){\n        return num(row.income_median);\n      }\n\n      return num(row.income_mean);\n\n    }\n\n    function getSelected(){\n\n      return {\n        year:parseInt(\n          el('income-year').value,\n          10\n        ),\n\n        parlimen:\n          el('income-parlimen').value,\n\n        dun:\n          el('income-dun').value,\n\n        metric:\n          el('income-metric').value\n      };\n\n    }\n\n    function selectedRows(ignoreDun){\n\n      var s=getSelected();\n\n      return melakaRows.filter(function(r){\n\n        var p=\n          s.parlimen==='all' ||\n          r.parlimen===s.parlimen;\n\n        var d=\n          ignoreDun ||\n          s.dun==='all' ||\n          r.dun===s.dun;\n\n        return p&&d;\n\n      });\n\n    }\n\n    function rowsForYear(ignoreDun){\n\n      var s=getSelected();\n\n      return selectedRows(ignoreDun)\n        .filter(function(r){\n          return yr(r)===s.year;\n        });\n\n    }\n\n    function populateFilters(){\n\n      var years=\n        Array.from(\n          new Set(melakaRows.map(yr))\n        )\n        .filter(function(y){\n          return !isNaN(y);\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('income-year').innerHTML=\n        years.map(function(y){\n          return '<option value=\"'+\n            y+\n            '\">'+\n            y+\n            '<\/option>';\n        }).join('');\n\n      if(years.length){\n        el('income-year').value=\n          years[0];\n      }\n\n      var parls=\n        Array.from(\n          new Set(\n            melakaRows.map(function(r){\n              return r.parlimen;\n            })\n          )\n        ).sort();\n\n      el('income-parlimen').innerHTML=\n        '<option value=\"all\">Semua Parlimen<\/option>'+\n        parls.map(function(p){\n\n          return '<option value=\"'+\n            p+\n            '\">'+\n            p+\n            '<\/option>';\n\n        }).join('');\n\n      populateDunFilter();\n\n    }\n\n    function populateDunFilter(){\n\n      var s=getSelected();\n\n      var list=\n        melakaRows.filter(function(r){\n\n          return (\n            s.parlimen==='all' ||\n            r.parlimen===s.parlimen\n          );\n\n        });\n\n      var duns=\n        Array.from(\n          new Set(\n            list.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      el('income-dun').innerHTML=\n        '<option value=\"all\">Semua DUN<\/option>'+\n        duns.map(function(d){\n\n          return '<option value=\"'+\n            d+\n            '\">'+\n            d+\n            '<\/option>';\n\n        }).join('');\n\n    }\n\n    function updateCards(){\n\n      var s=getSelected();\n      var rows=rowsForYear(false);\n\n      var mean=\n        avg(\n          rows.map(function(r){\n            return num(r.income_mean);\n          })\n        );\n\n      var median=\n        avg(\n          rows.map(function(r){\n            return num(r.income_median);\n          })\n        );\n\n      el('income-stat-year').textContent=\n        s.year||'\u2014';\n\n      el('income-stat-mean').textContent=\n        rows.length\n          ? rm(mean)\n          : '\u2014';\n\n      el('income-stat-median').textContent=\n        rows.length\n          ? rm(median)\n          : '\u2014';\n\n      el('income-stat-gap').textContent=\n        rows.length\n          ? rm(mean-median)\n          : '\u2014';\n\n      el('income-stats').style.display='';\n\n    }\n\n    function chartTooltipRM(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            return ' '+\n              c.dataset.label+\n              ': '+\n              rm(c.parsed.y);\n\n          }\n        }\n      };\n\n    }\n\n    function buildTrendChart(){\n\n      var s=getSelected();\n      var rows=selectedRows(false);\n\n      var years=\n        Array.from(\n          new Set(rows.map(yr))\n        ).sort(function(a,b){\n          return a-b;\n        });\n\n      var data=\n        years.map(function(y){\n\n          var yearRows=\n            rows.filter(function(r){\n              return yr(r)===y;\n            });\n\n          return {\n            year:y,\n\n            mean:\n              avg(\n                yearRows.map(function(r){\n                  return num(r.income_mean);\n                })\n              ),\n\n            median:\n              avg(\n                yearRows.map(function(r){\n                  return num(r.income_median);\n                })\n              )\n          };\n\n        });\n\n      var datasets=[];\n\n      if(\n        s.metric==='both' ||\n        s.metric==='income_mean'\n      ){\n\n        datasets.push({\n          label:'Pendapatan Purata',\n          data:data.map(function(d){\n            return d.mean;\n          }),\n          borderColor:'#2563eb',\n          backgroundColor:'rgba(37,99,235,.15)',\n          fill:s.metric!=='both',\n          tension:.35,\n          borderWidth:2.5,\n          pointRadius:4\n        });\n\n      }\n\n      if(\n        s.metric==='both' ||\n        s.metric==='income_median'\n      ){\n\n        datasets.push({\n          label:'Pendapatan Median',\n          data:data.map(function(d){\n            return d.median;\n          }),\n          borderColor:'#f97316',\n          backgroundColor:'rgba(249,115,22,.15)',\n          fill:s.metric!=='both',\n          tension:.35,\n          borderWidth:2.5,\n          pointRadius:4\n        });\n\n      }\n\n      el('income-trend-sub').textContent=\n        FIXED_STATE+\n        ' \u00b7 '+\n        (\n          s.parlimen==='all'\n            ? 'Semua Parlimen'\n            : s.parlimen\n        )+\n        ' \u00b7 '+\n        (\n          s.dun==='all'\n            ? 'Semua DUN'\n            : s.dun\n        )+\n        ' \u00b7 '+\n        metricLabel(s.metric);\n\n      destroyChart('trend');\n\n      charts.trend=\n        new Chart(\n          el('income-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:data.map(function(d){\n                return d.year;\n              }),\n              datasets:datasets\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  chartTooltipRM()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return 'RM '+\n                        Number(v)\n                          .toLocaleString('ms-MY');\n                    }\n                  }\n                }\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function buildDunChart(){\n\n      var s=getSelected();\n      var rows=rowsForYear(true);\n\n      var duns=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      var data=\n        duns.map(function(dun){\n\n          var r=\n            rows.filter(function(x){\n              return x.dun===dun;\n            });\n\n          return {\n            dun:dun,\n\n            mean:\n              avg(\n                r.map(function(x){\n                  return num(x.income_mean);\n                })\n              ),\n\n            median:\n              avg(\n                r.map(function(x){\n                  return num(x.income_median);\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.mean-a.mean;\n        });\n\n      var datasets=[];\n\n      if(s.metric==='both'){\n\n        datasets=[\n\n          {\n            label:'Pendapatan Purata',\n            data:data.map(function(d){\n              return d.mean;\n            }),\n            backgroundColor:'rgba(37,99,235,.78)',\n            borderRadius:6\n          },\n\n          {\n            label:'Pendapatan Median',\n            data:data.map(function(d){\n              return d.median;\n            }),\n            backgroundColor:'rgba(249,115,22,.78)',\n            borderRadius:6\n          }\n\n        ];\n\n      }else{\n\n        datasets=[{\n          label:metricLabel(s.metric),\n\n          data:data.map(function(d){\n\n            return (\n              s.metric==='income_mean'\n                ? d.mean\n                : d.median\n            );\n\n          }),\n\n          backgroundColor:\n            s.metric==='income_mean'\n              ? 'rgba(37,99,235,.78)'\n              : 'rgba(249,115,22,.78)',\n\n          borderRadius:8\n        }];\n\n      }\n\n      el('income-dun-sub').textContent=\n        s.year+\n        ' \u00b7 '+\n        (\n          s.parlimen==='all'\n            ? 'Semua Parlimen'\n            : s.parlimen\n        )+\n        ' \u00b7 '+\n        metricLabel(s.metric);\n\n      destroyChart('dun');\n\n      charts.dun=\n        new Chart(\n          el('income-chart-dun')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:data.map(function(d){\n                return d.dun;\n              }),\n              datasets:datasets\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:\n                    s.metric==='both',\n                  position:'bottom'\n                },\n\n                tooltip:\n                  chartTooltipRM()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return 'RM '+\n                        Number(v)\n                          .toLocaleString('ms-MY');\n                    }\n                  }\n                }\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function buildParlimenChart(){\n\n      var s=getSelected();\n\n      var rows=\n        melakaRows.filter(function(r){\n          return yr(r)===s.year;\n        });\n\n      var parls=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.parlimen;\n            })\n          )\n        ).sort();\n\n      var data=\n        parls.map(function(p){\n\n          var r=\n            rows.filter(function(x){\n              return x.parlimen===p;\n            });\n\n          return {\n            parlimen:p,\n\n            mean:\n              avg(\n                r.map(function(x){\n                  return num(x.income_mean);\n                })\n              ),\n\n            median:\n              avg(\n                r.map(function(x){\n                  return num(x.income_median);\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.mean-a.mean;\n        });\n\n      el('income-parlimen-sub').textContent=\n        s.year+\n        ' \u00b7 Purata peringkat Parlimen berdasarkan DUN';\n\n      destroyChart('parlimen');\n\n      charts.parlimen=\n        new Chart(\n          el('income-chart-parlimen')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:data.map(function(d){\n                return d.parlimen;\n              }),\n\n              datasets:[\n\n                {\n                  label:'Pendapatan Purata',\n                  data:data.map(function(d){\n                    return d.mean;\n                  }),\n                  backgroundColor:'rgba(37,99,235,.78)',\n                  borderRadius:6\n                },\n\n                {\n                  label:'Pendapatan Median',\n                  data:data.map(function(d){\n                    return d.median;\n                  }),\n                  backgroundColor:'rgba(249,115,22,.78)',\n                  borderRadius:6\n                }\n\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  chartTooltipRM()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return 'RM '+\n                        Number(v)\n                          .toLocaleString('ms-MY');\n                    }\n                  }\n                }\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function buildGapCharts(){\n\n      var s=getSelected();\n      var rows=rowsForYear(true);\n\n      var duns=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      var data=\n        duns.map(function(dun){\n\n          var r=\n            rows.filter(function(x){\n              return x.dun===dun;\n            });\n\n          var mean=\n            avg(\n              r.map(function(x){\n                return num(x.income_mean);\n              })\n            );\n\n          var median=\n            avg(\n              r.map(function(x){\n                return num(x.income_median);\n              })\n            );\n\n          return {\n            dun:dun,\n            gap:mean-median\n          };\n\n        })\n        .sort(function(a,b){\n          return b.gap-a.gap;\n        });\n\n      el('income-gap-sub').textContent=\n        s.year+\n        ' \u00b7 Jurang yang lebih besar mungkin menunjukkan taburan pendapatan yang lebih tidak sekata';\n\n      destroyChart('gap');\n\n      charts.gap=\n        new Chart(\n          el('income-chart-gap')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:data.map(function(d){\n                return d.dun;\n              }),\n\n              datasets:[{\n                label:'Jurang Purata-Median',\n                data:data.map(function(d){\n                  return d.gap;\n                }),\n                backgroundColor:'rgba(124,58,237,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  chartTooltipRM()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return 'RM '+\n                        Number(v)\n                          .toLocaleString('ms-MY');\n                    }\n                  }\n                }\n              }\n\n            }\n          }\n        );\n\n      var trendRows=\n        selectedRows(false);\n\n      var years=\n        Array.from(\n          new Set(\n            trendRows.map(yr)\n          )\n        )\n        .sort(function(a,b){\n          return a-b;\n        });\n\n      var gapTrend=\n        years.map(function(y){\n\n          var r=\n            trendRows.filter(function(x){\n              return yr(x)===y;\n            });\n\n          return {\n            year:y,\n\n            gap:\n              avg(\n                r.map(function(x){\n                  return num(x.income_mean);\n                })\n              )\n              -\n              avg(\n                r.map(function(x){\n                  return num(x.income_median);\n                })\n              )\n          };\n\n        });\n\n      el('income-gap-trend-sub').textContent=\n        'Trend jurang berdasarkan Parlimen dan DUN yang dipilih';\n\n      destroyChart('gapTrend');\n\n      charts.gapTrend=\n        new Chart(\n          el('income-chart-gap-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                gapTrend.map(function(d){\n                  return d.year;\n                }),\n\n              datasets:[{\n                label:'Jurang Purata-Median',\n\n                data:\n                  gapTrend.map(function(d){\n                    return d.gap;\n                  }),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  chartTooltipRM()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return 'RM '+\n                        Number(v)\n                          .toLocaleString('ms-MY');\n                    }\n                  }\n                }\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function buildTopBottomCharts(){\n\n      var s=getSelected();\n      var rows=rowsForYear(true);\n\n      var duns=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      var data=\n        duns.map(function(dun){\n\n          var r=\n            rows.filter(function(x){\n              return x.dun===dun;\n            });\n\n          return {\n            dun:dun,\n\n            value:\n              avg(\n                r.map(function(x){\n\n                  return (\n                    s.metric==='income_median'\n                      ? num(x.income_median)\n                      : num(x.income_mean)\n                  );\n\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      var top=\n        data.slice(0,10);\n\n      var bottom=\n        data.slice(-10).reverse();\n\n      el('income-top-sub').textContent=\n        s.year+\n        ' \u00b7 '+\n        (\n          s.metric==='income_median'\n            ? 'Pendapatan Median'\n            : 'Pendapatan Purata'\n        );\n\n      el('income-bottom-sub').textContent=\n        s.year+\n        ' \u00b7 Kawasan yang mempunyai tahap pendapatan lebih rendah';\n\n      function makeChart(\n        id,\n        name,\n        data,\n        color\n      ){\n\n        destroyChart(name);\n\n        charts[name]=\n          new Chart(\n            el(id).getContext('2d'),\n            {\n              type:'bar',\n\n              data:{\n                labels:\n                  data.map(function(d){\n                    return d.dun;\n                  }),\n\n                datasets:[{\n                  label:'Pendapatan',\n\n                  data:\n                    data.map(function(d){\n                      return d.value;\n                    }),\n\n                  backgroundColor:color,\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n                responsive:true,\n                maintainAspectRatio:false,\n\n                plugins:{\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:{\n                    backgroundColor:'#0f172a',\n\n                    callbacks:{\n                      label:function(c){\n\n                        return ' Pendapatan: '+\n                          rm(c.parsed.y);\n\n                      }\n                    }\n                  }\n                },\n\n                scales:{\n                  x:{\n                    grid:{\n                      display:false\n                    },\n\n                    ticks:{\n                      maxRotation:45\n                    }\n                  },\n\n                  y:{\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(v){\n                        return 'RM '+\n                          Number(v)\n                            .toLocaleString('ms-MY');\n                      }\n                    }\n                  }\n                }\n\n              }\n            }\n          );\n\n      }\n\n      makeChart(\n        'income-chart-top',\n        'top',\n        top,\n        'rgba(22,163,74,.78)'\n      );\n\n      makeChart(\n        'income-chart-bottom',\n        'bottom',\n        bottom,\n        'rgba(220,38,38,.78)'\n      );\n\n    }\n\n    function buildGrowthChart(){\n\n      var s=getSelected();\n      var current=s.year;\n\n      var availableYears=\n        Array.from(\n          new Set(\n            melakaRows.map(yr)\n          )\n        )\n        .filter(function(y){\n\n          return (\n            !isNaN(y) &&\n            y<current\n          );\n\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      var prev=\n        availableYears.length\n          ? availableYears[0]\n          : null;\n\n      if(!prev){\n\n        el('income-growth-sub')\n          .textContent=\n            current+\n            ' \u00b7 Tiada tahun sebelumnya tersedia untuk pengiraan pertumbuhan.';\n\n        destroyChart('growth');\n\n        return;\n      }\n\n      var rowsCurrent=\n        melakaRows.filter(function(r){\n          return yr(r)===current;\n        });\n\n      var rowsPrev=\n        melakaRows.filter(function(r){\n          return yr(r)===prev;\n        });\n\n      var duns=\n        Array.from(\n          new Set(\n            rowsCurrent.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      var data=\n        duns.map(function(dun){\n\n          var c=\n            rowsCurrent.find(function(r){\n              return r.dun===dun;\n            });\n\n          var p=\n            rowsPrev.find(function(r){\n              return r.dun===dun;\n            });\n\n          var cv=\n            c\n              ? metricValue(c,s.metric)\n              : 0;\n\n          var pv=\n            p\n              ? metricValue(p,s.metric)\n              : 0;\n\n          return {\n            dun:dun,\n\n            growth:\n              pv\n                ? ((cv-pv)\/pv)*100\n                : null\n          };\n\n        })\n        .filter(function(d){\n          return d.growth!==null;\n        })\n        .sort(function(a,b){\n          return b.growth-a.growth;\n        });\n\n      el('income-growth-sub')\n        .textContent=\n          current+\n          ' berbanding '+\n          prev+\n          ' \u00b7 Pertumbuhan '+\n          (\n            s.metric==='income_median'\n              ? 'Pendapatan Median'\n              : 'Pendapatan Purata'\n          );\n\n      destroyChart('growth');\n\n      charts.growth=\n        new Chart(\n          el('income-chart-growth')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.dun;\n                }),\n\n              datasets:[{\n                label:'Kadar Pertumbuhan',\n\n                data:\n                  data.map(function(d){\n                    return d.growth;\n                  }),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return ' Pertumbuhan: '+\n                        pct(c.parsed.y);\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return v+'%';\n                    }\n                  }\n                }\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function buildStateChart(){\n\n      var s=getSelected();\n\n      var rows=\n        allRows.filter(function(r){\n          return yr(r)===s.year;\n        });\n\n      var states=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.state;\n            })\n          )\n        ).sort();\n\n      var data=\n        states.map(function(state){\n\n          var r=\n            rows.filter(function(x){\n              return x.state===state;\n            });\n\n          return {\n            state:state,\n\n            mean:\n              avg(\n                r.map(function(x){\n                  return num(x.income_mean);\n                })\n              ),\n\n            median:\n              avg(\n                r.map(function(x){\n                  return num(x.income_median);\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.mean-a.mean;\n        });\n\n      var datasets=[];\n\n      if(s.metric==='both'){\n\n        datasets=[\n\n          {\n            label:'Pendapatan Purata',\n            data:data.map(function(d){\n              return d.mean;\n            }),\n            backgroundColor:'rgba(37,99,235,.78)',\n            borderRadius:6\n          },\n\n          {\n            label:'Pendapatan Median',\n            data:data.map(function(d){\n              return d.median;\n            }),\n            backgroundColor:'rgba(249,115,22,.78)',\n            borderRadius:6\n          }\n\n        ];\n\n      }else{\n\n        datasets=[{\n          label:metricLabel(s.metric),\n\n          data:data.map(function(d){\n\n            return (\n              s.metric==='income_mean'\n                ? d.mean\n                : d.median\n            );\n\n          }),\n\n          backgroundColor:\n            s.metric==='income_mean'\n              ? 'rgba(37,99,235,.78)'\n              : 'rgba(249,115,22,.78)',\n\n          borderRadius:8\n        }];\n\n      }\n\n      el('income-state-sub').textContent=\n        s.year+\n        ' \u00b7 Semua Negeri \u00b7 Purata mengikut DUN \u00b7 '+\n        metricLabel(s.metric);\n\n      destroyChart('state');\n\n      charts.state=\n        new Chart(\n          el('income-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:datasets\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:\n                    s.metric==='both',\n                  position:'bottom'\n                },\n\n                tooltip:\n                  chartTooltipRM()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return 'RM '+\n                        Number(v)\n                          .toLocaleString('ms-MY');\n                    }\n                  }\n                }\n              }\n\n            }\n          }\n        );\n\n    }\n\n    function activeTab(){\n\n      var a=\n        document.querySelector(\n          '#income-wrap .tbtn.active'\n        );\n\n      return a\n        ? a.getAttribute('data-tab')\n        : 'trend';\n\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      if(activeTab()==='trend'){\n        buildTrendChart();\n      }\n\n      if(activeTab()==='dun'){\n        buildDunChart();\n      }\n\n      if(activeTab()==='parlimen'){\n        buildParlimenChart();\n      }\n\n      if(activeTab()==='gap'){\n        buildGapCharts();\n      }\n\n      if(activeTab()==='topbottom'){\n        buildTopBottomCharts();\n      }\n\n      if(activeTab()==='growth'){\n        buildGrowthChart();\n      }\n\n      if(activeTab()==='state'){\n        buildStateChart();\n      }\n\n    }\n\n    document\n      .querySelectorAll(\n        '#income-wrap .tbtn'\n      )\n      .forEach(function(btn){\n\n        btn.addEventListener(\n          'click',\n          function(){\n\n            document\n              .querySelectorAll(\n                '#income-wrap .tbtn'\n              )\n              .forEach(function(b){\n                b.classList.remove('active');\n              });\n\n            btn.classList.add('active');\n\n            [\n              'trend',\n              'dun',\n              'parlimen',\n              'gap',\n              'topbottom',\n              'growth',\n              'state'\n            ].forEach(function(t){\n\n              el(\n                'income-tab-'+t\n              ).style.display='none';\n\n            });\n\n            el(\n              'income-tab-'+\n              btn.getAttribute('data-tab')\n            ).style.display='';\n\n            redraw();\n\n          }\n        );\n\n      });\n\n    el('income-parlimen')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateDunFilter();\n          redraw();\n\n        }\n      );\n\n    [\n      'income-year',\n      'income-dun',\n      'income-metric'\n    ].forEach(function(id){\n\n      el(id)\n        .addEventListener(\n          'change',\n          redraw\n        );\n\n    });\n\n    fetch(API_URL)\n\n      .then(function(r){\n\n        if(!r.ok){\n          throw new Error(\n            'HTTP '+r.status\n          );\n        }\n\n        return r.json();\n\n      })\n\n      .then(function(data){\n\n        allRows=\n          Array.isArray(data)\n            ? data\n            : [];\n\n        melakaRows=\n          allRows.filter(function(r){\n\n            return String(\n              r.state||''\n            )\n            .trim()\n            .toLowerCase()\n            ===\n            'melaka';\n\n          });\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            'Tiada rekod Melaka ditemui.'\n          );\n\n        }\n\n        populateFilters();\n\n        el('income-loading')\n          .style.display='none';\n\n        el('income-area')\n          .style.display='';\n\n        redraw();\n\n      })\n\n      .catch(function(e){\n\n        el('income-loading')\n          .innerHTML=\n\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">'+\n          'Gagal memuatkan data'+\n          '<\/div>'+\n          '<p style=\"font-size:13px;color:#64748b\">'+\n          e.message+\n          '<\/p>';\n\n      });\n\n  }\n\n  if(\n    document.readyState===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#poverty-wrap, #poverty-wrap * { box-sizing:border-box; }\n#poverty-wrap{\n  --bg:#f8fafc;--card:#fff;--soft:#eff6ff;--border:#dbeafe;\n  --text:#0f172a;--muted:#64748b;--accent:#2563eb;\n  --green:#16a34a;--red:#dc2626;--orange:#f97316;--purple:#7c3aed;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%;padding:28px;background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);border-radius:22px;font-family:Arial,sans-serif;color:var(--text);\n}\n#poverty-wrap .dash{max-width:1180px;margin:0 auto;}\n#poverty-wrap .hdr,#poverty-wrap .cc,#poverty-wrap .sc,#poverty-wrap .sbox{\n  background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow);\n}\n#poverty-wrap .hdr{border-radius:20px;padding:26px;margin-bottom:20px;}\n#poverty-wrap .eyebrow{color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px;}\n#poverty-wrap h2{margin:0;font-size:clamp(26px,4vw,44px);line-height:1.1;font-weight:800;}\n#poverty-wrap .hdesc{color:var(--muted);margin-top:10px;line-height:1.6;}\n#poverty-wrap .filter-bar{\n  display:grid;grid-template-columns:repeat(3,1fr);gap:14px;background:#fff;border:1px solid var(--border);\n  border-radius:16px;padding:18px;margin-bottom:20px;box-shadow:var(--shadow);\n}\n#poverty-wrap .cg{display:flex;flex-direction:column;gap:7px;}\n#poverty-wrap label{font-size:11px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;}\n#poverty-wrap select{\n  width:100%;min-height:44px;padding:10px 40px 10px 12px;border:1.5px solid #bfdbfe;border-radius:10px;\n  background-color:#fff;color:var(--text);font-size:14px;font-weight:600;outline:none;appearance:none;cursor:pointer;\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n  background-repeat:no-repeat;background-position:right 12px center;\n}\n#poverty-wrap select:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(37,99,235,.12);}\n#poverty-wrap .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;}\n#poverty-wrap .sc{border-radius:16px;padding:18px;border-left:4px solid var(--accent);}\n#poverty-wrap .sl{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;margin-bottom:8px;}\n#poverty-wrap .sv{font-size:25px;font-weight:800;}\n#poverty-wrap .su{color:var(--muted);font-size:12px;margin-top:4px;}\n#poverty-wrap .tab-bar{\n  display:flex;justify-content:space-between;gap:10px;background:var(--soft);border:1px solid var(--border);\n  border-radius:999px;padding:8px;margin-bottom:18px;overflow-x:auto;\n}\n#poverty-wrap .tbtn{\n  flex:1;border:0;background:transparent;padding:12px 18px;border-radius:999px;color:var(--muted);\n  font-weight:800;cursor:pointer;white-space:nowrap;\n}\n#poverty-wrap .tbtn.active{background:#fff;color:var(--accent);box-shadow:0 5px 14px rgba(15,23,42,.08);}\n#poverty-wrap .cc{border-radius:20px;padding:24px;margin-bottom:18px;}\n#poverty-wrap .ct{font-size:18px;font-weight:800;margin-bottom:5px;}\n#poverty-wrap .cs{color:var(--muted);font-size:13px;margin-bottom:18px;}\n#poverty-wrap .two{display:grid;grid-template-columns:1fr 1fr;gap:18px;}\n#poverty-wrap .chart-box{position:relative;height:350px;}\n#poverty-wrap .chart-box-sm{position:relative;height:320px;}\n#poverty-wrap .sbox{border-radius:20px;padding:60px 24px;text-align:center;color:var(--muted);}\n#poverty-wrap .spin{\n  width:38px;height:38px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;\n  animation:poverty-spin .8s linear infinite;margin:0 auto 16px;\n}\n@keyframes poverty-spin{to{transform:rotate(360deg);}}\n@media(max-width:1024px){\n  #poverty-wrap .filter-bar,#poverty-wrap .stats{grid-template-columns:repeat(2,1fr);}\n  #poverty-wrap .two{grid-template-columns:1fr;}\n}\n@media(max-width:600px){\n  #poverty-wrap{padding:14px;border-radius:16px;}\n  #poverty-wrap .filter-bar,#poverty-wrap .stats{grid-template-columns:1fr;}\n  #poverty-wrap .tab-bar{justify-content:flex-start;}\n  #poverty-wrap .tbtn{flex:0 0 auto;}\n  #poverty-wrap .chart-box,#poverty-wrap .chart-box-sm{height:300px;}\n}\n<\/style>\n\n<div id=\"poverty-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Kadar Kemiskinan Melaka<\/div>\n      <h2>Papan Pemuka Kadar Kemiskinan<\/h2>\n      <p class=\"hdesc\">\n        Visualisasi kadar kemiskinan mutlak di Melaka mengikut kawasan Parlimen dan DUN.\n      <\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n      <div class=\"cg\"><label>Tahun<\/label><select id=\"poverty-year\"><\/select><\/div>\n      <div class=\"cg\"><label>Parlimen<\/label><select id=\"poverty-parlimen\"><\/select><\/div>\n      <div class=\"cg\"><label>DUN<\/label><select id=\"poverty-dun\"><\/select><\/div>\n    <\/div>\n\n    <div class=\"stats\" id=\"poverty-stats\" style=\"display:none\">\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"poverty-stat-year\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Purata Kadar Kemiskinan<\/div>\n        <div class=\"sv\" id=\"poverty-stat-avg\">\u2014<\/div>\n        <div class=\"su\">kadar kemiskinan mutlak<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">DUN Tertinggi<\/div>\n        <div class=\"sv\" id=\"poverty-stat-high\">\u2014<\/div>\n        <div class=\"su\" id=\"poverty-stat-high-name\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Perubahan<\/div>\n        <div class=\"sv\" id=\"poverty-stat-change\">\u2014<\/div>\n        <div class=\"su\" id=\"poverty-stat-change-year\">berbanding tahun sebelumnya<\/div>\n      <\/div>\n    <\/div>\n\n    <div class=\"sbox\" id=\"poverty-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod kadar kemiskinan Melaka.<\/div>\n    <\/div>\n\n    <div id=\"poverty-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"dun\">Perbandingan DUN<\/button>\n        <button class=\"tbtn\" data-tab=\"parlimen\">Parlimen<\/button>\n        <button class=\"tbtn\" data-tab=\"risk\">Tertinggi &#038; Terendah<\/button>\n        <button class=\"tbtn\" data-tab=\"change\">Perubahan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <div id=\"poverty-tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Kadar Kemiskinan<\/div>\n          <div class=\"cs\" id=\"poverty-trend-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"poverty-chart-trend\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"poverty-tab-dun\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Kadar Kemiskinan Mengikut DUN<\/div>\n          <div class=\"cs\" id=\"poverty-dun-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"poverty-chart-dun\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"poverty-tab-parlimen\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Kadar Kemiskinan Mengikut Parlimen<\/div>\n          <div class=\"cs\" id=\"poverty-parlimen-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"poverty-chart-parlimen\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"poverty-tab-risk\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">DUN dengan Kadar Kemiskinan Tertinggi<\/div>\n            <div class=\"cs\" id=\"poverty-high-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"poverty-chart-high\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">DUN dengan Kadar Kemiskinan Terendah<\/div>\n            <div class=\"cs\" id=\"poverty-low-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"poverty-chart-low\"><\/canvas><\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"poverty-tab-change\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Perubahan Kadar Kemiskinan Mengikut DUN<\/div>\n            <div class=\"cs\" id=\"poverty-change-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"poverty-chart-change\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Purata Perubahan (Mata Peratusan)<\/div>\n            <div class=\"cs\" id=\"poverty-change-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"poverty-chart-change-trend\"><\/canvas><\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"poverty-tab-state\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Kadar Kemiskinan Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"poverty-state-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"poverty-chart-state\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  function init(){\n    if(typeof Chart === 'undefined'){ setTimeout(init,500); return; }\n\n    var API_URL='https:\/\/api.data.gov.my\/data-catalogue?id=hh_poverty_dun&limit=2000';\n    var FIXED_STATE='Melaka';\n    var allRows=[], melakaRows=[], charts={};\n\n    Chart.defaults.color='#64748b';\n    Chart.defaults.borderColor='#dbeafe';\n    Chart.defaults.font.family='Arial, sans-serif';\n\n    function el(id){return document.getElementById(id);}\n    function yr(r){return parseInt(String(r.date||'').slice(0,4),10);}\n    function num(v){return Number(v)||0;}\n    function pct(v){return Number(v).toLocaleString('ms-MY',{maximumFractionDigits:1})+'%';}\n    function pp(v){return (v>0?'+':'')+Number(v).toLocaleString('ms-MY',{maximumFractionDigits:1})+' mata peratusan';}\n    function avg(arr){return arr.length?arr.reduce(function(a,b){return a+b;},0)\/arr.length:0;}\n    function destroyChart(name){if(charts[name]){charts[name].destroy();delete charts[name];}}\n\n    function getSelected(){\n      return {\n        year:parseInt(el('poverty-year').value,10),\n        parlimen:el('poverty-parlimen').value,\n        dun:el('poverty-dun').value\n      };\n    }\n\n    function selectedRows(ignoreDun){\n      var s=getSelected();\n      return melakaRows.filter(function(r){\n        var p=s.parlimen==='all'||r.parlimen===s.parlimen;\n        var d=ignoreDun||s.dun==='all'||r.dun===s.dun;\n        return p&&d;\n      });\n    }\n\n    function rowsForYear(ignoreDun){\n      var s=getSelected();\n      return selectedRows(ignoreDun).filter(function(r){return yr(r)===s.year;});\n    }\n\n    function getPrevYear(current){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){return !isNaN(y)&&y<current;})\n        .sort(function(a,b){return b-a;});\n      return years.length?years[0]:null;\n    }\n\n    function populateFilters(){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){return !isNaN(y);})\n        .sort(function(a,b){return b-a;});\n\n      el('poverty-year').innerHTML=years.map(function(y){\n        return '<option value=\"'+y+'\">'+y+'<\/option>';\n      }).join('');\n\n      if(years.length) el('poverty-year').value=years[0];\n\n      var parls=Array.from(new Set(melakaRows.map(function(r){return r.parlimen;}))).sort();\n\n      el('poverty-parlimen').innerHTML=\n        '<option value=\"all\">Semua Parlimen<\/option>'+\n        parls.map(function(p){\n          return '<option value=\"'+p+'\">'+p+'<\/option>';\n        }).join('');\n\n      populateDunFilter();\n    }\n\n    function populateDunFilter(){\n      var s=getSelected();\n\n      var list=melakaRows.filter(function(r){\n        return s.parlimen==='all'||r.parlimen===s.parlimen;\n      });\n\n      var duns=Array.from(new Set(list.map(function(r){return r.dun;}))).sort();\n\n      el('poverty-dun').innerHTML=\n        '<option value=\"all\">Semua DUN<\/option>'+\n        duns.map(function(d){\n          return '<option value=\"'+d+'\">'+d+'<\/option>';\n        }).join('');\n    }\n\n    function updateCards(){\n      var s=getSelected();\n      var rows=rowsForYear(false);\n\n      var avgPoverty=avg(rows.map(function(r){\n        return num(r.poverty_absolute);\n      }));\n\n      var high=rows.slice().sort(function(a,b){\n        return num(b.poverty_absolute)-num(a.poverty_absolute);\n      })[0];\n\n      var prev=getPrevYear(s.year);\n\n      var prevRows=prev\n        ? selectedRows(false).filter(function(r){return yr(r)===prev;})\n        : [];\n\n      var prevAvg=avg(prevRows.map(function(r){\n        return num(r.poverty_absolute);\n      }));\n\n      var change=prevRows.length\n        ? avgPoverty-prevAvg\n        : null;\n\n      el('poverty-stat-year').textContent=s.year||'\u2014';\n      el('poverty-stat-avg').textContent=rows.length?pct(avgPoverty):'\u2014';\n      el('poverty-stat-high').textContent=high?pct(num(high.poverty_absolute)):'\u2014';\n      el('poverty-stat-high-name').textContent=high?high.dun:'\u2014';\n      el('poverty-stat-change').textContent=change!==null?pp(change):'\u2014';\n\n      el('poverty-stat-change-year').textContent=\n        prev\n          ? 'berbanding '+prev\n          : 'berbanding tahun sebelumnya';\n\n      el('poverty-stats').style.display='';\n    }\n\n    function tooltipPct(){\n      return {\n        backgroundColor:'#0f172a',\n        callbacks:{\n          label:function(c){\n            return ' '+c.dataset.label+': '+pct(c.parsed.y);\n          }\n        }\n      };\n    }\n\n    function buildTrendChart(){\n      var s=getSelected(), rows=selectedRows(false);\n\n      var years=Array.from(new Set(rows.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var data=years.map(function(y){\n        var r=rows.filter(function(x){return yr(x)===y;});\n\n        return {\n          year:y,\n          value:avg(r.map(function(x){\n            return num(x.poverty_absolute);\n          }))\n        };\n      });\n\n      el('poverty-trend-sub').textContent=\n        FIXED_STATE+' \u00b7 '+\n        (s.parlimen==='all'?'Semua Parlimen':s.parlimen)+' \u00b7 '+\n        (s.dun==='all'?'Semua DUN':s.dun);\n\n      destroyChart('trend');\n\n      charts.trend=new Chart(el('poverty-chart-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:data.map(function(d){return d.year;}),\n          datasets:[{\n            label:'Kadar Kemiskinan',\n            data:data.map(function(d){return d.value;}),\n            borderColor:'#2563eb',\n            backgroundColor:'rgba(37,99,235,.15)',\n            fill:true,\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:4\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipPct()\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{callback:function(v){return v+'%';}}\n            }\n          }\n        }\n      });\n    }\n\n    function buildDunChart(){\n      var s=getSelected(), rows=rowsForYear(true);\n\n      var duns=Array.from(new Set(rows.map(function(r){return r.dun;}))).sort();\n\n      var data=duns.map(function(dun){\n        var r=rows.filter(function(x){return x.dun===dun;});\n\n        return {\n          dun:dun,\n          value:avg(r.map(function(x){\n            return num(x.poverty_absolute);\n          }))\n        };\n      }).sort(function(a,b){return b.value-a.value;});\n\n      el('poverty-dun-sub').textContent=\n        s.year+' \u00b7 '+(s.parlimen==='all'?'Semua Parlimen':s.parlimen);\n\n      destroyChart('dun');\n\n      charts.dun=new Chart(el('poverty-chart-dun').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.dun;}),\n          datasets:[{\n            label:'Kadar Kemiskinan',\n            data:data.map(function(d){return d.value;}),\n            backgroundColor:'rgba(37,99,235,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipPct()\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{callback:function(v){return v+'%';}}\n            }\n          }\n        }\n      });\n    }\n\n    function buildParlimenChart(){\n      var s=getSelected();\n\n      var rows=melakaRows.filter(function(r){\n        return yr(r)===s.year;\n      });\n\n      var parls=Array.from(new Set(rows.map(function(r){\n        return r.parlimen;\n      }))).sort();\n\n      var data=parls.map(function(p){\n        var r=rows.filter(function(x){\n          return x.parlimen===p;\n        });\n\n        return {\n          parlimen:p,\n          value:avg(r.map(function(x){\n            return num(x.poverty_absolute);\n          }))\n        };\n      }).sort(function(a,b){return b.value-a.value;});\n\n      el('poverty-parlimen-sub').textContent=\n        s.year+' \u00b7 Purata peringkat Parlimen berdasarkan DUN';\n\n      destroyChart('parlimen');\n\n      charts.parlimen=new Chart(el('poverty-chart-parlimen').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.parlimen;}),\n          datasets:[{\n            label:'Kadar Kemiskinan',\n            data:data.map(function(d){return d.value;}),\n            backgroundColor:'rgba(249,115,22,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipPct()\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{callback:function(v){return v+'%';}}\n            }\n          }\n        }\n      });\n    }\n\n    function buildRiskCharts(){\n      var s=getSelected(), rows=rowsForYear(true);\n\n      var duns=Array.from(new Set(rows.map(function(r){return r.dun;}))).sort();\n\n      var data=duns.map(function(dun){\n        var r=rows.filter(function(x){return x.dun===dun;});\n\n        return {\n          dun:dun,\n          value:avg(r.map(function(x){\n            return num(x.poverty_absolute);\n          }))\n        };\n      }).sort(function(a,b){return b.value-a.value;});\n\n      var high=data.slice(0,10);\n      var low=data.slice(-10).reverse();\n\n      el('poverty-high-sub').textContent=\n        s.year+' \u00b7 kawasan keutamaan dengan kadar kemiskinan tertinggi';\n\n      el('poverty-low-sub').textContent=\n        s.year+' \u00b7 kawasan dengan kadar kemiskinan terendah';\n\n      function makeChart(id,name,data,color){\n        destroyChart(name);\n\n        charts[name]=new Chart(el(id).getContext('2d'),{\n          type:'bar',\n          data:{\n            labels:data.map(function(d){return d.dun;}),\n            datasets:[{\n              label:'Kadar Kemiskinan',\n              data:data.map(function(d){return d.value;}),\n              backgroundColor:color,\n              borderRadius:8\n            }]\n          },\n          options:{\n            responsive:true,\n            maintainAspectRatio:false,\n            plugins:{\n              legend:{display:false},\n              tooltip:tooltipPct()\n            },\n            scales:{\n              x:{grid:{display:false},ticks:{maxRotation:45}},\n              y:{\n                grid:{color:'#dbeafe'},\n                ticks:{callback:function(v){return v+'%';}}\n              }\n            }\n          }\n        });\n      }\n\n      makeChart('poverty-chart-high','high',high,'rgba(220,38,38,.78)');\n      makeChart('poverty-chart-low','low',low,'rgba(22,163,74,.78)');\n    }\n\n    function buildChangeCharts(){\n      var s=getSelected();\n      var current=s.year;\n      var prev=getPrevYear(current);\n\n      if(!prev){\n        el('poverty-change-sub').textContent=\n          current+' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('poverty-change-trend-sub').textContent=\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n        return;\n      }\n\n      var rowsCurrent=melakaRows.filter(function(r){\n        return yr(r)===current;\n      });\n\n      var rowsPrev=melakaRows.filter(function(r){\n        return yr(r)===prev;\n      });\n\n      var duns=Array.from(new Set(rowsCurrent.map(function(r){\n        return r.dun;\n      }))).sort();\n\n      var data=duns.map(function(dun){\n        var c=rowsCurrent.find(function(r){\n          return r.dun===dun;\n        });\n\n        var p=rowsPrev.find(function(r){\n          return r.dun===dun;\n        });\n\n        return {\n          dun:dun,\n          change:c&&p\n            ? num(c.poverty_absolute)-num(p.poverty_absolute)\n            : null\n        };\n      })\n      .filter(function(d){return d.change!==null;})\n      .sort(function(a,b){return b.change-a.change;});\n\n      el('poverty-change-sub').textContent=\n        current+' berbanding '+prev+\n        ' \u00b7 nilai positif menunjukkan kadar kemiskinan meningkat';\n\n      destroyChart('change');\n\n      charts.change=new Chart(el('poverty-chart-change').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.dun;}),\n          datasets:[{\n            label:'Perubahan',\n            data:data.map(function(d){return d.change;}),\n            backgroundColor:'rgba(124,58,237,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Perubahan: '+pp(c.parsed.y);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return v+' mata peratusan';\n                }\n              }\n            }\n          }\n        }\n      });\n\n      var selected=selectedRows(false);\n\n      var years=Array.from(new Set(selected.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var trend=years.map(function(y,i){\n        if(i===0){\n          return {year:y,change:null};\n        }\n\n        var prevYear=years[i-1];\n\n        var r=selected.filter(function(x){\n          return yr(x)===y;\n        });\n\n        var p=selected.filter(function(x){\n          return yr(x)===prevYear;\n        });\n\n        return {\n          year:String(y)+' berbanding '+String(prevYear),\n          change:\n            avg(r.map(function(x){return num(x.poverty_absolute);}))\n            -\n            avg(p.map(function(x){return num(x.poverty_absolute);}))\n        };\n      }).filter(function(d){return d.change!==null;});\n\n      el('poverty-change-trend-sub').textContent=\n        'Purata perubahan kadar kemiskinan bagi tempoh data yang tersedia';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend=new Chart(el('poverty-chart-change-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:trend.map(function(d){return d.year;}),\n          datasets:[{\n            label:'Purata Perubahan',\n            data:trend.map(function(d){return d.change;}),\n            borderColor:'#7c3aed',\n            backgroundColor:'rgba(124,58,237,.15)',\n            fill:true,\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:4\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Perubahan: '+pp(c.parsed.y);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return v+' mata peratusan';\n                }\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function buildStateChart(){\n      var s=getSelected();\n\n      var rows=allRows.filter(function(r){\n        return yr(r)===s.year;\n      });\n\n      var states=Array.from(new Set(rows.map(function(r){\n        return r.state;\n      }))).sort();\n\n      var data=states.map(function(state){\n        var r=rows.filter(function(x){\n          return x.state===state;\n        });\n\n        return {\n          state:state,\n          value:avg(r.map(function(x){\n            return num(x.poverty_absolute);\n          }))\n        };\n      }).sort(function(a,b){return b.value-a.value;});\n\n      el('poverty-state-sub').textContent=\n        s.year+' \u00b7 semua negeri \u00b7 purata mengikut DUN';\n\n      destroyChart('state');\n\n      charts.state=new Chart(el('poverty-chart-state').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.state;}),\n          datasets:[{\n            label:'Kadar Kemiskinan',\n            data:data.map(function(d){return d.value;}),\n            backgroundColor:data.map(function(d){\n              return d.state==='Melaka'\n                ? 'rgba(249,115,22,.95)'\n                : 'rgba(37,99,235,.70)';\n            }),\n            borderColor:data.map(function(d){\n              return d.state==='Melaka'\n                ? '#f97316'\n                : 'rgba(37,99,235,.70)';\n            }),\n            borderWidth:data.map(function(d){\n              return d.state==='Melaka' ? 3 : 0;\n            }),\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipPct()\n          },\n          scales:{\n            x:{\n              grid:{display:false},\n              ticks:{\n                maxRotation:45,\n                color:function(context){\n                  var label=context.tick.label;\n                  return label==='Melaka'\n                    ? '#f97316'\n                    : '#64748b';\n                },\n                font:function(context){\n                  var label=context.tick.label;\n\n                  return {\n                    weight:label==='Melaka'?'800':'400',\n                    size:label==='Melaka'?13:12\n                  };\n                }\n              }\n            },\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return v+'%';\n                }\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function activeTab(){\n      var a=document.querySelector('#poverty-wrap .tbtn.active');\n      return a?a.getAttribute('data-tab'):'trend';\n    }\n\n    function redraw(){\n      updateCards();\n\n      if(activeTab()==='trend') buildTrendChart();\n      if(activeTab()==='dun') buildDunChart();\n      if(activeTab()==='parlimen') buildParlimenChart();\n      if(activeTab()==='risk') buildRiskCharts();\n      if(activeTab()==='change') buildChangeCharts();\n      if(activeTab()==='state') buildStateChart();\n    }\n\n    document.querySelectorAll('#poverty-wrap .tbtn').forEach(function(btn){\n      btn.addEventListener('click',function(){\n\n        document.querySelectorAll('#poverty-wrap .tbtn').forEach(function(b){\n          b.classList.remove('active');\n        });\n\n        btn.classList.add('active');\n\n        ['trend','dun','parlimen','risk','change','state'].forEach(function(t){\n          el('poverty-tab-'+t).style.display='none';\n        });\n\n        el('poverty-tab-'+btn.getAttribute('data-tab')).style.display='';\n\n        redraw();\n      });\n    });\n\n    el('poverty-parlimen').addEventListener('change',function(){\n      populateDunFilter();\n      redraw();\n    });\n\n    ['poverty-year','poverty-dun'].forEach(function(id){\n      el(id).addEventListener('change',redraw);\n    });\n\n    fetch(API_URL)\n      .then(function(r){\n        if(!r.ok) throw new Error('HTTP '+r.status);\n        return r.json();\n      })\n      .then(function(data){\n\n        allRows=Array.isArray(data)?data:[];\n\n        melakaRows=allRows.filter(function(r){\n          return String(r.state||'').trim().toLowerCase()==='melaka';\n        });\n\n        if(!melakaRows.length){\n          throw new Error('Tiada rekod Melaka ditemui.');\n        }\n\n        populateFilters();\n\n        el('poverty-loading').style.display='none';\n        el('poverty-area').style.display='';\n\n        redraw();\n      })\n      .catch(function(e){\n\n        el('poverty-loading').innerHTML=\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">'+\n          'Gagal memuatkan data'+\n          '<\/div>'+\n          '<p style=\"font-size:13px;color:#64748b\">'+\n          e.message+\n          '<\/p>';\n      });\n  }\n\n  if(document.readyState==='loading'){\n    document.addEventListener('DOMContentLoaded',init);\n  }else{\n    init();\n  }\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#ineq-wrap, #ineq-wrap * { box-sizing:border-box; }\n\n#ineq-wrap{\n  --bg:#f8fafc;--card:#ffffff;--soft:#eff6ff;--border:#dbeafe;\n  --text:#0f172a;--muted:#64748b;--accent:#2563eb;\n  --green:#16a34a;--red:#dc2626;--orange:#f97316;--purple:#7c3aed;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%;padding:28px;background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);border-radius:22px;font-family:Arial,sans-serif;color:var(--text);\n}\n\n#ineq-wrap .dash{max-width:1180px;margin:0 auto;}\n#ineq-wrap .hdr,#ineq-wrap .cc,#ineq-wrap .sc,#ineq-wrap .sbox{\n  background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow);\n}\n#ineq-wrap .hdr{border-radius:20px;padding:26px;margin-bottom:20px;}\n#ineq-wrap .eyebrow{color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px;}\n#ineq-wrap h2{margin:0;font-size:clamp(26px,4vw,44px);line-height:1.1;font-weight:800;}\n#ineq-wrap .hdesc{color:var(--muted);margin-top:10px;line-height:1.6;}\n\n#ineq-wrap .filter-bar{\n  display:grid;grid-template-columns:repeat(3,1fr);gap:14px;background:#fff;border:1px solid var(--border);\n  border-radius:16px;padding:18px;margin-bottom:20px;box-shadow:var(--shadow);\n}\n\n#ineq-wrap .cg{display:flex;flex-direction:column;gap:7px;}\n#ineq-wrap label{font-size:11px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;}\n\n#ineq-wrap select{\n  width:100%;min-height:44px;padding:10px 40px 10px 12px;border:1.5px solid #bfdbfe;border-radius:10px;\n  background-color:#fff;color:var(--text);font-size:14px;font-weight:600;outline:none;appearance:none;cursor:pointer;\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n  background-repeat:no-repeat;background-position:right 12px center;\n}\n\n#ineq-wrap select:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(37,99,235,.12);}\n\n#ineq-wrap .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;}\n#ineq-wrap .sc{border-radius:16px;padding:18px;border-left:4px solid var(--accent);}\n#ineq-wrap .sl{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;margin-bottom:8px;}\n#ineq-wrap .sv{font-size:25px;font-weight:800;}\n#ineq-wrap .su{color:var(--muted);font-size:12px;margin-top:4px;}\n\n#ineq-wrap .tab-bar{\n  display:flex;justify-content:space-between;gap:10px;background:var(--soft);\n  border:1px solid var(--border);border-radius:999px;padding:8px;margin-bottom:18px;overflow-x:auto;\n}\n\n#ineq-wrap .tbtn{\n  flex:1;border:0;background:transparent;padding:12px 18px;border-radius:999px;\n  color:var(--muted);font-weight:800;cursor:pointer;white-space:nowrap;\n}\n\n#ineq-wrap .tbtn.active{background:#fff;color:var(--accent);box-shadow:0 5px 14px rgba(15,23,42,.08);}\n\n#ineq-wrap .cc{border-radius:20px;padding:24px;margin-bottom:18px;}\n#ineq-wrap .ct{font-size:18px;font-weight:800;margin-bottom:5px;}\n#ineq-wrap .cs{color:var(--muted);font-size:13px;margin-bottom:18px;}\n#ineq-wrap .two{display:grid;grid-template-columns:1fr 1fr;gap:18px;}\n#ineq-wrap .chart-box{position:relative;height:350px;}\n#ineq-wrap .chart-box-sm{position:relative;height:320px;}\n#ineq-wrap .sbox{border-radius:20px;padding:60px 24px;text-align:center;color:var(--muted);}\n\n#ineq-wrap .spin{\n  width:38px;height:38px;border:3px solid var(--border);border-top-color:var(--accent);\n  border-radius:50%;animation:ineq-spin .8s linear infinite;margin:0 auto 16px;\n}\n\n@keyframes ineq-spin{to{transform:rotate(360deg);}}\n\n@media(max-width:1024px){\n  #ineq-wrap .filter-bar,#ineq-wrap .stats{grid-template-columns:repeat(2,1fr);}\n  #ineq-wrap .two{grid-template-columns:1fr;}\n}\n\n@media(max-width:600px){\n  #ineq-wrap{padding:14px;border-radius:16px;}\n  #ineq-wrap .filter-bar,#ineq-wrap .stats{grid-template-columns:1fr;}\n  #ineq-wrap .tab-bar{justify-content:flex-start;}\n  #ineq-wrap .tbtn{flex:0 0 auto;}\n  #ineq-wrap .chart-box,#ineq-wrap .chart-box-sm{height:300px;}\n}\n<\/style>\n\n<div id=\"ineq-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Ketidaksamaan Pendapatan Melaka<\/div>\n\n      <h2>Papan Pemuka Ketidaksamaan Pendapatan<\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi Pekali Gini bagi Melaka mengikut kawasan Parlimen dan DUN.\n        Nilai Gini yang lebih tinggi menunjukkan tahap ketidaksamaan pendapatan yang lebih tinggi.\n      <\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"ineq-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Parlimen<\/label>\n        <select id=\"ineq-parlimen\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>DUN<\/label>\n        <select id=\"ineq-dun\"><\/select>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"stats\" id=\"ineq-stats\" style=\"display:none\">\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"ineq-stat-year\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Purata Gini<\/div>\n        <div class=\"sv\" id=\"ineq-stat-avg\">\u2014<\/div>\n        <div class=\"su\">kawasan dipilih<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">DUN Tertinggi<\/div>\n        <div class=\"sv\" id=\"ineq-stat-high\">\u2014<\/div>\n        <div class=\"su\" id=\"ineq-stat-high-name\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Perubahan<\/div>\n        <div class=\"sv\" id=\"ineq-stat-change\">\u2014<\/div>\n        <div class=\"su\" id=\"ineq-stat-change-year\">berbanding tahun sebelumnya<\/div>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"sbox\" id=\"ineq-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod ketidaksamaan pendapatan Melaka.<\/div>\n    <\/div>\n\n    <div id=\"ineq-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"dun\">Perbandingan DUN<\/button>\n        <button class=\"tbtn\" data-tab=\"parlimen\">Parlimen<\/button>\n        <button class=\"tbtn\" data-tab=\"risk\">Tertinggi &#038; Terendah<\/button>\n        <button class=\"tbtn\" data-tab=\"change\">Perubahan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <div id=\"ineq-tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Pekali Gini Mengikut Masa<\/div>\n          <div class=\"cs\" id=\"ineq-trend-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"ineq-chart-trend\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"ineq-tab-dun\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Pekali Gini Mengikut DUN<\/div>\n          <div class=\"cs\" id=\"ineq-dun-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"ineq-chart-dun\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"ineq-tab-parlimen\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Pekali Gini Mengikut Parlimen<\/div>\n          <div class=\"cs\" id=\"ineq-parlimen-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"ineq-chart-parlimen\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"ineq-tab-risk\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">DUN dengan Ketidaksamaan Tertinggi<\/div>\n            <div class=\"cs\" id=\"ineq-high-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"ineq-chart-high\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">DUN dengan Ketidaksamaan Terendah<\/div>\n            <div class=\"cs\" id=\"ineq-low-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"ineq-chart-low\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"ineq-tab-change\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Perubahan Pekali Gini Mengikut DUN<\/div>\n            <div class=\"cs\" id=\"ineq-change-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"ineq-chart-change\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Purata Perubahan<\/div>\n            <div class=\"cs\" id=\"ineq-change-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"ineq-chart-change-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"ineq-tab-state\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Pekali Gini Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"ineq-state-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"ineq-chart-state\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL='https:\/\/api.data.gov.my\/data-catalogue?id=hh_inequality_dun&limit=2000';\n    var FIXED_STATE='Melaka';\n\n    var allRows=[];\n    var melakaRows=[];\n    var charts={};\n\n    Chart.defaults.color='#64748b';\n    Chart.defaults.borderColor='#dbeafe';\n    Chart.defaults.font.family='Arial, sans-serif';\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(r){\n      return parseInt(String(r.date||'').slice(0,4),10);\n    }\n\n    function num(v){\n      return Number(v)||0;\n    }\n\n    function gini(v){\n      return Number(v).toLocaleString(\n        'ms-MY',\n        {\n          minimumFractionDigits:3,\n          maximumFractionDigits:3\n        }\n      );\n    }\n\n    function changeText(v){\n      return (v>0?'+':'')+\n        Number(v).toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:3,\n            maximumFractionDigits:3\n          }\n        );\n    }\n\n    function avg(arr){\n      return arr.length\n        ? arr.reduce(function(a,b){\n            return a+b;\n          },0)\/arr.length\n        : 0;\n    }\n\n    function destroyChart(name){\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    function getSelected(){\n      return {\n        year:parseInt(el('ineq-year').value,10),\n        parlimen:el('ineq-parlimen').value,\n        dun:el('ineq-dun').value\n      };\n    }\n\n    function selectedRows(ignoreDun){\n\n      var s=getSelected();\n\n      return melakaRows.filter(function(r){\n\n        var p=\n          s.parlimen==='all' ||\n          r.parlimen===s.parlimen;\n\n        var d=\n          ignoreDun ||\n          s.dun==='all' ||\n          r.dun===s.dun;\n\n        return p&&d;\n\n      });\n    }\n\n    function rowsForYear(ignoreDun){\n\n      var s=getSelected();\n\n      return selectedRows(ignoreDun)\n        .filter(function(r){\n          return yr(r)===s.year;\n        });\n    }\n\n    function getPrevYear(current){\n\n      var years=\n        Array.from(\n          new Set(melakaRows.map(yr))\n        )\n        .filter(function(y){\n          return !isNaN(y)&&y<current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n    function populateFilters(){\n\n      var years=\n        Array.from(\n          new Set(melakaRows.map(yr))\n        )\n        .filter(function(y){\n          return !isNaN(y);\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('ineq-year').innerHTML=\n        years.map(function(y){\n          return '<option value=\"'+y+'\">'+y+'<\/option>';\n        }).join('');\n\n      if(years.length){\n        el('ineq-year').value=years[0];\n      }\n\n      var parls=\n        Array.from(\n          new Set(\n            melakaRows.map(function(r){\n              return r.parlimen;\n            })\n          )\n        ).sort();\n\n      el('ineq-parlimen').innerHTML=\n        '<option value=\"all\">Semua Parlimen<\/option>'+\n        parls.map(function(p){\n          return '<option value=\"'+p+'\">'+p+'<\/option>';\n        }).join('');\n\n      populateDunFilter();\n    }\n\n    function populateDunFilter(){\n\n      var s=getSelected();\n\n      var list=\n        melakaRows.filter(function(r){\n\n          return (\n            s.parlimen==='all' ||\n            r.parlimen===s.parlimen\n          );\n\n        });\n\n      var duns=\n        Array.from(\n          new Set(\n            list.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      el('ineq-dun').innerHTML=\n        '<option value=\"all\">Semua DUN<\/option>'+\n        duns.map(function(d){\n          return '<option value=\"'+d+'\">'+d+'<\/option>';\n        }).join('');\n    }\n\n    function updateCards(){\n\n      var s=getSelected();\n      var rows=rowsForYear(false);\n\n      var avgGini=\n        avg(\n          rows.map(function(r){\n            return num(r.gini);\n          })\n        );\n\n      var high=\n        rows.slice().sort(function(a,b){\n          return num(b.gini)-num(a.gini);\n        })[0];\n\n      var prev=getPrevYear(s.year);\n\n      var prevRows=\n        prev\n          ? selectedRows(false).filter(function(r){\n              return yr(r)===prev;\n            })\n          : [];\n\n      var prevAvg=\n        avg(\n          prevRows.map(function(r){\n            return num(r.gini);\n          })\n        );\n\n      var change=\n        prevRows.length\n          ? avgGini-prevAvg\n          : null;\n\n      el('ineq-stat-year').textContent=\n        s.year||'\u2014';\n\n      el('ineq-stat-avg').textContent=\n        rows.length\n          ? gini(avgGini)\n          : '\u2014';\n\n      el('ineq-stat-high').textContent=\n        high\n          ? gini(num(high.gini))\n          : '\u2014';\n\n      el('ineq-stat-high-name').textContent=\n        high\n          ? high.dun\n          : '\u2014';\n\n      el('ineq-stat-change').textContent=\n        change!==null\n          ? changeText(change)\n          : '\u2014';\n\n      el('ineq-stat-change-year').textContent=\n        prev\n          ? 'berbanding '+prev\n          : 'berbanding tahun sebelumnya';\n\n      el('ineq-stats').style.display='';\n    }\n\n    function tooltipGini(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            return ' '+\n              c.dataset.label+\n              ': '+\n              gini(c.parsed.y);\n\n          }\n        }\n      };\n    }\n\n    function buildTrendChart(){\n\n      var s=getSelected();\n      var rows=selectedRows(false);\n\n      var years=\n        Array.from(\n          new Set(rows.map(yr))\n        )\n        .sort(function(a,b){\n          return a-b;\n        });\n\n      var data=\n        years.map(function(y){\n\n          var r=\n            rows.filter(function(x){\n              return yr(x)===y;\n            });\n\n          return {\n            year:y,\n\n            value:\n              avg(\n                r.map(function(x){\n                  return num(x.gini);\n                })\n              )\n          };\n        });\n\n      el('ineq-trend-sub').textContent=\n        FIXED_STATE+\n        ' \u00b7 '+\n        (\n          s.parlimen==='all'\n            ? 'Semua Parlimen'\n            : s.parlimen\n        )+\n        ' \u00b7 '+\n        (\n          s.dun==='all'\n            ? 'Semua DUN'\n            : s.dun\n        );\n\n      destroyChart('trend');\n\n      charts.trend=\n        new Chart(\n          el('ineq-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.year;\n                }),\n\n              datasets:[{\n                label:'Pekali Gini',\n\n                data:\n                  data.map(function(d){\n                    return d.value;\n                  }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipGini()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return Number(v).toFixed(3);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildDunChart(){\n\n      var s=getSelected();\n      var rows=rowsForYear(true);\n\n      var duns=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      var data=\n        duns.map(function(dun){\n\n          var r=\n            rows.filter(function(x){\n              return x.dun===dun;\n            });\n\n          return {\n            dun:dun,\n\n            value:\n              avg(\n                r.map(function(x){\n                  return num(x.gini);\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('ineq-dun-sub').textContent=\n        s.year+\n        ' \u00b7 '+\n        (\n          s.parlimen==='all'\n            ? 'Semua Parlimen'\n            : s.parlimen\n        );\n\n      destroyChart('dun');\n\n      charts.dun=\n        new Chart(\n          el('ineq-chart-dun')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.dun;\n                }),\n\n              datasets:[{\n                label:'Pekali Gini',\n\n                data:\n                  data.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipGini()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return Number(v).toFixed(3);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildParlimenChart(){\n\n      var s=getSelected();\n\n      var rows=\n        melakaRows.filter(function(r){\n          return yr(r)===s.year;\n        });\n\n      var parls=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.parlimen;\n            })\n          )\n        ).sort();\n\n      var data=\n        parls.map(function(p){\n\n          var r=\n            rows.filter(function(x){\n              return x.parlimen===p;\n            });\n\n          return {\n            parlimen:p,\n\n            value:\n              avg(\n                r.map(function(x){\n                  return num(x.gini);\n                })\n              )\n          };\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('ineq-parlimen-sub').textContent=\n        s.year+\n        ' \u00b7 Purata peringkat Parlimen berdasarkan DUN';\n\n      destroyChart('parlimen');\n\n      charts.parlimen=\n        new Chart(\n          el('ineq-chart-parlimen')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.parlimen;\n                }),\n\n              datasets:[{\n                label:'Pekali Gini',\n\n                data:\n                  data.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:'rgba(249,115,22,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipGini()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return Number(v).toFixed(3);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildRiskCharts(){\n\n      var s=getSelected();\n      var rows=rowsForYear(true);\n\n      var duns=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      var data=\n        duns.map(function(dun){\n\n          var r=\n            rows.filter(function(x){\n              return x.dun===dun;\n            });\n\n          return {\n            dun:dun,\n\n            value:\n              avg(\n                r.map(function(x){\n                  return num(x.gini);\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      var high=data.slice(0,10);\n      var low=data.slice(-10).reverse();\n\n      el('ineq-high-sub').textContent=\n        s.year+\n        ' \u00b7 DUN dengan tahap ketidaksamaan pendapatan tertinggi';\n\n      el('ineq-low-sub').textContent=\n        s.year+\n        ' \u00b7 DUN dengan tahap ketidaksamaan pendapatan terendah';\n\n      function makeChart(\n        id,\n        name,\n        data,\n        color\n      ){\n\n        destroyChart(name);\n\n        charts[name]=\n          new Chart(\n            el(id).getContext('2d'),\n            {\n              type:'bar',\n\n              data:{\n                labels:\n                  data.map(function(d){\n                    return d.dun;\n                  }),\n\n                datasets:[{\n                  label:'Pekali Gini',\n\n                  data:\n                    data.map(function(d){\n                      return d.value;\n                    }),\n\n                  backgroundColor:color,\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n                responsive:true,\n                maintainAspectRatio:false,\n\n                plugins:{\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipGini()\n                },\n\n                scales:{\n                  x:{\n                    grid:{\n                      display:false\n                    },\n\n                    ticks:{\n                      maxRotation:45\n                    }\n                  },\n\n                  y:{\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(v){\n                        return Number(v).toFixed(3);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n      }\n\n      makeChart(\n        'ineq-chart-high',\n        'high',\n        high,\n        'rgba(220,38,38,.78)'\n      );\n\n      makeChart(\n        'ineq-chart-low',\n        'low',\n        low,\n        'rgba(22,163,74,.78)'\n      );\n    }\n\n    function buildChangeCharts(){\n\n      var s=getSelected();\n      var current=s.year;\n      var prev=getPrevYear(current);\n\n      if(!prev){\n\n        el('ineq-change-sub').textContent=\n          current+\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('ineq-change-trend-sub').textContent=\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n      var rowsCurrent=\n        melakaRows.filter(function(r){\n          return yr(r)===current;\n        });\n\n      var rowsPrev=\n        melakaRows.filter(function(r){\n          return yr(r)===prev;\n        });\n\n      var duns=\n        Array.from(\n          new Set(\n            rowsCurrent.map(function(r){\n              return r.dun;\n            })\n          )\n        ).sort();\n\n      var data=\n        duns.map(function(dun){\n\n          var c=\n            rowsCurrent.find(function(r){\n              return r.dun===dun;\n            });\n\n          var p=\n            rowsPrev.find(function(r){\n              return r.dun===dun;\n            });\n\n          return {\n            dun:dun,\n\n            change:\n              c&&p\n                ? num(c.gini)-num(p.gini)\n                : null\n          };\n\n        })\n        .filter(function(d){\n          return d.change!==null;\n        })\n        .sort(function(a,b){\n          return b.change-a.change;\n        });\n\n      el('ineq-change-sub').textContent=\n        current+\n        ' berbanding '+\n        prev+\n        ' \u00b7 nilai positif menunjukkan ketidaksamaan pendapatan meningkat';\n\n      destroyChart('change');\n\n      charts.change=\n        new Chart(\n          el('ineq-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.dun;\n                }),\n\n              datasets:[{\n                label:'Perubahan Gini',\n\n                data:\n                  data.map(function(d){\n                    return d.change;\n                  }),\n\n                backgroundColor:'rgba(124,58,237,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return ' Perubahan: '+\n                        changeText(c.parsed.y);\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return Number(v).toFixed(3);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var selected=\n        selectedRows(false);\n\n      var years=\n        Array.from(\n          new Set(\n            selected.map(yr)\n          )\n        )\n        .sort(function(a,b){\n          return a-b;\n        });\n\n      var trend=\n        years.map(function(y,i){\n\n          if(i===0){\n            return {\n              year:y,\n              change:null\n            };\n          }\n\n          var prevYear=\n            years[i-1];\n\n          var r=\n            selected.filter(function(x){\n              return yr(x)===y;\n            });\n\n          var p=\n            selected.filter(function(x){\n              return yr(x)===prevYear;\n            });\n\n          return {\n            year:\n              String(y)+\n              ' berbanding '+\n              String(prevYear),\n\n            change:\n              avg(\n                r.map(function(x){\n                  return num(x.gini);\n                })\n              )\n              -\n              avg(\n                p.map(function(x){\n                  return num(x.gini);\n                })\n              )\n          };\n\n        })\n        .filter(function(d){\n          return d.change!==null;\n        });\n\n      el('ineq-change-trend-sub').textContent=\n        'Purata perubahan Pekali Gini bagi tempoh data yang tersedia';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend=\n        new Chart(\n          el('ineq-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                trend.map(function(d){\n                  return d.year;\n                }),\n\n              datasets:[{\n                label:'Purata Perubahan',\n\n                data:\n                  trend.map(function(d){\n                    return d.change;\n                  }),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return ' Perubahan: '+\n                        changeText(c.parsed.y);\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return Number(v).toFixed(3);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildStateChart(){\n\n      var s=getSelected();\n\n      var rows=\n        allRows.filter(function(r){\n          return yr(r)===s.year;\n        });\n\n      var states=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.state;\n            })\n          )\n        ).sort();\n\n      var data=\n        states.map(function(state){\n\n          var r=\n            rows.filter(function(x){\n              return x.state===state;\n            });\n\n          return {\n            state:state,\n\n            value:\n              avg(\n                r.map(function(x){\n                  return num(x.gini);\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('ineq-state-sub').textContent=\n        s.year+\n        ' \u00b7 semua negeri \u00b7 purata mengikut DUN';\n\n      destroyChart('state');\n\n      charts.state=\n        new Chart(\n          el('ineq-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:[{\n                label:'Pekali Gini',\n\n                data:\n                  data.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.state==='Melaka'\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(37,99,235,.70)';\n\n                  }),\n\n                borderColor:\n                  data.map(function(d){\n\n                    return d.state==='Melaka'\n                      ? '#f97316'\n                      : 'rgba(37,99,235,.70)';\n\n                  }),\n\n                borderWidth:\n                  data.map(function(d){\n\n                    return d.state==='Melaka'\n                      ? 3\n                      : 0;\n\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipGini()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      var label=\n                        context.tick.label;\n\n                      return label==='Melaka'\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      var label=\n                        context.tick.label;\n\n                      return {\n                        weight:\n                          label==='Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          label==='Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return Number(v).toFixed(3);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function activeTab(){\n\n      var a=\n        document.querySelector(\n          '#ineq-wrap .tbtn.active'\n        );\n\n      return a\n        ? a.getAttribute('data-tab')\n        : 'trend';\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      if(activeTab()==='trend'){\n        buildTrendChart();\n      }\n\n      if(activeTab()==='dun'){\n        buildDunChart();\n      }\n\n      if(activeTab()==='parlimen'){\n        buildParlimenChart();\n      }\n\n      if(activeTab()==='risk'){\n        buildRiskCharts();\n      }\n\n      if(activeTab()==='change'){\n        buildChangeCharts();\n      }\n\n      if(activeTab()==='state'){\n        buildStateChart();\n      }\n    }\n\n    document\n      .querySelectorAll(\n        '#ineq-wrap .tbtn'\n      )\n      .forEach(function(btn){\n\n        btn.addEventListener(\n          'click',\n          function(){\n\n            document\n              .querySelectorAll(\n                '#ineq-wrap .tbtn'\n              )\n              .forEach(function(b){\n\n                b.classList.remove(\n                  'active'\n                );\n\n              });\n\n            btn.classList.add(\n              'active'\n            );\n\n            [\n              'trend',\n              'dun',\n              'parlimen',\n              'risk',\n              'change',\n              'state'\n            ].forEach(function(t){\n\n              el(\n                'ineq-tab-'+t\n              ).style.display='none';\n\n            });\n\n            el(\n              'ineq-tab-'+\n              btn.getAttribute('data-tab')\n            ).style.display='';\n\n            redraw();\n          }\n        );\n      });\n\n    el('ineq-parlimen')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateDunFilter();\n          redraw();\n\n        }\n      );\n\n    [\n      'ineq-year',\n      'ineq-dun'\n    ].forEach(function(id){\n\n      el(id)\n        .addEventListener(\n          'change',\n          redraw\n        );\n\n    });\n\n    fetch(API_URL)\n\n      .then(function(r){\n\n        if(!r.ok){\n\n          throw new Error(\n            'HTTP '+r.status\n          );\n\n        }\n\n        return r.json();\n      })\n\n      .then(function(data){\n\n        allRows=\n          Array.isArray(data)\n            ? data\n            : [];\n\n        melakaRows=\n          allRows.filter(function(r){\n\n            return String(\n              r.state||''\n            )\n            .trim()\n            .toLowerCase()\n            ===\n            'melaka';\n\n          });\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            'Tiada rekod Melaka ditemui.'\n          );\n\n        }\n\n        populateFilters();\n\n        el('ineq-loading')\n          .style.display='none';\n\n        el('ineq-area')\n          .style.display='';\n\n        redraw();\n      })\n\n      .catch(function(e){\n\n        el('ineq-loading')\n          .innerHTML=\n\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">'+\n          'Gagal memuatkan data'+\n          '<\/div>'+\n          '<p style=\"font-size:13px;color:#64748b\">'+\n          e.message+\n          '<\/p>';\n\n      });\n\n  }\n\n  if(document.readyState==='loading'){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#exp-wrap, #exp-wrap * { box-sizing:border-box; }\n#exp-wrap{\n  --bg:#f8fafc;--card:#ffffff;--soft:#eff6ff;--border:#dbeafe;\n  --text:#0f172a;--muted:#64748b;--accent:#2563eb;\n  --green:#16a34a;--red:#dc2626;--orange:#f97316;--purple:#7c3aed;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%;padding:28px;background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);border-radius:22px;font-family:Arial,sans-serif;color:var(--text);\n}\n#exp-wrap .dash{max-width:1180px;margin:0 auto;}\n#exp-wrap .hdr,#exp-wrap .cc,#exp-wrap .sc,#exp-wrap .sbox{background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow);}\n#exp-wrap .hdr{border-radius:20px;padding:26px;margin-bottom:20px;}\n#exp-wrap .eyebrow{color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px;}\n#exp-wrap h2{margin:0;font-size:clamp(26px,4vw,44px);line-height:1.1;font-weight:800;}\n#exp-wrap .hdesc{color:var(--muted);margin-top:10px;line-height:1.6;}\n#exp-wrap .filter-bar{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;background:#fff;border:1px solid var(--border);border-radius:16px;padding:18px;margin-bottom:20px;box-shadow:var(--shadow);}\n#exp-wrap .cg{display:flex;flex-direction:column;gap:7px;}\n#exp-wrap label{font-size:11px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;}\n#exp-wrap select{width:100%;min-height:44px;padding:10px 40px 10px 12px;border:1.5px solid #bfdbfe;border-radius:10px;background-color:#fff;color:var(--text);font-size:14px;font-weight:600;outline:none;appearance:none;cursor:pointer;background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");background-repeat:no-repeat;background-position:right 12px center;}\n#exp-wrap select:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(37,99,235,.12);}\n#exp-wrap .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;}\n#exp-wrap .sc{border-radius:16px;padding:18px;border-left:4px solid var(--accent);}\n#exp-wrap .sl{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;margin-bottom:8px;}\n#exp-wrap .sv{font-size:25px;font-weight:800;}\n#exp-wrap .su{color:var(--muted);font-size:12px;margin-top:4px;}\n#exp-wrap .tab-bar{display:flex;justify-content:space-between;gap:10px;background:var(--soft);border:1px solid var(--border);border-radius:999px;padding:8px;margin-bottom:18px;overflow-x:auto;}\n#exp-wrap .tbtn{flex:1;border:0;background:transparent;padding:12px 18px;border-radius:999px;color:var(--muted);font-weight:800;cursor:pointer;white-space:nowrap;}\n#exp-wrap .tbtn.active{background:#fff;color:var(--accent);box-shadow:0 5px 14px rgba(15,23,42,.08);}\n#exp-wrap .cc{border-radius:20px;padding:24px;margin-bottom:18px;}\n#exp-wrap .ct{font-size:18px;font-weight:800;margin-bottom:5px;}\n#exp-wrap .cs{color:var(--muted);font-size:13px;margin-bottom:18px;}\n#exp-wrap .two{display:grid;grid-template-columns:1fr 1fr;gap:18px;}\n#exp-wrap .chart-box{position:relative;height:350px;}\n#exp-wrap .chart-box-sm{position:relative;height:320px;}\n#exp-wrap .sbox{border-radius:20px;padding:60px 24px;text-align:center;color:var(--muted);}\n#exp-wrap .spin{width:38px;height:38px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:exp-spin .8s linear infinite;margin:0 auto 16px;}\n@keyframes exp-spin{to{transform:rotate(360deg);}}\n@media(max-width:1024px){#exp-wrap .filter-bar,#exp-wrap .stats{grid-template-columns:repeat(2,1fr);}#exp-wrap .two{grid-template-columns:1fr;}}\n@media(max-width:600px){#exp-wrap{padding:14px;border-radius:16px;}#exp-wrap .filter-bar,#exp-wrap .stats{grid-template-columns:1fr;}#exp-wrap .tab-bar{justify-content:flex-start;}#exp-wrap .tbtn{flex:0 0 auto;}#exp-wrap .chart-box,#exp-wrap .chart-box-sm{height:300px;}}\n<\/style>\n\n<div id=\"exp-wrap\">\n  <div class=\"dash\">\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Perbelanjaan Isi Rumah Melaka<\/div>\n      <h2>Papan Pemuka Perbelanjaan Isi Rumah<\/h2>\n      <p class=\"hdesc\">Visualisasi purata perbelanjaan kasar bulanan isi rumah di Melaka mengikut kawasan Parlimen dan DUN.<\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n      <div class=\"cg\"><label>Tahun<\/label><select id=\"exp-year\"><\/select><\/div>\n      <div class=\"cg\"><label>Parlimen<\/label><select id=\"exp-parlimen\"><\/select><\/div>\n      <div class=\"cg\"><label>DUN<\/label><select id=\"exp-dun\"><\/select><\/div>\n    <\/div>\n\n    <div class=\"stats\" id=\"exp-stats\" style=\"display:none\">\n      <div class=\"sc\"><div class=\"sl\">Tahun Dipilih<\/div><div class=\"sv\" id=\"exp-stat-year\">\u2014<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">Purata Perbelanjaan<\/div><div class=\"sv\" id=\"exp-stat-avg\">\u2014<\/div><div class=\"su\">RM \/ bulan<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">DUN Tertinggi<\/div><div class=\"sv\" id=\"exp-stat-high\">\u2014<\/div><div class=\"su\" id=\"exp-stat-high-name\">\u2014<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">Pertumbuhan<\/div><div class=\"sv\" id=\"exp-stat-growth\">\u2014<\/div><div class=\"su\" id=\"exp-stat-growth-year\">berbanding tahun sebelumnya<\/div><\/div>\n    <\/div>\n\n    <div class=\"sbox\" id=\"exp-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod perbelanjaan isi rumah Melaka.<\/div>\n    <\/div>\n\n    <div id=\"exp-area\" style=\"display:none\">\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"dun\">Perbandingan DUN<\/button>\n        <button class=\"tbtn\" data-tab=\"parlimen\">Parlimen<\/button>\n        <button class=\"tbtn\" data-tab=\"topbottom\">Tertinggi &#038; Terendah<\/button>\n        <button class=\"tbtn\" data-tab=\"growth\">Pertumbuhan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <div id=\"exp-tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Perbelanjaan Mengikut Masa<\/div>\n          <div class=\"cs\" id=\"exp-trend-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"exp-chart-trend\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"exp-tab-dun\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Perbelanjaan Mengikut DUN<\/div>\n          <div class=\"cs\" id=\"exp-dun-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"exp-chart-dun\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"exp-tab-parlimen\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Perbelanjaan Mengikut Parlimen<\/div>\n          <div class=\"cs\" id=\"exp-parlimen-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"exp-chart-parlimen\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"exp-tab-topbottom\" style=\"display:none\">\n        <div class=\"two\">\n          <div class=\"cc\">\n            <div class=\"ct\">10 DUN dengan Perbelanjaan Tertinggi<\/div>\n            <div class=\"cs\" id=\"exp-top-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"exp-chart-top\"><\/canvas><\/div>\n          <\/div>\n          <div class=\"cc\">\n            <div class=\"ct\">10 DUN dengan Perbelanjaan Terendah<\/div>\n            <div class=\"cs\" id=\"exp-bottom-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"exp-chart-bottom\"><\/canvas><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"exp-tab-growth\" style=\"display:none\">\n        <div class=\"two\">\n          <div class=\"cc\">\n            <div class=\"ct\">Pertumbuhan Perbelanjaan Mengikut DUN<\/div>\n            <div class=\"cs\" id=\"exp-growth-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"exp-chart-growth\"><\/canvas><\/div>\n          <\/div>\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Purata Pertumbuhan<\/div>\n            <div class=\"cs\" id=\"exp-growth-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"exp-chart-growth-trend\"><\/canvas><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"exp-tab-state\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Perbelanjaan Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"exp-state-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"exp-chart-state\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  function init(){\n    if(typeof Chart === 'undefined'){ setTimeout(init,500); return; }\n\n    var API_URL='https:\/\/api.data.gov.my\/data-catalogue?id=hh_expenditure_dun&limit=2000';\n    var FIXED_STATE='Melaka';\n    var allRows=[], melakaRows=[], charts={};\n\n    Chart.defaults.color='#64748b';\n    Chart.defaults.borderColor='#dbeafe';\n    Chart.defaults.font.family='Arial, sans-serif';\n\n    function el(id){return document.getElementById(id);}\n    function yr(r){return parseInt(String(r.date||'').slice(0,4),10);}\n    function num(v){return Number(v)||0;}\n    function rm(v){return 'RM '+Number(v).toLocaleString('ms-MY',{maximumFractionDigits:0});}\n    function pct(v){return Number(v).toLocaleString('ms-MY',{maximumFractionDigits:1})+'%';}\n    function avg(arr){return arr.length?arr.reduce(function(a,b){return a+b;},0)\/arr.length:0;}\n    function destroyChart(name){if(charts[name]){charts[name].destroy();delete charts[name];}}\n\n    function getSelected(){\n      return {\n        year:parseInt(el('exp-year').value,10),\n        parlimen:el('exp-parlimen').value,\n        dun:el('exp-dun').value\n      };\n    }\n\n    function selectedRows(ignoreDun){\n      var s=getSelected();\n      return melakaRows.filter(function(r){\n        var p=s.parlimen==='all'||r.parlimen===s.parlimen;\n        var d=ignoreDun||s.dun==='all'||r.dun===s.dun;\n        return p&&d;\n      });\n    }\n\n    function rowsForYear(ignoreDun){\n      var s=getSelected();\n      return selectedRows(ignoreDun).filter(function(r){return yr(r)===s.year;});\n    }\n\n    function getPrevYear(current){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){return !isNaN(y)&&y<current;})\n        .sort(function(a,b){return b-a;});\n      return years.length?years[0]:null;\n    }\n\n    function populateFilters(){\n      var years=Array.from(new Set(melakaRows.map(yr))).filter(function(y){return !isNaN(y);}).sort(function(a,b){return b-a;});\n\n      el('exp-year').innerHTML=years.map(function(y){\n        return '<option value=\"'+y+'\">'+y+'<\/option>';\n      }).join('');\n\n      if(years.length) el('exp-year').value=years[0];\n\n      var parls=Array.from(new Set(melakaRows.map(function(r){return r.parlimen;}))).sort();\n\n      el('exp-parlimen').innerHTML=\n        '<option value=\"all\">Semua Parlimen<\/option>'+\n        parls.map(function(p){\n          return '<option value=\"'+p+'\">'+p+'<\/option>';\n        }).join('');\n\n      populateDunFilter();\n    }\n\n    function populateDunFilter(){\n      var s=getSelected();\n\n      var list=melakaRows.filter(function(r){\n        return s.parlimen==='all'||r.parlimen===s.parlimen;\n      });\n\n      var duns=Array.from(new Set(list.map(function(r){return r.dun;}))).sort();\n\n      el('exp-dun').innerHTML=\n        '<option value=\"all\">Semua DUN<\/option>'+\n        duns.map(function(d){\n          return '<option value=\"'+d+'\">'+d+'<\/option>';\n        }).join('');\n    }\n\n    function updateCards(){\n      var s=getSelected();\n      var rows=rowsForYear(false);\n\n      var avgExp=avg(rows.map(function(r){\n        return num(r.expenditure);\n      }));\n\n      var high=rows.slice().sort(function(a,b){\n        return num(b.expenditure)-num(a.expenditure);\n      })[0];\n\n      var prev=getPrevYear(s.year);\n\n      var prevRows=prev\n        ? selectedRows(false).filter(function(r){return yr(r)===prev;})\n        : [];\n\n      var prevAvg=avg(prevRows.map(function(r){\n        return num(r.expenditure);\n      }));\n\n      var growth=prevRows.length&&prevAvg\n        ? ((avgExp-prevAvg)\/prevAvg)*100\n        : null;\n\n      el('exp-stat-year').textContent=s.year||'\u2014';\n      el('exp-stat-avg').textContent=rows.length?rm(avgExp):'\u2014';\n      el('exp-stat-high').textContent=high?rm(num(high.expenditure)):'\u2014';\n      el('exp-stat-high-name').textContent=high?high.dun:'\u2014';\n      el('exp-stat-growth').textContent=growth!==null?pct(growth):'\u2014';\n\n      el('exp-stat-growth-year').textContent=\n        prev\n          ? 'berbanding '+prev\n          : 'berbanding tahun sebelumnya';\n\n      el('exp-stats').style.display='';\n    }\n\n    function tooltipRM(){\n      return {\n        backgroundColor:'#0f172a',\n        callbacks:{\n          label:function(c){\n            return ' '+c.dataset.label+': '+rm(c.parsed.y);\n          }\n        }\n      };\n    }\n\n    function buildTrendChart(){\n      var s=getSelected(), rows=selectedRows(false);\n\n      var years=Array.from(new Set(rows.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var data=years.map(function(y){\n        var r=rows.filter(function(x){return yr(x)===y;});\n\n        return {\n          year:y,\n          value:avg(r.map(function(x){\n            return num(x.expenditure);\n          }))\n        };\n      });\n\n      el('exp-trend-sub').textContent=\n        FIXED_STATE+' \u00b7 '+\n        (s.parlimen==='all'?'Semua Parlimen':s.parlimen)+' \u00b7 '+\n        (s.dun==='all'?'Semua DUN':s.dun);\n\n      destroyChart('trend');\n\n      charts.trend=new Chart(el('exp-chart-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:data.map(function(d){return d.year;}),\n          datasets:[{\n            label:'Perbelanjaan',\n            data:data.map(function(d){return d.value;}),\n            borderColor:'#2563eb',\n            backgroundColor:'rgba(37,99,235,.15)',\n            fill:true,\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:4\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipRM()\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return 'RM '+Number(v).toLocaleString('ms-MY');\n                }\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function buildDunChart(){\n      var s=getSelected(), rows=rowsForYear(true);\n\n      var duns=Array.from(new Set(rows.map(function(r){return r.dun;}))).sort();\n\n      var data=duns.map(function(dun){\n        var r=rows.filter(function(x){return x.dun===dun;});\n\n        return {\n          dun:dun,\n          value:avg(r.map(function(x){\n            return num(x.expenditure);\n          }))\n        };\n      }).sort(function(a,b){return b.value-a.value;});\n\n      el('exp-dun-sub').textContent=\n        s.year+' \u00b7 '+\n        (s.parlimen==='all'?'Semua Parlimen':s.parlimen);\n\n      destroyChart('dun');\n\n      charts.dun=new Chart(el('exp-chart-dun').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.dun;}),\n          datasets:[{\n            label:'Perbelanjaan',\n            data:data.map(function(d){return d.value;}),\n            backgroundColor:'rgba(37,99,235,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipRM()\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return 'RM '+Number(v).toLocaleString('ms-MY');\n                }\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function buildParlimenChart(){\n      var s=getSelected();\n\n      var rows=melakaRows.filter(function(r){\n        return yr(r)===s.year;\n      });\n\n      var parls=Array.from(new Set(rows.map(function(r){\n        return r.parlimen;\n      }))).sort();\n\n      var data=parls.map(function(p){\n        var r=rows.filter(function(x){\n          return x.parlimen===p;\n        });\n\n        return {\n          parlimen:p,\n          value:avg(r.map(function(x){\n            return num(x.expenditure);\n          }))\n        };\n      }).sort(function(a,b){return b.value-a.value;});\n\n      el('exp-parlimen-sub').textContent=\n        s.year+' \u00b7 Purata peringkat Parlimen berdasarkan DUN';\n\n      destroyChart('parlimen');\n\n      charts.parlimen=new Chart(el('exp-chart-parlimen').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.parlimen;}),\n          datasets:[{\n            label:'Perbelanjaan',\n            data:data.map(function(d){return d.value;}),\n            backgroundColor:'rgba(249,115,22,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipRM()\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return 'RM '+Number(v).toLocaleString('ms-MY');\n                }\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function buildTopBottomCharts(){\n      var s=getSelected(), rows=rowsForYear(true);\n\n      var duns=Array.from(new Set(rows.map(function(r){return r.dun;}))).sort();\n\n      var data=duns.map(function(dun){\n        var r=rows.filter(function(x){return x.dun===dun;});\n\n        return {\n          dun:dun,\n          value:avg(r.map(function(x){\n            return num(x.expenditure);\n          }))\n        };\n      }).sort(function(a,b){return b.value-a.value;});\n\n      var top=data.slice(0,10);\n      var bottom=data.slice(-10).reverse();\n\n      el('exp-top-sub').textContent=\n        s.year+' \u00b7 kawasan DUN dengan perbelanjaan tertinggi';\n\n      el('exp-bottom-sub').textContent=\n        s.year+' \u00b7 kawasan DUN dengan perbelanjaan terendah';\n\n      function makeChart(id,name,data,color){\n        destroyChart(name);\n\n        charts[name]=new Chart(el(id).getContext('2d'),{\n          type:'bar',\n          data:{\n            labels:data.map(function(d){return d.dun;}),\n            datasets:[{\n              label:'Perbelanjaan',\n              data:data.map(function(d){return d.value;}),\n              backgroundColor:color,\n              borderRadius:8\n            }]\n          },\n          options:{\n            responsive:true,\n            maintainAspectRatio:false,\n            plugins:{\n              legend:{display:false},\n              tooltip:tooltipRM()\n            },\n            scales:{\n              x:{grid:{display:false},ticks:{maxRotation:45}},\n              y:{\n                grid:{color:'#dbeafe'},\n                ticks:{\n                  callback:function(v){\n                    return 'RM '+Number(v).toLocaleString('ms-MY');\n                  }\n                }\n              }\n            }\n          }\n        });\n      }\n\n      makeChart('exp-chart-top','top',top,'rgba(22,163,74,.78)');\n      makeChart('exp-chart-bottom','bottom',bottom,'rgba(220,38,38,.78)');\n    }\n\n    function buildGrowthCharts(){\n      var s=getSelected();\n      var current=s.year;\n      var prev=getPrevYear(current);\n\n      if(!prev){\n        el('exp-growth-sub').textContent=\n          current+' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('exp-growth-trend-sub').textContent=\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('growth');\n        destroyChart('growthTrend');\n        return;\n      }\n\n      var rowsCurrent=melakaRows.filter(function(r){\n        return yr(r)===current;\n      });\n\n      var rowsPrev=melakaRows.filter(function(r){\n        return yr(r)===prev;\n      });\n\n      var duns=Array.from(new Set(rowsCurrent.map(function(r){\n        return r.dun;\n      }))).sort();\n\n      var data=duns.map(function(dun){\n        var c=rowsCurrent.find(function(r){\n          return r.dun===dun;\n        });\n\n        var p=rowsPrev.find(function(r){\n          return r.dun===dun;\n        });\n\n        var cv=c?num(c.expenditure):0;\n        var pv=p?num(p.expenditure):0;\n\n        return {\n          dun:dun,\n          growth:pv?((cv-pv)\/pv)*100:null\n        };\n      })\n      .filter(function(d){return d.growth!==null;})\n      .sort(function(a,b){return b.growth-a.growth;});\n\n      el('exp-growth-sub').textContent=\n        current+' berbanding '+prev+\n        ' \u00b7 kadar pertumbuhan perbelanjaan mengikut DUN';\n\n      destroyChart('growth');\n\n      charts.growth=new Chart(el('exp-chart-growth').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.dun;}),\n          datasets:[{\n            label:'Kadar Pertumbuhan',\n            data:data.map(function(d){return d.growth;}),\n            backgroundColor:'rgba(124,58,237,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Pertumbuhan: '+pct(c.parsed.y);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return v+'%';\n                }\n              }\n            }\n          }\n        }\n      });\n\n      var selected=selectedRows(false);\n\n      var years=Array.from(new Set(selected.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var trend=years.map(function(y,i){\n        if(i===0){\n          return {year:y,growth:null};\n        }\n\n        var prevYear=years[i-1];\n\n        var r=selected.filter(function(x){\n          return yr(x)===y;\n        });\n\n        var p=selected.filter(function(x){\n          return yr(x)===prevYear;\n        });\n\n        var avgNow=avg(r.map(function(x){\n          return num(x.expenditure);\n        }));\n\n        var avgPrev=avg(p.map(function(x){\n          return num(x.expenditure);\n        }));\n\n        return {\n          year:String(y)+' berbanding '+String(prevYear),\n          growth:avgPrev?((avgNow-avgPrev)\/avgPrev)*100:null\n        };\n      }).filter(function(d){return d.growth!==null;});\n\n      el('exp-growth-trend-sub').textContent=\n        'Purata pertumbuhan perbelanjaan bagi tempoh data yang tersedia';\n\n      destroyChart('growthTrend');\n\n      charts.growthTrend=new Chart(el('exp-chart-growth-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:trend.map(function(d){return d.year;}),\n          datasets:[{\n            label:'Purata Pertumbuhan',\n            data:trend.map(function(d){return d.growth;}),\n            borderColor:'#7c3aed',\n            backgroundColor:'rgba(124,58,237,.15)',\n            fill:true,\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:4\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Pertumbuhan: '+pct(c.parsed.y);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return v+'%';\n                }\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function buildStateChart(){\n      var s=getSelected();\n\n      var rows=allRows.filter(function(r){\n        return yr(r)===s.year;\n      });\n\n      var states=Array.from(new Set(rows.map(function(r){\n        return r.state;\n      }))).sort();\n\n      var data=states.map(function(state){\n        var r=rows.filter(function(x){\n          return x.state===state;\n        });\n\n        return {\n          state:state,\n          value:avg(r.map(function(x){\n            return num(x.expenditure);\n          }))\n        };\n      }).sort(function(a,b){return b.value-a.value;});\n\n      el('exp-state-sub').textContent=\n        s.year+' \u00b7 semua negeri \u00b7 purata mengikut DUN';\n\n      destroyChart('state');\n\n      charts.state=new Chart(el('exp-chart-state').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.state;}),\n          datasets:[{\n            label:'Perbelanjaan',\n            data:data.map(function(d){return d.value;}),\n            backgroundColor:data.map(function(d){\n              return d.state==='Melaka'\n                ? 'rgba(249,115,22,.95)'\n                : 'rgba(37,99,235,.70)';\n            }),\n            borderColor:data.map(function(d){\n              return d.state==='Melaka'\n                ? '#f97316'\n                : 'rgba(37,99,235,.70)';\n            }),\n            borderWidth:data.map(function(d){\n              return d.state==='Melaka'?3:0;\n            }),\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipRM()\n          },\n          scales:{\n            x:{\n              grid:{display:false},\n              ticks:{\n                maxRotation:45,\n                color:function(c){\n                  return c.tick.label==='Melaka'\n                    ? '#f97316'\n                    : '#64748b';\n                },\n                font:function(c){\n                  return {\n                    weight:c.tick.label==='Melaka'?'800':'400',\n                    size:c.tick.label==='Melaka'?13:12\n                  };\n                }\n              }\n            },\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{\n                callback:function(v){\n                  return 'RM '+Number(v).toLocaleString('ms-MY');\n                }\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function activeTab(){\n      var a=document.querySelector('#exp-wrap .tbtn.active');\n      return a?a.getAttribute('data-tab'):'trend';\n    }\n\n    function redraw(){\n      updateCards();\n\n      if(activeTab()==='trend')buildTrendChart();\n      if(activeTab()==='dun')buildDunChart();\n      if(activeTab()==='parlimen')buildParlimenChart();\n      if(activeTab()==='topbottom')buildTopBottomCharts();\n      if(activeTab()==='growth')buildGrowthCharts();\n      if(activeTab()==='state')buildStateChart();\n    }\n\n    document.querySelectorAll('#exp-wrap .tbtn').forEach(function(btn){\n      btn.addEventListener('click',function(){\n\n        document.querySelectorAll('#exp-wrap .tbtn').forEach(function(b){\n          b.classList.remove('active');\n        });\n\n        btn.classList.add('active');\n\n        ['trend','dun','parlimen','topbottom','growth','state'].forEach(function(t){\n          el('exp-tab-'+t).style.display='none';\n        });\n\n        el('exp-tab-'+btn.getAttribute('data-tab')).style.display='';\n\n        redraw();\n      });\n    });\n\n    el('exp-parlimen').addEventListener('change',function(){\n      populateDunFilter();\n      redraw();\n    });\n\n    ['exp-year','exp-dun'].forEach(function(id){\n      el(id).addEventListener('change',redraw);\n    });\n\n    fetch(API_URL)\n      .then(function(r){\n        if(!r.ok)throw new Error('HTTP '+r.status);\n        return r.json();\n      })\n      .then(function(data){\n\n        allRows=Array.isArray(data)?data:[];\n\n        melakaRows=allRows.filter(function(r){\n          return String(r.state||'').trim().toLowerCase()==='melaka';\n        });\n\n        if(!melakaRows.length){\n          throw new Error('Tiada rekod Melaka ditemui.');\n        }\n\n        populateFilters();\n\n        el('exp-loading').style.display='none';\n        el('exp-area').style.display='';\n\n        redraw();\n      })\n      .catch(function(e){\n\n        el('exp-loading').innerHTML=\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">'+\n          'Gagal memuatkan data'+\n          '<\/div>'+\n          '<p style=\"font-size:13px;color:#64748b\">'+\n          e.message+\n          '<\/p>';\n      });\n  }\n\n  if(document.readyState==='loading'){\n    document.addEventListener('DOMContentLoaded',init);\n  }else{\n    init();\n  }\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<div style=\"text-align: center; margin: 40px 0 30px 0;\">\n  <h2 style=\"\n    font-size: 32px;\n    font-weight: 700;\n    color: #1f3c88;\n    margin-bottom: 10px;\n    letter-spacing: 0.5px;\n  \">\n    Dashboard Kategori Pendidikan\n  <\/h2>\n\n  <div style=\"\n    width: 80px;\n    height: 4px;\n    background-color: #1f3c88;\n    margin: 0 auto;\n    border-radius: 4px;\n  \"><\/div>\n<\/div>\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#edu-wrap, #edu-wrap * { box-sizing:border-box; }\n#edu-wrap{\n  --bg:#f8fafc;--card:#ffffff;--soft:#eff6ff;--border:#dbeafe;\n  --text:#0f172a;--muted:#64748b;--accent:#2563eb;\n  --green:#16a34a;--red:#dc2626;--orange:#f97316;--purple:#7c3aed;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%;padding:28px;background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);border-radius:22px;font-family:Arial,sans-serif;color:var(--text);\n}\n#edu-wrap .dash{max-width:1180px;margin:0 auto;}\n#edu-wrap .hdr,#edu-wrap .cc,#edu-wrap .sc,#edu-wrap .sbox{background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow);}\n#edu-wrap .hdr{border-radius:20px;padding:26px;margin-bottom:20px;}\n#edu-wrap .eyebrow{color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px;}\n#edu-wrap h2{margin:0;font-size:clamp(26px,4vw,44px);line-height:1.1;font-weight:800;}\n#edu-wrap .hdesc{color:var(--muted);margin-top:10px;line-height:1.6;}\n#edu-wrap .filter-bar{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;background:#fff;border:1px solid var(--border);border-radius:16px;padding:18px;margin-bottom:20px;box-shadow:var(--shadow);}\n#edu-wrap .cg{display:flex;flex-direction:column;gap:7px;}\n#edu-wrap label{font-size:11px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;}\n#edu-wrap select{width:100%;min-height:44px;padding:10px 40px 10px 12px;border:1.5px solid #bfdbfe;border-radius:10px;background-color:#fff;color:var(--text);font-size:14px;font-weight:600;outline:none;appearance:none;cursor:pointer;background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");background-repeat:no-repeat;background-position:right 12px center;}\n#edu-wrap select:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(37,99,235,.12);}\n#edu-wrap .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;}\n#edu-wrap .sc{border-radius:16px;padding:18px;border-left:4px solid var(--accent);}\n#edu-wrap .sl{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;margin-bottom:8px;}\n#edu-wrap .sv{font-size:25px;font-weight:800;}\n#edu-wrap .su{color:var(--muted);font-size:12px;margin-top:4px;}\n#edu-wrap .tab-bar{display:flex;justify-content:space-between;gap:10px;background:var(--soft);border:1px solid var(--border);border-radius:999px;padding:8px;margin-bottom:18px;overflow-x:auto;}\n#edu-wrap .tbtn{flex:1;border:0;background:transparent;padding:12px 18px;border-radius:999px;color:var(--muted);font-weight:800;cursor:pointer;white-space:nowrap;}\n#edu-wrap .tbtn.active{background:#fff;color:var(--accent);box-shadow:0 5px 14px rgba(15,23,42,.08);}\n#edu-wrap .cc{border-radius:20px;padding:24px;margin-bottom:18px;}\n#edu-wrap .ct{font-size:18px;font-weight:800;margin-bottom:5px;}\n#edu-wrap .cs{color:var(--muted);font-size:13px;margin-bottom:18px;}\n#edu-wrap .two{display:grid;grid-template-columns:1fr 1fr;gap:18px;}\n#edu-wrap .chart-box{position:relative;height:350px;}\n#edu-wrap .chart-box-sm{position:relative;height:320px;}\n#edu-wrap .sbox{border-radius:20px;padding:60px 24px;text-align:center;color:var(--muted);}\n#edu-wrap .spin{width:38px;height:38px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:edu-spin .8s linear infinite;margin:0 auto 16px;}\n@keyframes edu-spin{to{transform:rotate(360deg);}}\n@media(max-width:1024px){#edu-wrap .filter-bar,#edu-wrap .stats{grid-template-columns:repeat(2,1fr);}#edu-wrap .two{grid-template-columns:1fr;}}\n@media(max-width:600px){#edu-wrap{padding:14px;border-radius:16px;}#edu-wrap .filter-bar,#edu-wrap .stats{grid-template-columns:1fr;}#edu-wrap .tab-bar{justify-content:flex-start;}#edu-wrap .tbtn{flex:0 0 auto;}#edu-wrap .chart-box,#edu-wrap .chart-box-sm{height:300px;}}\n<\/style>\n\n<div id=\"edu-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Institusi Pendidikan Awam Melaka<\/div>\n      <h2>Papan Pemuka Institusi Pendidikan Awam<\/h2>\n      <p class=\"hdesc\">\n        Visualisasi sekolah dan institusi pendidikan awam di Melaka mengikut tahun, daerah, peringkat pendidikan dan jenis institusi.\n      <\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n      <div class=\"cg\"><label>Tahun<\/label><select id=\"edu-year\"><\/select><\/div>\n      <div class=\"cg\"><label>Daerah<\/label><select id=\"edu-district\"><\/select><\/div>\n      <div class=\"cg\"><label>Peringkat<\/label><select id=\"edu-stage\"><\/select><\/div>\n      <div class=\"cg\"><label>Jenis<\/label><select id=\"edu-type\"><\/select><\/div>\n    <\/div>\n\n    <div class=\"stats\" id=\"edu-stats\" style=\"display:none\">\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"edu-stat-year\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Jumlah Institusi<\/div>\n        <div class=\"sv\" id=\"edu-stat-total\">\u2014<\/div>\n        <div class=\"su\">sekolah \/ institusi<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Daerah Tertinggi<\/div>\n        <div class=\"sv\" id=\"edu-stat-high\">\u2014<\/div>\n        <div class=\"su\" id=\"edu-stat-high-name\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Perubahan<\/div>\n        <div class=\"sv\" id=\"edu-stat-change\">\u2014<\/div>\n        <div class=\"su\" id=\"edu-stat-change-year\">berbanding tahun sebelumnya<\/div>\n      <\/div>\n    <\/div>\n\n    <div class=\"sbox\" id=\"edu-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod institusi pendidikan awam Melaka.<\/div>\n    <\/div>\n\n    <div id=\"edu-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"district\">Daerah<\/button>\n        <button class=\"tbtn\" data-tab=\"stage\">Peringkat<\/button>\n        <button class=\"tbtn\" data-tab=\"type\">Jenis<\/button>\n        <button class=\"tbtn\" data-tab=\"topbottom\">Tertinggi &#038; Terendah<\/button>\n        <button class=\"tbtn\" data-tab=\"change\">Perubahan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <div id=\"edu-tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Institusi Mengikut Masa<\/div>\n          <div class=\"cs\" id=\"edu-trend-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"edu-chart-trend\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"edu-tab-district\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Institusi Mengikut Daerah<\/div>\n          <div class=\"cs\" id=\"edu-district-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"edu-chart-district\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"edu-tab-stage\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Institusi Mengikut Peringkat Pendidikan<\/div>\n            <div class=\"cs\" id=\"edu-stage-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"edu-chart-stage\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Mengikut Peringkat<\/div>\n            <div class=\"cs\">Trend bilangan institusi mengikut peringkat pendidikan.<\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"edu-chart-stage-trend\"><\/canvas><\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"edu-tab-type\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Pecahan Mengikut Jenis Institusi<\/div>\n          <div class=\"cs\" id=\"edu-type-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"edu-chart-type\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"edu-tab-topbottom\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Daerah dengan Bilangan Institusi Tertinggi<\/div>\n            <div class=\"cs\" id=\"edu-top-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"edu-chart-top\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Daerah dengan Bilangan Institusi Terendah<\/div>\n            <div class=\"cs\" id=\"edu-bottom-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"edu-chart-bottom\"><\/canvas><\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"edu-tab-change\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Perubahan Bilangan Institusi Mengikut Daerah<\/div>\n            <div class=\"cs\" id=\"edu-change-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"edu-chart-change\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Purata Perubahan<\/div>\n            <div class=\"cs\" id=\"edu-change-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"edu-chart-change-trend\"><\/canvas><\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"edu-tab-state\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Institusi Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"edu-state-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"edu-chart-state\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  function init(){\n    if(typeof Chart === 'undefined'){ setTimeout(init,500); return; }\n\n    var API_URL='https:\/\/api.data.gov.my\/data-catalogue\/?id=schools_district&limit=3000';\n    var FIXED_STATE='Melaka';\n    var allRows=[], melakaRows=[], charts={};\n\n    Chart.defaults.color='#64748b';\n    Chart.defaults.borderColor='#dbeafe';\n    Chart.defaults.font.family='Arial, sans-serif';\n\n    function el(id){return document.getElementById(id);}\n    function yr(r){return parseInt(String(r.date||'').slice(0,4),10);}\n    function num(v){return Number(v)||0;}\n    function fmt(v){return Number(v).toLocaleString('ms-MY',{maximumFractionDigits:0});}\n    function destroyChart(name){if(charts[name]){charts[name].destroy();delete charts[name];}}\n\n    function labelStage(v){\n      if(v==='all') return 'Semua Peringkat';\n      if(v==='primary') return 'Rendah';\n      if(v==='secondary') return 'Menengah';\n      if(v==='tertiary') return 'Tertiari';\n      return v;\n    }\n\n    function getSelected(){\n      return {\n        year:parseInt(el('edu-year').value,10),\n        district:el('edu-district').value,\n        stage:el('edu-stage').value,\n        type:el('edu-type').value\n      };\n    }\n\n    function rowMatches(row, s, opts){\n      opts=opts||{};\n      var districtMatch=opts.ignoreDistrict||s.district==='all'||row.district===s.district;\n      var stageMatch=opts.ignoreStage||s.stage==='all'||row.stage===s.stage;\n      var typeMatch=opts.ignoreType||s.type==='all'||row.type===s.type;\n      return districtMatch&&stageMatch&&typeMatch;\n    }\n\n    function selectedRows(opts){\n      var s=getSelected();\n      return melakaRows.filter(function(r){return rowMatches(r,s,opts);});\n    }\n\n    function rowsForYear(opts){\n      var s=getSelected();\n      return selectedRows(opts).filter(function(r){return yr(r)===s.year;});\n    }\n\n    function sumSchools(rows){\n      return rows.reduce(function(total,r){return total+num(r.schools);},0);\n    }\n\n    function getPrevYear(current){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){return !isNaN(y)&&y<current;})\n        .sort(function(a,b){return b-a;});\n      return years.length?years[0]:null;\n    }\n\n    function populateFilters(){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){return !isNaN(y);})\n        .sort(function(a,b){return b-a;});\n\n      el('edu-year').innerHTML=years.map(function(y){\n        return '<option value=\"'+y+'\">'+y+'<\/option>';\n      }).join('');\n\n      if(years.length) el('edu-year').value=years[0];\n\n      var districts=Array.from(new Set(melakaRows.map(function(r){return r.district;})))\n        .filter(function(d){return d&&d!=='All Districts';})\n        .sort();\n\n      el('edu-district').innerHTML=\n        '<option value=\"all\">Semua Daerah<\/option>'+\n        districts.map(function(d){\n          return '<option value=\"'+d+'\">'+d+'<\/option>';\n        }).join('');\n\n      var stages=Array.from(new Set(melakaRows.map(function(r){return r.stage;})))\n        .filter(Boolean)\n        .sort();\n\n      el('edu-stage').innerHTML=\n        '<option value=\"all\">Semua Peringkat<\/option>'+\n        stages.map(function(s){\n          return '<option value=\"'+s+'\">'+labelStage(s)+'<\/option>';\n        }).join('');\n\n      populateTypeFilter();\n    }\n\n    function populateTypeFilter(){\n      var s=getSelected();\n\n      var list=melakaRows.filter(function(r){\n        return s.stage==='all'||r.stage===s.stage;\n      });\n\n      var types=Array.from(new Set(list.map(function(r){return r.type;})))\n        .filter(Boolean)\n        .sort();\n\n      el('edu-type').innerHTML=\n        '<option value=\"all\">Semua Jenis<\/option>'+\n        types.map(function(t){\n          return '<option value=\"'+t+'\">'+t+'<\/option>';\n        }).join('');\n    }\n\n    function updateCards(){\n      var s=getSelected();\n      var rows=rowsForYear({});\n      var total=sumSchools(rows);\n\n      var districtRows=rowsForYear({ignoreDistrict:true})\n        .filter(function(r){return r.district!=='All Districts';});\n\n      var districts=Array.from(new Set(districtRows.map(function(r){\n        return r.district;\n      })));\n\n      var districtData=districts.map(function(d){\n        return {\n          district:d,\n          total:sumSchools(\n            districtRows.filter(function(x){\n              return x.district===d;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      var high=districtData[0];\n      var prev=getPrevYear(s.year);\n\n      var prevRows=prev\n        ? selectedRows({}).filter(function(r){return yr(r)===prev;})\n        : [];\n\n      var prevTotal=sumSchools(prevRows);\n      var change=prevRows.length?total-prevTotal:null;\n\n      el('edu-stat-year').textContent=s.year||'\u2014';\n      el('edu-stat-total').textContent=rows.length?fmt(total):'\u2014';\n      el('edu-stat-high').textContent=high?fmt(high.total):'\u2014';\n      el('edu-stat-high-name').textContent=high?high.district:'\u2014';\n\n      el('edu-stat-change').textContent=\n        change!==null\n          ? ((change>0?'+':'')+fmt(change))\n          : '\u2014';\n\n      el('edu-stat-change-year').textContent=\n        prev\n          ? 'berbanding '+prev\n          : 'berbanding tahun sebelumnya';\n\n      el('edu-stats').style.display='';\n    }\n\n    function tooltipCount(){\n      return {\n        backgroundColor:'#0f172a',\n        callbacks:{\n          label:function(c){\n            return ' '+c.dataset.label+': '+fmt(c.parsed.y);\n          }\n        }\n      };\n    }\n\n    function buildTrendChart(){\n      var s=getSelected();\n      var rows=selectedRows({});\n\n      var years=Array.from(new Set(rows.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var data=years.map(function(y){\n        return {\n          year:y,\n          total:sumSchools(\n            rows.filter(function(x){\n              return yr(x)===y;\n            })\n          )\n        };\n      });\n\n      el('edu-trend-sub').textContent=\n        FIXED_STATE+' \u00b7 '+\n        (s.district==='all'?'Semua Daerah':s.district)+' \u00b7 '+\n        labelStage(s.stage)+' \u00b7 '+\n        (s.type==='all'?'Semua Jenis':s.type);\n\n      destroyChart('trend');\n\n      charts.trend=new Chart(el('edu-chart-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:data.map(function(d){return d.year;}),\n          datasets:[{\n            label:'Institusi',\n            data:data.map(function(d){return d.total;}),\n            borderColor:'#2563eb',\n            backgroundColor:'rgba(37,99,235,.15)',\n            fill:true,\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:4\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipCount()\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildDistrictChart(){\n      var s=getSelected();\n\n      var rows=rowsForYear({ignoreDistrict:true})\n        .filter(function(r){return r.district!=='All Districts';});\n\n      var districts=Array.from(new Set(rows.map(function(r){\n        return r.district;\n      }))).sort();\n\n      var data=districts.map(function(d){\n        return {\n          district:d,\n          total:sumSchools(\n            rows.filter(function(x){\n              return x.district===d;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      el('edu-district-sub').textContent=\n        s.year+' \u00b7 Semua Daerah \u00b7 '+\n        labelStage(s.stage)+' \u00b7 '+\n        (s.type==='all'?'Semua Jenis':s.type);\n\n      destroyChart('district');\n\n      charts.district=new Chart(el('edu-chart-district').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.district;}),\n          datasets:[{\n            label:'Institusi',\n            data:data.map(function(d){return d.total;}),\n            backgroundColor:'rgba(37,99,235,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipCount()\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildStageCharts(){\n      var s=getSelected();\n      var rows=rowsForYear({ignoreStage:true});\n\n      var stages=Array.from(new Set(rows.map(function(r){\n        return r.stage;\n      }))).filter(Boolean).sort();\n\n      var data=stages.map(function(stage){\n        return {\n          stage:labelStage(stage),\n          key:stage,\n          total:sumSchools(\n            rows.filter(function(x){\n              return x.stage===stage;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      el('edu-stage-sub').textContent=\n        s.year+' \u00b7 '+\n        (s.district==='all'?'Semua Daerah':s.district)+\n        ' \u00b7 Semua Peringkat';\n\n      destroyChart('stage');\n\n      charts.stage=new Chart(el('edu-chart-stage').getContext('2d'),{\n        type:'doughnut',\n        data:{\n          labels:data.map(function(d){return d.stage;}),\n          datasets:[{\n            label:'Institusi',\n            data:data.map(function(d){return d.total;}),\n            backgroundColor:['#2563eb','#f97316','#16a34a','#7c3aed'],\n            borderColor:'#ffffff',\n            borderWidth:3\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{position:'bottom'},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' '+c.label+': '+fmt(c.parsed);\n                }\n              }\n            }\n          }\n        }\n      });\n\n      var trendRows=selectedRows({ignoreStage:true});\n\n      var years=Array.from(new Set(trendRows.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var datasets=stages.map(function(stage){\n        return {\n          label:labelStage(stage),\n          data:years.map(function(y){\n            return sumSchools(\n              trendRows.filter(function(r){\n                return yr(r)===y&&r.stage===stage;\n              })\n            );\n          }),\n          tension:.35,\n          borderWidth:2.5,\n          pointRadius:3\n        };\n      });\n\n      destroyChart('stageTrend');\n\n      charts.stageTrend=new Chart(el('edu-chart-stage-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:years,\n          datasets:datasets\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          interaction:{\n            mode:'index',\n            intersect:false\n          },\n          plugins:{\n            legend:{position:'bottom'},\n            tooltip:tooltipCount()\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildTypeChart(){\n      var s=getSelected();\n      var rows=rowsForYear({ignoreType:true});\n\n      var types=Array.from(new Set(rows.map(function(r){\n        return r.type;\n      }))).filter(Boolean).sort();\n\n      var data=types.map(function(type){\n        return {\n          type:type,\n          total:sumSchools(\n            rows.filter(function(x){\n              return x.type===type;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      el('edu-type-sub').textContent=\n        s.year+' \u00b7 '+\n        (s.district==='all'?'Semua Daerah':s.district)+' \u00b7 '+\n        labelStage(s.stage)+' \u00b7 Semua Jenis';\n\n      destroyChart('type');\n\n      charts.type=new Chart(el('edu-chart-type').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.type;}),\n          datasets:[{\n            label:'Institusi',\n            data:data.map(function(d){return d.total;}),\n            backgroundColor:'rgba(249,115,22,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          indexAxis:'y',\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Institusi: '+fmt(c.parsed.x);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{\n              grid:{color:'#dbeafe'},\n              ticks:{callback:function(v){return fmt(v);}}\n            },\n            y:{grid:{display:false}}\n          }\n        }\n      });\n    }\n\n    function buildTopBottomCharts(){\n      var s=getSelected();\n\n      var rows=rowsForYear({ignoreDistrict:true})\n        .filter(function(r){return r.district!=='All Districts';});\n\n      var districts=Array.from(new Set(rows.map(function(r){\n        return r.district;\n      }))).sort();\n\n      var data=districts.map(function(d){\n        return {\n          district:d,\n          total:sumSchools(\n            rows.filter(function(x){\n              return x.district===d;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      var top=data.slice(0,10);\n      var bottom=data.slice(-10).reverse();\n\n      el('edu-top-sub').textContent=\n        s.year+' \u00b7 daerah dengan bilangan institusi tertinggi';\n\n      el('edu-bottom-sub').textContent=\n        s.year+' \u00b7 daerah dengan bilangan institusi terendah';\n\n      function makeChart(id,name,data,color){\n        destroyChart(name);\n\n        charts[name]=new Chart(el(id).getContext('2d'),{\n          type:'bar',\n          data:{\n            labels:data.map(function(d){return d.district;}),\n            datasets:[{\n              label:'Institusi',\n              data:data.map(function(d){return d.total;}),\n              backgroundColor:color,\n              borderRadius:8\n            }]\n          },\n          options:{\n            responsive:true,\n            maintainAspectRatio:false,\n            plugins:{\n              legend:{display:false},\n              tooltip:tooltipCount()\n            },\n            scales:{\n              x:{grid:{display:false},ticks:{maxRotation:45}},\n              y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n            }\n          }\n        });\n      }\n\n      makeChart('edu-chart-top','top',top,'rgba(22,163,74,.78)');\n      makeChart('edu-chart-bottom','bottom',bottom,'rgba(220,38,38,.78)');\n    }\n\n    function buildChangeCharts(){\n      var s=getSelected();\n      var current=s.year;\n      var prev=getPrevYear(current);\n\n      if(!prev){\n        el('edu-change-sub').textContent=\n          current+' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('edu-change-trend-sub').textContent=\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n        return;\n      }\n\n      var rowsCurrent=melakaRows.filter(function(r){\n        return yr(r)===current;\n      });\n\n      var rowsPrev=melakaRows.filter(function(r){\n        return yr(r)===prev;\n      });\n\n      var districts=Array.from(new Set(rowsCurrent.map(function(r){\n        return r.district;\n      })))\n        .filter(function(d){return d&&d!=='All Districts';})\n        .sort();\n\n      var data=districts.map(function(d){\n\n        var c=sumSchools(\n          rowsCurrent.filter(function(r){\n            return r.district===d&&rowMatches(r,s,{ignoreDistrict:true});\n          })\n        );\n\n        var p=sumSchools(\n          rowsPrev.filter(function(r){\n            return r.district===d&&rowMatches(r,s,{ignoreDistrict:true});\n          })\n        );\n\n        return {\n          district:d,\n          change:c-p\n        };\n\n      }).sort(function(a,b){return b.change-a.change;});\n\n      el('edu-change-sub').textContent=\n        current+' berbanding '+prev+\n        ' \u00b7 nilai positif menunjukkan bilangan institusi meningkat';\n\n      destroyChart('change');\n\n      charts.change=new Chart(el('edu-chart-change').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.district;}),\n          datasets:[{\n            label:'Perubahan',\n            data:data.map(function(d){return d.change;}),\n            backgroundColor:'rgba(124,58,237,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Perubahan: '+\n                    (c.parsed.y>0?'+':'')+\n                    fmt(c.parsed.y);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n\n      var selected=selectedRows({});\n\n      var years=Array.from(new Set(selected.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var trend=years.map(function(y,i){\n        if(i===0){\n          return {year:y,change:null};\n        }\n\n        var prevYear=years[i-1];\n\n        var now=sumSchools(\n          selected.filter(function(x){\n            return yr(x)===y;\n          })\n        );\n\n        var before=sumSchools(\n          selected.filter(function(x){\n            return yr(x)===prevYear;\n          })\n        );\n\n        return {\n          year:String(y)+' berbanding '+String(prevYear),\n          change:now-before\n        };\n\n      }).filter(function(d){return d.change!==null;});\n\n      el('edu-change-trend-sub').textContent=\n        'Purata perubahan bagi tempoh data yang tersedia';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend=new Chart(el('edu-chart-change-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:trend.map(function(d){return d.year;}),\n          datasets:[{\n            label:'Perubahan',\n            data:trend.map(function(d){return d.change;}),\n            borderColor:'#7c3aed',\n            backgroundColor:'rgba(124,58,237,.15)',\n            fill:true,\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:4\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Perubahan: '+\n                    (c.parsed.y>0?'+':'')+\n                    fmt(c.parsed.y);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildStateChart(){\n      var s=getSelected();\n\n      var rows=allRows.filter(function(r){\n        return yr(r)===s.year &&\n          r.district==='All Districts' &&\n          (s.stage==='all'||r.stage===s.stage) &&\n          (s.type==='all'||r.type===s.type) &&\n          r.state!=='Malaysia';\n      });\n\n      var states=Array.from(new Set(rows.map(function(r){\n        return r.state;\n      }))).sort();\n\n      var data=states.map(function(state){\n        return {\n          state:state,\n          total:sumSchools(\n            rows.filter(function(x){\n              return x.state===state;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      el('edu-state-sub').textContent=\n        s.year+' \u00b7 semua negeri \u00b7 '+\n        labelStage(s.stage)+' \u00b7 '+\n        (s.type==='all'?'Semua Jenis':s.type);\n\n      destroyChart('state');\n\n      charts.state=new Chart(el('edu-chart-state').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.state;}),\n          datasets:[{\n            label:'Institusi',\n            data:data.map(function(d){return d.total;}),\n            backgroundColor:data.map(function(d){\n              return d.state==='Melaka'\n                ? 'rgba(249,115,22,.95)'\n                : 'rgba(37,99,235,.70)';\n            }),\n            borderColor:data.map(function(d){\n              return d.state==='Melaka'\n                ? '#f97316'\n                : 'rgba(37,99,235,.70)';\n            }),\n            borderWidth:data.map(function(d){\n              return d.state==='Melaka'?3:0;\n            }),\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipCount()\n          },\n          scales:{\n            x:{\n              grid:{display:false},\n              ticks:{\n                maxRotation:45,\n                color:function(c){\n                  return c.tick.label==='Melaka'\n                    ? '#f97316'\n                    : '#64748b';\n                },\n                font:function(c){\n                  return {\n                    weight:c.tick.label==='Melaka'?'800':'400',\n                    size:c.tick.label==='Melaka'?13:12\n                  };\n                }\n              }\n            },\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{callback:function(v){return fmt(v);}}\n            }\n          }\n        }\n      });\n    }\n\n    function activeTab(){\n      var a=document.querySelector('#edu-wrap .tbtn.active');\n      return a?a.getAttribute('data-tab'):'trend';\n    }\n\n    function redraw(){\n      updateCards();\n\n      if(activeTab()==='trend')buildTrendChart();\n      if(activeTab()==='district')buildDistrictChart();\n      if(activeTab()==='stage')buildStageCharts();\n      if(activeTab()==='type')buildTypeChart();\n      if(activeTab()==='topbottom')buildTopBottomCharts();\n      if(activeTab()==='change')buildChangeCharts();\n      if(activeTab()==='state')buildStateChart();\n    }\n\n    document.querySelectorAll('#edu-wrap .tbtn').forEach(function(btn){\n      btn.addEventListener('click',function(){\n\n        document.querySelectorAll('#edu-wrap .tbtn').forEach(function(b){\n          b.classList.remove('active');\n        });\n\n        btn.classList.add('active');\n\n        ['trend','district','stage','type','topbottom','change','state'].forEach(function(t){\n          el('edu-tab-'+t).style.display='none';\n        });\n\n        el('edu-tab-'+btn.getAttribute('data-tab')).style.display='';\n\n        redraw();\n      });\n    });\n\n    el('edu-stage').addEventListener('change',function(){\n      populateTypeFilter();\n      redraw();\n    });\n\n    ['edu-year','edu-district','edu-type'].forEach(function(id){\n      el(id).addEventListener('change',redraw);\n    });\n\n    fetch(API_URL)\n      .then(function(r){\n        if(!r.ok)throw new Error('HTTP '+r.status);\n        return r.json();\n      })\n      .then(function(data){\n\n        allRows=Array.isArray(data)?data:[];\n\n        melakaRows=allRows.filter(function(r){\n          return String(r.state||'').trim().toLowerCase()==='melaka';\n        });\n\n        if(!melakaRows.length){\n          throw new Error('Tiada rekod Melaka ditemui.');\n        }\n\n        populateFilters();\n\n        el('edu-loading').style.display='none';\n        el('edu-area').style.display='';\n\n        redraw();\n      })\n      .catch(function(e){\n\n        el('edu-loading').innerHTML=\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">'+\n          'Gagal memuatkan data'+\n          '<\/div>'+\n          '<p style=\"font-size:13px;color:#64748b\">'+\n          e.message+\n          '<\/p>';\n      });\n  }\n\n  if(document.readyState==='loading'){\n    document.addEventListener('DOMContentLoaded',init);\n  }else{\n    init();\n  }\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#teacher-wrap, #teacher-wrap * { box-sizing:border-box; }\n#teacher-wrap{\n  --bg:#f8fafc;--card:#ffffff;--soft:#eff6ff;--border:#dbeafe;\n  --text:#0f172a;--muted:#64748b;--accent:#2563eb;\n  --green:#16a34a;--red:#dc2626;--orange:#f97316;--purple:#7c3aed;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%;padding:28px;background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);border-radius:22px;font-family:Arial,sans-serif;color:var(--text);\n}\n#teacher-wrap .dash{max-width:1180px;margin:0 auto;}\n#teacher-wrap .hdr,#teacher-wrap .cc,#teacher-wrap .sc,#teacher-wrap .sbox{background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow);}\n#teacher-wrap .hdr{border-radius:20px;padding:26px;margin-bottom:20px;}\n#teacher-wrap .eyebrow{color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px;}\n#teacher-wrap h2{margin:0;font-size:clamp(26px,4vw,44px);line-height:1.1;font-weight:800;}\n#teacher-wrap .hdesc{color:var(--muted);margin-top:10px;line-height:1.6;}\n#teacher-wrap .filter-bar{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;background:#fff;border:1px solid var(--border);border-radius:16px;padding:18px;margin-bottom:20px;box-shadow:var(--shadow);}\n#teacher-wrap .cg{display:flex;flex-direction:column;gap:7px;}\n#teacher-wrap label{font-size:11px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;}\n#teacher-wrap select{width:100%;min-height:44px;padding:10px 40px 10px 12px;border:1.5px solid #bfdbfe;border-radius:10px;background-color:#fff;color:var(--text);font-size:14px;font-weight:600;outline:none;appearance:none;cursor:pointer;background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");background-repeat:no-repeat;background-position:right 12px center;}\n#teacher-wrap select:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(37,99,235,.12);}\n#teacher-wrap .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;}\n#teacher-wrap .sc{border-radius:16px;padding:18px;border-left:4px solid var(--accent);}\n#teacher-wrap .sl{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;margin-bottom:8px;}\n#teacher-wrap .sv{font-size:25px;font-weight:800;}\n#teacher-wrap .su{color:var(--muted);font-size:12px;margin-top:4px;}\n#teacher-wrap .tab-bar{display:flex;justify-content:space-between;gap:10px;background:var(--soft);border:1px solid var(--border);border-radius:999px;padding:8px;margin-bottom:18px;overflow-x:auto;}\n#teacher-wrap .tbtn{flex:1;border:0;background:transparent;padding:12px 18px;border-radius:999px;color:var(--muted);font-weight:800;cursor:pointer;white-space:nowrap;}\n#teacher-wrap .tbtn.active{background:#fff;color:var(--accent);box-shadow:0 5px 14px rgba(15,23,42,.08);}\n#teacher-wrap .cc{border-radius:20px;padding:24px;margin-bottom:18px;}\n#teacher-wrap .ct{font-size:18px;font-weight:800;margin-bottom:5px;}\n#teacher-wrap .cs{color:var(--muted);font-size:13px;margin-bottom:18px;}\n#teacher-wrap .two{display:grid;grid-template-columns:1fr 1fr;gap:18px;}\n#teacher-wrap .chart-box{position:relative;height:350px;}\n#teacher-wrap .chart-box-sm{position:relative;height:320px;}\n#teacher-wrap .sbox{border-radius:20px;padding:60px 24px;text-align:center;color:var(--muted);}\n#teacher-wrap .spin{width:38px;height:38px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:teacher-spin .8s linear infinite;margin:0 auto 16px;}\n@keyframes teacher-spin{to{transform:rotate(360deg);}}\n@media(max-width:1024px){#teacher-wrap .filter-bar,#teacher-wrap .stats{grid-template-columns:repeat(2,1fr);}#teacher-wrap .two{grid-template-columns:1fr;}}\n@media(max-width:600px){#teacher-wrap{padding:14px;border-radius:16px;}#teacher-wrap .filter-bar,#teacher-wrap .stats{grid-template-columns:1fr;}#teacher-wrap .tab-bar{justify-content:flex-start;}#teacher-wrap .tbtn{flex:0 0 auto;}#teacher-wrap .chart-box,#teacher-wrap .chart-box-sm{height:300px;}}\n<\/style>\n\n<div id=\"teacher-wrap\">\n  <div class=\"dash\">\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Guru di Sekolah Kerajaan Melaka<\/div>\n      <h2>Papan Pemuka Guru di Sekolah Kerajaan<\/h2>\n      <p class=\"hdesc\">Visualisasi bilangan guru di sekolah kerajaan di Melaka mengikut tahun, daerah, peringkat sekolah dan jantina.<\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n      <div class=\"cg\"><label>Tahun<\/label><select id=\"teacher-year\"><\/select><\/div>\n      <div class=\"cg\"><label>Daerah<\/label><select id=\"teacher-district\"><\/select><\/div>\n      <div class=\"cg\"><label>Peringkat<\/label><select id=\"teacher-stage\"><\/select><\/div>\n      <div class=\"cg\"><label>Jantina<\/label><select id=\"teacher-sex\"><\/select><\/div>\n    <\/div>\n\n    <div class=\"stats\" id=\"teacher-stats\" style=\"display:none\">\n      <div class=\"sc\"><div class=\"sl\">Tahun Dipilih<\/div><div class=\"sv\" id=\"teacher-stat-year\">\u2014<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">Jumlah Guru<\/div><div class=\"sv\" id=\"teacher-stat-total\">\u2014<\/div><div class=\"su\">guru<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">Daerah Tertinggi<\/div><div class=\"sv\" id=\"teacher-stat-high\">\u2014<\/div><div class=\"su\" id=\"teacher-stat-high-name\">\u2014<\/div><\/div>\n      <div class=\"sc\"><div class=\"sl\">Perubahan<\/div><div class=\"sv\" id=\"teacher-stat-change\">\u2014<\/div><div class=\"su\" id=\"teacher-stat-change-year\">berbanding tahun sebelumnya<\/div><\/div>\n    <\/div>\n\n    <div class=\"sbox\" id=\"teacher-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod guru Melaka.<\/div>\n    <\/div>\n\n    <div id=\"teacher-area\" style=\"display:none\">\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"district\">Daerah<\/button>\n        <button class=\"tbtn\" data-tab=\"stage\">Peringkat<\/button>\n        <button class=\"tbtn\" data-tab=\"sex\">Jantina<\/button>\n        <button class=\"tbtn\" data-tab=\"topbottom\">Tertinggi &#038; Terendah<\/button>\n        <button class=\"tbtn\" data-tab=\"change\">Perubahan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <div id=\"teacher-tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Bilangan Guru Mengikut Masa<\/div>\n          <div class=\"cs\" id=\"teacher-trend-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"teacher-chart-trend\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"teacher-tab-district\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Bilangan Guru Mengikut Daerah<\/div>\n          <div class=\"cs\" id=\"teacher-district-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"teacher-chart-district\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"teacher-tab-stage\" style=\"display:none\">\n        <div class=\"two\">\n          <div class=\"cc\">\n            <div class=\"ct\">Guru Mengikut Peringkat Sekolah<\/div>\n            <div class=\"cs\" id=\"teacher-stage-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"teacher-chart-stage\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Mengikut Peringkat<\/div>\n            <div class=\"cs\">Trend bilangan guru mengikut peringkat sekolah.<\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"teacher-chart-stage-trend\"><\/canvas><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"teacher-tab-sex\" style=\"display:none\">\n        <div class=\"two\">\n          <div class=\"cc\">\n            <div class=\"ct\">Guru Mengikut Jantina<\/div>\n            <div class=\"cs\" id=\"teacher-sex-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"teacher-chart-sex\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Mengikut Jantina<\/div>\n            <div class=\"cs\">Trend bilangan guru mengikut jantina.<\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"teacher-chart-sex-trend\"><\/canvas><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"teacher-tab-topbottom\" style=\"display:none\">\n        <div class=\"two\">\n          <div class=\"cc\">\n            <div class=\"ct\">Daerah dengan Bilangan Guru Tertinggi<\/div>\n            <div class=\"cs\" id=\"teacher-top-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"teacher-chart-top\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Daerah dengan Bilangan Guru Terendah<\/div>\n            <div class=\"cs\" id=\"teacher-bottom-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"teacher-chart-bottom\"><\/canvas><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"teacher-tab-change\" style=\"display:none\">\n        <div class=\"two\">\n          <div class=\"cc\">\n            <div class=\"ct\">Perubahan Bilangan Guru Mengikut Daerah<\/div>\n            <div class=\"cs\" id=\"teacher-change-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"teacher-chart-change\"><\/canvas><\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Purata Perubahan<\/div>\n            <div class=\"cs\" id=\"teacher-change-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\"><canvas id=\"teacher-chart-change-trend\"><\/canvas><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"teacher-tab-state\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Bilangan Guru Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"teacher-state-sub\"><\/div>\n          <div class=\"chart-box\"><canvas id=\"teacher-chart-state\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  function init(){\n    if(typeof Chart === 'undefined'){ setTimeout(init,500); return; }\n\n    var API_URL='https:\/\/api.data.gov.my\/data-catalogue\/?id=teachers_district&limit=6000';\n    var FIXED_STATE='Melaka';\n    var allRows=[], melakaRows=[], charts={};\n\n    Chart.defaults.color='#64748b';\n    Chart.defaults.borderColor='#dbeafe';\n    Chart.defaults.font.family='Arial, sans-serif';\n\n    function el(id){return document.getElementById(id);}\n    function yr(r){return parseInt(String(r.date||'').slice(0,4),10);}\n    function num(v){return Number(v)||0;}\n    function fmt(v){return Number(v).toLocaleString('ms-MY',{maximumFractionDigits:0});}\n    function destroyChart(name){if(charts[name]){charts[name].destroy();delete charts[name];}}\n\n    function labelStage(v){\n      if(v==='all') return 'Semua Peringkat';\n      if(v==='primary') return 'Rendah';\n      if(v==='secondary') return 'Menengah';\n      return v;\n    }\n\n    function labelSex(v){\n      if(v==='both') return 'Kedua-dua Jantina';\n      if(v==='male') return 'Lelaki';\n      if(v==='female') return 'Perempuan';\n      return v;\n    }\n\n    function getSelected(){\n      return {\n        year:parseInt(el('teacher-year').value,10),\n        district:el('teacher-district').value,\n        stage:el('teacher-stage').value,\n        sex:el('teacher-sex').value\n      };\n    }\n\n    function rowMatches(row, s, opts){\n      opts=opts||{};\n      var districtMatch=opts.ignoreDistrict||s.district==='all'||row.district===s.district;\n      var stageMatch=opts.ignoreStage||s.stage==='all'||row.stage===s.stage;\n      var sexMatch=opts.ignoreSex||row.sex===s.sex;\n      return districtMatch&&stageMatch&&sexMatch;\n    }\n\n    function selectedRows(opts){\n      var s=getSelected();\n      return melakaRows.filter(function(r){return rowMatches(r,s,opts);});\n    }\n\n    function rowsForYear(opts){\n      var s=getSelected();\n      return selectedRows(opts).filter(function(r){return yr(r)===s.year;});\n    }\n\n    function sumTeachers(rows){\n      return rows.reduce(function(total,r){return total+num(r.teachers);},0);\n    }\n\n    function getPrevYear(current){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){return !isNaN(y)&&y<current;})\n        .sort(function(a,b){return b-a;});\n      return years.length?years[0]:null;\n    }\n\n    function populateFilters(){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){return !isNaN(y);})\n        .sort(function(a,b){return b-a;});\n\n      el('teacher-year').innerHTML=years.map(function(y){\n        return '<option value=\"'+y+'\">'+y+'<\/option>';\n      }).join('');\n\n      if(years.length) el('teacher-year').value=years[0];\n\n      var districts=Array.from(new Set(melakaRows.map(function(r){\n        return r.district;\n      })))\n        .filter(function(d){return d&&d!=='All Districts';})\n        .sort();\n\n      el('teacher-district').innerHTML=\n        '<option value=\"all\">Semua Daerah<\/option>'+\n        districts.map(function(d){\n          return '<option value=\"'+d+'\">'+d+'<\/option>';\n        }).join('');\n\n      var stages=Array.from(new Set(melakaRows.map(function(r){\n        return r.stage;\n      }))).filter(Boolean).sort();\n\n      el('teacher-stage').innerHTML=\n        '<option value=\"all\">Semua Peringkat<\/option>'+\n        stages.map(function(s){\n          return '<option value=\"'+s+'\">'+labelStage(s)+'<\/option>';\n        }).join('');\n\n      el('teacher-sex').innerHTML=\n        '<option value=\"both\">Kedua-dua Jantina<\/option>'+\n        '<option value=\"female\">Perempuan<\/option>'+\n        '<option value=\"male\">Lelaki<\/option>';\n    }\n\n    function updateCards(){\n      var s=getSelected();\n      var rows=rowsForYear({});\n      var total=sumTeachers(rows);\n\n      var districtRows=rowsForYear({ignoreDistrict:true})\n        .filter(function(r){return r.district!=='All Districts';});\n\n      var districts=Array.from(new Set(districtRows.map(function(r){\n        return r.district;\n      })));\n\n      var districtData=districts.map(function(d){\n        return {\n          district:d,\n          total:sumTeachers(\n            districtRows.filter(function(x){\n              return x.district===d;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      var high=districtData[0];\n      var prev=getPrevYear(s.year);\n\n      var prevRows=prev\n        ? selectedRows({}).filter(function(r){return yr(r)===prev;})\n        : [];\n\n      var prevTotal=sumTeachers(prevRows);\n      var change=prevRows.length?total-prevTotal:null;\n\n      el('teacher-stat-year').textContent=s.year||'\u2014';\n      el('teacher-stat-total').textContent=rows.length?fmt(total):'\u2014';\n      el('teacher-stat-high').textContent=high?fmt(high.total):'\u2014';\n      el('teacher-stat-high-name').textContent=high?high.district:'\u2014';\n\n      el('teacher-stat-change').textContent=\n        change!==null\n          ? ((change>0?'+':'')+fmt(change))\n          : '\u2014';\n\n      el('teacher-stat-change-year').textContent=\n        prev\n          ? 'berbanding '+prev\n          : 'berbanding tahun sebelumnya';\n\n      el('teacher-stats').style.display='';\n    }\n\n    function tooltipY(){\n      return {\n        backgroundColor:'#0f172a',\n        callbacks:{\n          label:function(c){\n            return ' '+c.dataset.label+': '+fmt(c.parsed.y);\n          }\n        }\n      };\n    }\n\n    function buildTrendChart(){\n      var s=getSelected();\n      var rows=selectedRows({});\n\n      var years=Array.from(new Set(rows.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var data=years.map(function(y){\n        return {\n          year:y,\n          total:sumTeachers(\n            rows.filter(function(x){\n              return yr(x)===y;\n            })\n          )\n        };\n      });\n\n      el('teacher-trend-sub').textContent=\n        FIXED_STATE+' \u00b7 '+\n        (s.district==='all'?'Semua Daerah':s.district)+' \u00b7 '+\n        labelStage(s.stage)+' \u00b7 '+\n        labelSex(s.sex);\n\n      destroyChart('trend');\n\n      charts.trend=new Chart(el('teacher-chart-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:data.map(function(d){return d.year;}),\n          datasets:[{\n            label:'Guru',\n            data:data.map(function(d){return d.total;}),\n            borderColor:'#2563eb',\n            backgroundColor:'rgba(37,99,235,.15)',\n            fill:true,\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:4\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipY()\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildDistrictChart(){\n      var s=getSelected();\n\n      var rows=rowsForYear({ignoreDistrict:true})\n        .filter(function(r){return r.district!=='All Districts';});\n\n      var districts=Array.from(new Set(rows.map(function(r){\n        return r.district;\n      }))).sort();\n\n      var data=districts.map(function(d){\n        return {\n          district:d,\n          total:sumTeachers(\n            rows.filter(function(x){\n              return x.district===d;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      el('teacher-district-sub').textContent=\n        s.year+' \u00b7 Semua Daerah \u00b7 '+\n        labelStage(s.stage)+' \u00b7 '+\n        labelSex(s.sex);\n\n      destroyChart('district');\n\n      charts.district=new Chart(el('teacher-chart-district').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.district;}),\n          datasets:[{\n            label:'Guru',\n            data:data.map(function(d){return d.total;}),\n            backgroundColor:'rgba(37,99,235,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipY()\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildStageCharts(){\n      var s=getSelected();\n      var rows=rowsForYear({ignoreStage:true});\n\n      var stages=Array.from(new Set(rows.map(function(r){\n        return r.stage;\n      }))).filter(Boolean).sort();\n\n      var data=stages.map(function(stage){\n        return {\n          stage:labelStage(stage),\n          key:stage,\n          total:sumTeachers(\n            rows.filter(function(x){\n              return x.stage===stage;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      el('teacher-stage-sub').textContent=\n        s.year+' \u00b7 '+\n        (s.district==='all'?'Semua Daerah':s.district)+\n        ' \u00b7 Semua Peringkat \u00b7 '+\n        labelSex(s.sex);\n\n      destroyChart('stage');\n\n      charts.stage=new Chart(el('teacher-chart-stage').getContext('2d'),{\n        type:'doughnut',\n        data:{\n          labels:data.map(function(d){return d.stage;}),\n          datasets:[{\n            label:'Guru',\n            data:data.map(function(d){return d.total;}),\n            backgroundColor:['#2563eb','#f97316','#16a34a'],\n            borderColor:'#ffffff',\n            borderWidth:3\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{position:'bottom'},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' '+c.label+': '+fmt(c.parsed);\n                }\n              }\n            }\n          }\n        }\n      });\n\n      var trendRows=selectedRows({ignoreStage:true});\n\n      var years=Array.from(new Set(trendRows.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var datasets=stages.map(function(stage){\n        return {\n          label:labelStage(stage),\n          data:years.map(function(y){\n            return sumTeachers(\n              trendRows.filter(function(r){\n                return yr(r)===y&&r.stage===stage;\n              })\n            );\n          }),\n          tension:.35,\n          borderWidth:2.5,\n          pointRadius:3\n        };\n      });\n\n      destroyChart('stageTrend');\n\n      charts.stageTrend=new Chart(el('teacher-chart-stage-trend').getContext('2d'),{\n        type:'line',\n        data:{labels:years,datasets:datasets},\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          interaction:{mode:'index',intersect:false},\n          plugins:{\n            legend:{position:'bottom'},\n            tooltip:tooltipY()\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildSexCharts(){\n      var s=getSelected();\n      var rows=rowsForYear({ignoreSex:true});\n\n      var sexes=Array.from(new Set(rows.map(function(r){\n        return r.sex;\n      }))).filter(Boolean).sort();\n\n      var data=sexes.map(function(sex){\n        return {\n          sex:labelSex(sex),\n          key:sex,\n          total:sumTeachers(\n            rows.filter(function(x){\n              return x.sex===sex;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      el('teacher-sex-sub').textContent=\n        s.year+' \u00b7 '+\n        (s.district==='all'?'Semua Daerah':s.district)+' \u00b7 '+\n        labelStage(s.stage)+' \u00b7 Semua Jantina';\n\n      destroyChart('sex');\n\n      charts.sex=new Chart(el('teacher-chart-sex').getContext('2d'),{\n        type:'doughnut',\n        data:{\n          labels:data.map(function(d){return d.sex;}),\n          datasets:[{\n            label:'Guru',\n            data:data.map(function(d){return d.total;}),\n            backgroundColor:['#2563eb','#f97316','#16a34a'],\n            borderColor:'#ffffff',\n            borderWidth:3\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{position:'bottom'},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' '+c.label+': '+fmt(c.parsed);\n                }\n              }\n            }\n          }\n        }\n      });\n\n      var trendRows=selectedRows({ignoreSex:true});\n\n      var years=Array.from(new Set(trendRows.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var datasets=sexes.map(function(sex){\n        return {\n          label:labelSex(sex),\n          data:years.map(function(y){\n            return sumTeachers(\n              trendRows.filter(function(r){\n                return yr(r)===y&&r.sex===sex;\n              })\n            );\n          }),\n          tension:.35,\n          borderWidth:2.5,\n          pointRadius:3\n        };\n      });\n\n      destroyChart('sexTrend');\n\n      charts.sexTrend=new Chart(el('teacher-chart-sex-trend').getContext('2d'),{\n        type:'line',\n        data:{labels:years,datasets:datasets},\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          interaction:{mode:'index',intersect:false},\n          plugins:{\n            legend:{position:'bottom'},\n            tooltip:tooltipY()\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildTopBottomCharts(){\n      var s=getSelected();\n\n      var rows=rowsForYear({ignoreDistrict:true})\n        .filter(function(r){return r.district!=='All Districts';});\n\n      var districts=Array.from(new Set(rows.map(function(r){\n        return r.district;\n      }))).sort();\n\n      var data=districts.map(function(d){\n        return {\n          district:d,\n          total:sumTeachers(\n            rows.filter(function(x){\n              return x.district===d;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      var top=data.slice(0,10);\n      var bottom=data.slice(-10).reverse();\n\n      el('teacher-top-sub').textContent=\n        s.year+' \u00b7 daerah dengan bilangan guru tertinggi';\n\n      el('teacher-bottom-sub').textContent=\n        s.year+' \u00b7 daerah dengan bilangan guru terendah';\n\n      function makeChart(id,name,data,color){\n        destroyChart(name);\n\n        charts[name]=new Chart(el(id).getContext('2d'),{\n          type:'bar',\n          data:{\n            labels:data.map(function(d){return d.district;}),\n            datasets:[{\n              label:'Guru',\n              data:data.map(function(d){return d.total;}),\n              backgroundColor:color,\n              borderRadius:8\n            }]\n          },\n          options:{\n            responsive:true,\n            maintainAspectRatio:false,\n            plugins:{\n              legend:{display:false},\n              tooltip:tooltipY()\n            },\n            scales:{\n              x:{grid:{display:false},ticks:{maxRotation:45}},\n              y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n            }\n          }\n        });\n      }\n\n      makeChart('teacher-chart-top','top',top,'rgba(22,163,74,.78)');\n      makeChart('teacher-chart-bottom','bottom',bottom,'rgba(220,38,38,.78)');\n    }\n\n    function buildChangeCharts(){\n      var s=getSelected();\n      var current=s.year;\n      var prev=getPrevYear(current);\n\n      if(!prev){\n        el('teacher-change-sub').textContent=\n          current+' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('teacher-change-trend-sub').textContent=\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n        return;\n      }\n\n      var rowsCurrent=melakaRows.filter(function(r){\n        return yr(r)===current;\n      });\n\n      var rowsPrev=melakaRows.filter(function(r){\n        return yr(r)===prev;\n      });\n\n      var districts=Array.from(new Set(rowsCurrent.map(function(r){\n        return r.district;\n      })))\n        .filter(function(d){return d&&d!=='All Districts';})\n        .sort();\n\n      var data=districts.map(function(d){\n        var c=sumTeachers(\n          rowsCurrent.filter(function(r){\n            return r.district===d&&rowMatches(r,s,{ignoreDistrict:true});\n          })\n        );\n\n        var p=sumTeachers(\n          rowsPrev.filter(function(r){\n            return r.district===d&&rowMatches(r,s,{ignoreDistrict:true});\n          })\n        );\n\n        return {\n          district:d,\n          change:c-p\n        };\n      }).sort(function(a,b){return b.change-a.change;});\n\n      el('teacher-change-sub').textContent=\n        current+' berbanding '+prev+\n        ' \u00b7 nilai positif menunjukkan bilangan guru meningkat';\n\n      destroyChart('change');\n\n      charts.change=new Chart(el('teacher-chart-change').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.district;}),\n          datasets:[{\n            label:'Perubahan',\n            data:data.map(function(d){return d.change;}),\n            backgroundColor:'rgba(124,58,237,.78)',\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Perubahan: '+\n                    (c.parsed.y>0?'+':'')+\n                    fmt(c.parsed.y);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{display:false},ticks:{maxRotation:45}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n\n      var selected=selectedRows({});\n\n      var years=Array.from(new Set(selected.map(yr)))\n        .sort(function(a,b){return a-b;});\n\n      var trend=years.map(function(y,i){\n        if(i===0){\n          return {year:y,change:null};\n        }\n\n        var prevYear=years[i-1];\n\n        var now=sumTeachers(\n          selected.filter(function(x){\n            return yr(x)===y;\n          })\n        );\n\n        var before=sumTeachers(\n          selected.filter(function(x){\n            return yr(x)===prevYear;\n          })\n        );\n\n        return {\n          year:String(y)+' berbanding '+String(prevYear),\n          change:now-before\n        };\n      }).filter(function(d){return d.change!==null;});\n\n      el('teacher-change-trend-sub').textContent=\n        'Purata perubahan bilangan guru bagi tempoh data yang tersedia';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend=new Chart(el('teacher-chart-change-trend').getContext('2d'),{\n        type:'line',\n        data:{\n          labels:trend.map(function(d){return d.year;}),\n          datasets:[{\n            label:'Perubahan',\n            data:trend.map(function(d){return d.change;}),\n            borderColor:'#7c3aed',\n            backgroundColor:'rgba(124,58,237,.15)',\n            fill:true,\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:4\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:{\n              backgroundColor:'#0f172a',\n              callbacks:{\n                label:function(c){\n                  return ' Perubahan: '+\n                    (c.parsed.y>0?'+':'')+\n                    fmt(c.parsed.y);\n                }\n              }\n            }\n          },\n          scales:{\n            x:{grid:{color:'#dbeafe'}},\n            y:{grid:{color:'#dbeafe'},ticks:{callback:function(v){return fmt(v);}}}\n          }\n        }\n      });\n    }\n\n    function buildStateChart(){\n      var s=getSelected();\n\n      var rows=allRows.filter(function(r){\n        return yr(r)===s.year &&\n          r.district==='All Districts' &&\n          (s.stage==='all'||r.stage===s.stage) &&\n          r.sex===s.sex &&\n          r.state!=='Malaysia';\n      });\n\n      var states=Array.from(new Set(rows.map(function(r){\n        return r.state;\n      }))).sort();\n\n      var data=states.map(function(state){\n        return {\n          state:state,\n          total:sumTeachers(\n            rows.filter(function(x){\n              return x.state===state;\n            })\n          )\n        };\n      }).sort(function(a,b){return b.total-a.total;});\n\n      el('teacher-state-sub').textContent=\n        s.year+' \u00b7 semua negeri \u00b7 '+\n        labelStage(s.stage)+' \u00b7 '+\n        labelSex(s.sex);\n\n      destroyChart('state');\n\n      charts.state=new Chart(el('teacher-chart-state').getContext('2d'),{\n        type:'bar',\n        data:{\n          labels:data.map(function(d){return d.state;}),\n          datasets:[{\n            label:'Guru',\n            data:data.map(function(d){return d.total;}),\n            backgroundColor:data.map(function(d){\n              return d.state==='Melaka'\n                ? 'rgba(249,115,22,.95)'\n                : 'rgba(37,99,235,.70)';\n            }),\n            borderColor:data.map(function(d){\n              return d.state==='Melaka'\n                ? '#f97316'\n                : 'rgba(37,99,235,.70)';\n            }),\n            borderWidth:data.map(function(d){\n              return d.state==='Melaka'?3:0;\n            }),\n            borderRadius:8\n          }]\n        },\n        options:{\n          responsive:true,\n          maintainAspectRatio:false,\n          plugins:{\n            legend:{display:false},\n            tooltip:tooltipY()\n          },\n          scales:{\n            x:{\n              grid:{display:false},\n              ticks:{\n                maxRotation:45,\n                color:function(c){\n                  return c.tick.label==='Melaka'\n                    ? '#f97316'\n                    : '#64748b';\n                },\n                font:function(c){\n                  return {\n                    weight:c.tick.label==='Melaka'?'800':'400',\n                    size:c.tick.label==='Melaka'?13:12\n                  };\n                }\n              }\n            },\n            y:{\n              grid:{color:'#dbeafe'},\n              ticks:{callback:function(v){return fmt(v);}}\n            }\n          }\n        }\n      });\n    }\n\n    function activeTab(){\n      var a=document.querySelector('#teacher-wrap .tbtn.active');\n      return a?a.getAttribute('data-tab'):'trend';\n    }\n\n    function redraw(){\n      updateCards();\n\n      if(activeTab()==='trend')buildTrendChart();\n      if(activeTab()==='district')buildDistrictChart();\n      if(activeTab()==='stage')buildStageCharts();\n      if(activeTab()==='sex')buildSexCharts();\n      if(activeTab()==='topbottom')buildTopBottomCharts();\n      if(activeTab()==='change')buildChangeCharts();\n      if(activeTab()==='state')buildStateChart();\n    }\n\n    document.querySelectorAll('#teacher-wrap .tbtn').forEach(function(btn){\n      btn.addEventListener('click',function(){\n        document.querySelectorAll('#teacher-wrap .tbtn').forEach(function(b){\n          b.classList.remove('active');\n        });\n\n        btn.classList.add('active');\n\n        ['trend','district','stage','sex','topbottom','change','state'].forEach(function(t){\n          el('teacher-tab-'+t).style.display='none';\n        });\n\n        el('teacher-tab-'+btn.getAttribute('data-tab')).style.display='';\n\n        redraw();\n      });\n    });\n\n    ['teacher-year','teacher-district','teacher-stage','teacher-sex'].forEach(function(id){\n      el(id).addEventListener('change',redraw);\n    });\n\n    fetch(API_URL)\n      .then(function(r){\n        if(!r.ok)throw new Error('HTTP '+r.status);\n        return r.json();\n      })\n      .then(function(data){\n\n        allRows=Array.isArray(data)?data:[];\n\n        melakaRows=allRows.filter(function(r){\n          return String(r.state||'').trim().toLowerCase()==='melaka';\n        });\n\n        if(!melakaRows.length){\n          throw new Error('Tiada rekod Melaka ditemui.');\n        }\n\n        populateFilters();\n\n        el('teacher-loading').style.display='none';\n        el('teacher-area').style.display='';\n\n        redraw();\n      })\n      .catch(function(e){\n        el('teacher-loading').innerHTML=\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">'+\n          'Gagal memuatkan data'+\n          '<\/div>'+\n          '<p style=\"font-size:13px;color:#64748b\">'+\n          e.message+\n          '<\/p>';\n      });\n  }\n\n  if(document.readyState==='loading'){\n    document.addEventListener('DOMContentLoaded',init);\n  }else{\n    init();\n  }\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#student-wrap, #student-wrap * { box-sizing:border-box; }\n#student-wrap{\n  --bg:#f8fafc;--card:#ffffff;--soft:#eff6ff;--border:#dbeafe;\n  --text:#0f172a;--muted:#64748b;--accent:#2563eb;\n  --green:#16a34a;--red:#dc2626;--orange:#f97316;--purple:#7c3aed;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n  width:100%;padding:28px;background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);border-radius:22px;font-family:Arial,sans-serif;color:var(--text);\n}\n#student-wrap .dash{max-width:1180px;margin:0 auto;}\n#student-wrap .hdr,#student-wrap .cc,#student-wrap .sc,#student-wrap .sbox{background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow);}\n#student-wrap .hdr{border-radius:20px;padding:26px;margin-bottom:20px;}\n#student-wrap .eyebrow{color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;margin-bottom:10px;}\n#student-wrap h2{margin:0;font-size:clamp(26px,4vw,44px);line-height:1.1;font-weight:800;}\n#student-wrap .hdesc{color:var(--muted);margin-top:10px;line-height:1.6;}\n#student-wrap .filter-bar{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;background:#fff;border:1px solid var(--border);border-radius:16px;padding:18px;margin-bottom:20px;box-shadow:var(--shadow);}\n#student-wrap .cg{display:flex;flex-direction:column;gap:7px;}\n#student-wrap label{font-size:11px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;}\n#student-wrap select{width:100%;min-height:44px;padding:10px 40px 10px 12px;border:1.5px solid #bfdbfe;border-radius:10px;background-color:#fff;color:var(--text);font-size:14px;font-weight:600;outline:none;appearance:none;cursor:pointer;background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");background-repeat:no-repeat;background-position:right 12px center;}\n#student-wrap select:focus{border-color:var(--accent);box-shadow:0 0 0 4px rgba(37,99,235,.12);}\n#student-wrap .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px;}\n#student-wrap .sc{border-radius:16px;padding:18px;border-left:4px solid var(--accent);}\n#student-wrap .sl{color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase;margin-bottom:8px;}\n#student-wrap .sv{font-size:25px;font-weight:800;}\n#student-wrap .su{color:var(--muted);font-size:12px;margin-top:4px;}\n#student-wrap .tab-bar{display:flex;justify-content:space-between;gap:10px;background:var(--soft);border:1px solid var(--border);border-radius:999px;padding:8px;margin-bottom:18px;overflow-x:auto;}\n#student-wrap .tbtn{flex:1;border:0;background:transparent;padding:12px 18px;border-radius:999px;color:var(--muted);font-weight:800;cursor:pointer;white-space:nowrap;}\n#student-wrap .tbtn.active{background:#fff;color:var(--accent);box-shadow:0 5px 14px rgba(15,23,42,.08);}\n#student-wrap .cc{border-radius:20px;padding:24px;margin-bottom:18px;}\n#student-wrap .ct{font-size:18px;font-weight:800;margin-bottom:5px;}\n#student-wrap .cs{color:var(--muted);font-size:13px;margin-bottom:18px;}\n#student-wrap .two{display:grid;grid-template-columns:1fr 1fr;gap:18px;}\n#student-wrap .chart-box{position:relative;height:350px;}\n#student-wrap .chart-box-sm{position:relative;height:320px;}\n#student-wrap .sbox{border-radius:20px;padding:60px 24px;text-align:center;color:var(--muted);}\n#student-wrap .spin{width:38px;height:38px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:student-spin .8s linear infinite;margin:0 auto 16px;}\n@keyframes student-spin{to{transform:rotate(360deg);}}\n@media(max-width:1024px){#student-wrap .filter-bar,#student-wrap .stats{grid-template-columns:repeat(2,1fr);}#student-wrap .two{grid-template-columns:1fr;}}\n@media(max-width:600px){#student-wrap{padding:14px;border-radius:16px;}#student-wrap .filter-bar,#student-wrap .stats{grid-template-columns:1fr;}#student-wrap .tab-bar{justify-content:flex-start;}#student-wrap .tbtn{flex:0 0 auto;}#student-wrap .chart-box,#student-wrap .chart-box-sm{height:300px;}}\n<\/style>\n\n<div id=\"student-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Enrolmen Murid Melaka<\/div>\n      <h2>Papan Pemuka Enrolmen Murid di Sekolah Kerajaan<\/h2>\n      <p class=\"hdesc\">\n        Visualisasi enrolmen murid di sekolah kerajaan di Melaka mengikut tahun, daerah, peringkat sekolah dan jantina.\n      <\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"student-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Daerah<\/label>\n        <select id=\"student-district\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Peringkat<\/label>\n        <select id=\"student-stage\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Jantina<\/label>\n        <select id=\"student-sex\"><\/select>\n      <\/div>\n    <\/div>\n\n    <div class=\"stats\" id=\"student-stats\" style=\"display:none\">\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"student-stat-year\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Jumlah Murid<\/div>\n        <div class=\"sv\" id=\"student-stat-total\">\u2014<\/div>\n        <div class=\"su\">murid<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Daerah Tertinggi<\/div>\n        <div class=\"sv\" id=\"student-stat-high\">\u2014<\/div>\n        <div class=\"su\" id=\"student-stat-high-name\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Perubahan<\/div>\n        <div class=\"sv\" id=\"student-stat-change\">\u2014<\/div>\n        <div class=\"su\" id=\"student-stat-change-year\">berbanding tahun sebelumnya<\/div>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"sbox\" id=\"student-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod enrolmen murid Melaka.<\/div>\n    <\/div>\n\n    <div id=\"student-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"district\">Daerah<\/button>\n        <button class=\"tbtn\" data-tab=\"stage\">Peringkat<\/button>\n        <button class=\"tbtn\" data-tab=\"sex\">Jantina<\/button>\n        <button class=\"tbtn\" data-tab=\"topbottom\">Tertinggi &#038; Terendah<\/button>\n        <button class=\"tbtn\" data-tab=\"change\">Perubahan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <div id=\"student-tab-trend\">\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Enrolmen Murid Mengikut Masa<\/div>\n          <div class=\"cs\" id=\"student-trend-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"student-chart-trend\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"student-tab-district\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Enrolmen Murid Mengikut Daerah<\/div>\n          <div class=\"cs\" id=\"student-district-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"student-chart-district\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div id=\"student-tab-stage\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Murid Mengikut Peringkat Sekolah<\/div>\n            <div class=\"cs\" id=\"student-stage-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"student-chart-stage\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Mengikut Peringkat<\/div>\n            <div class=\"cs\">Trend enrolmen murid mengikut peringkat sekolah.<\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"student-chart-stage-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"student-tab-sex\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Murid Mengikut Jantina<\/div>\n            <div class=\"cs\" id=\"student-sex-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"student-chart-sex\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Mengikut Jantina<\/div>\n            <div class=\"cs\">Trend enrolmen murid mengikut jantina.<\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"student-chart-sex-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"student-tab-topbottom\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Daerah dengan Enrolmen Murid Tertinggi<\/div>\n            <div class=\"cs\" id=\"student-top-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"student-chart-top\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Daerah dengan Enrolmen Murid Terendah<\/div>\n            <div class=\"cs\" id=\"student-bottom-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"student-chart-bottom\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"student-tab-change\" style=\"display:none\">\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Perubahan Enrolmen Murid Mengikut Daerah<\/div>\n            <div class=\"cs\" id=\"student-change-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"student-chart-change\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Purata Perubahan<\/div>\n            <div class=\"cs\" id=\"student-change-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"student-chart-change-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n      <\/div>\n\n      <div id=\"student-tab-state\" style=\"display:none\">\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Enrolmen Murid Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"student-state-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"student-chart-state\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  function init(){\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL='https:\/\/api.data.gov.my\/data-catalogue?id=enrolment_school_district&limit=9000';\n    var FIXED_STATE='Melaka';\n    var allRows=[], melakaRows=[], charts={};\n\n    Chart.defaults.color='#64748b';\n    Chart.defaults.borderColor='#dbeafe';\n    Chart.defaults.font.family='Arial, sans-serif';\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(r){\n      return parseInt(String(r.date||'').slice(0,4),10);\n    }\n\n    function num(v){\n      return Number(v)||0;\n    }\n\n    function fmt(v){\n      return Number(v).toLocaleString(\n        'ms-MY',\n        {maximumFractionDigits:0}\n      );\n    }\n\n    function destroyChart(name){\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    function labelStage(v){\n      if(v==='all') return 'Semua Peringkat';\n      if(v==='primary') return 'Rendah';\n      if(v==='secondary') return 'Menengah';\n      if(v==='post_secondary') return 'Lepas Menengah';\n      return v;\n    }\n\n    function labelSex(v){\n      if(v==='both') return 'Kedua-dua Jantina';\n      if(v==='male') return 'Lelaki';\n      if(v==='female') return 'Perempuan';\n      return v;\n    }\n\n    function getSelected(){\n      return {\n        year:parseInt(el('student-year').value,10),\n        district:el('student-district').value,\n        stage:el('student-stage').value,\n        sex:el('student-sex').value\n      };\n    }\n\n    function rowMatches(row, s, opts){\n      opts=opts||{};\n\n      var districtMatch=\n        opts.ignoreDistrict ||\n        s.district==='all' ||\n        row.district===s.district;\n\n      var stageMatch=\n        opts.ignoreStage ||\n        s.stage==='all' ||\n        row.stage===s.stage;\n\n      var sexMatch=\n        opts.ignoreSex ||\n        row.sex===s.sex;\n\n      return districtMatch&&stageMatch&&sexMatch;\n    }\n\n    function selectedRows(opts){\n      var s=getSelected();\n\n      return melakaRows.filter(function(r){\n        return rowMatches(r,s,opts);\n      });\n    }\n\n    function rowsForYear(opts){\n      var s=getSelected();\n\n      return selectedRows(opts).filter(function(r){\n        return yr(r)===s.year;\n      });\n    }\n\n    function sumStudents(rows){\n      return rows.reduce(function(total,r){\n        return total+num(r.students);\n      },0);\n    }\n\n    function getPrevYear(current){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){\n          return !isNaN(y)&&y<current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length?years[0]:null;\n    }\n\n    function populateFilters(){\n      var years=Array.from(new Set(melakaRows.map(yr)))\n        .filter(function(y){\n          return !isNaN(y);\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('student-year').innerHTML=\n        years.map(function(y){\n          return '<option value=\"'+y+'\">'+y+'<\/option>';\n        }).join('');\n\n      if(years.length){\n        el('student-year').value=years[0];\n      }\n\n      var districts=Array.from(\n        new Set(\n          melakaRows.map(function(r){\n            return r.district;\n          })\n        )\n      )\n      .filter(function(d){\n        return d&&d!=='All Districts';\n      })\n      .sort();\n\n      el('student-district').innerHTML=\n        '<option value=\"all\">Semua Daerah<\/option>'+\n        districts.map(function(d){\n          return '<option value=\"'+d+'\">'+d+'<\/option>';\n        }).join('');\n\n      var stages=Array.from(\n        new Set(\n          melakaRows.map(function(r){\n            return r.stage;\n          })\n        )\n      )\n      .filter(Boolean)\n      .sort();\n\n      el('student-stage').innerHTML=\n        '<option value=\"all\">Semua Peringkat<\/option>'+\n        stages.map(function(s){\n          return '<option value=\"'+s+'\">'+labelStage(s)+'<\/option>';\n        }).join('');\n\n      el('student-sex').innerHTML=\n        '<option value=\"both\">Kedua-dua Jantina<\/option>'+\n        '<option value=\"female\">Perempuan<\/option>'+\n        '<option value=\"male\">Lelaki<\/option>';\n    }\n\n    function updateCards(){\n      var s=getSelected();\n      var rows=rowsForYear({});\n      var total=sumStudents(rows);\n\n      var districtRows=\n        rowsForYear({ignoreDistrict:true})\n        .filter(function(r){\n          return r.district!=='All Districts';\n        });\n\n      var districts=Array.from(\n        new Set(\n          districtRows.map(function(r){\n            return r.district;\n          })\n        )\n      );\n\n      var districtData=\n        districts.map(function(d){\n\n          return {\n            district:d,\n            total:sumStudents(\n              districtRows.filter(function(x){\n                return x.district===d;\n              })\n            )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      var high=districtData[0];\n      var prev=getPrevYear(s.year);\n\n      var prevRows=\n        prev\n          ? selectedRows({}).filter(function(r){\n              return yr(r)===prev;\n            })\n          : [];\n\n      var prevTotal=sumStudents(prevRows);\n\n      var change=\n        prevRows.length\n          ? total-prevTotal\n          : null;\n\n      el('student-stat-year').textContent=\n        s.year||'\u2014';\n\n      el('student-stat-total').textContent=\n        rows.length\n          ? fmt(total)\n          : '\u2014';\n\n      el('student-stat-high').textContent=\n        high\n          ? fmt(high.total)\n          : '\u2014';\n\n      el('student-stat-high-name').textContent=\n        high\n          ? high.district\n          : '\u2014';\n\n      el('student-stat-change').textContent=\n        change!==null\n          ? ((change>0?'+':'')+fmt(change))\n          : '\u2014';\n\n      el('student-stat-change-year').textContent=\n        prev\n          ? 'berbanding '+prev\n          : 'berbanding tahun sebelumnya';\n\n      el('student-stats').style.display='';\n    }\n\n    function tooltipY(){\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n            return ' '+\n              c.dataset.label+\n              ': '+\n              fmt(c.parsed.y);\n          }\n        }\n      };\n    }\n\n    function buildTrendChart(){\n      var s=getSelected();\n      var rows=selectedRows({});\n\n      var years=Array.from(\n        new Set(rows.map(yr))\n      ).sort(function(a,b){\n        return a-b;\n      });\n\n      var data=\n        years.map(function(y){\n\n          return {\n            year:y,\n\n            total:\n              sumStudents(\n                rows.filter(function(x){\n                  return yr(x)===y;\n                })\n              )\n          };\n\n        });\n\n      el('student-trend-sub').textContent=\n        FIXED_STATE+\n        ' \u00b7 '+\n        (\n          s.district==='all'\n            ? 'Semua Daerah'\n            : s.district\n        )+\n        ' \u00b7 '+\n        labelStage(s.stage)+\n        ' \u00b7 '+\n        labelSex(s.sex);\n\n      destroyChart('trend');\n\n      charts.trend=\n        new Chart(\n          el('student-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.year;\n                }),\n\n              datasets:[{\n                label:'Murid',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildDistrictChart(){\n      var s=getSelected();\n\n      var rows=\n        rowsForYear({ignoreDistrict:true})\n        .filter(function(r){\n          return r.district!=='All Districts';\n        });\n\n      var districts=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.district;\n            })\n          )\n        ).sort();\n\n      var data=\n        districts.map(function(d){\n\n          return {\n            district:d,\n\n            total:\n              sumStudents(\n                rows.filter(function(x){\n                  return x.district===d;\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      el('student-district-sub').textContent=\n        s.year+\n        ' \u00b7 Semua Daerah \u00b7 '+\n        labelStage(s.stage)+\n        ' \u00b7 '+\n        labelSex(s.sex);\n\n      destroyChart('district');\n\n      charts.district=\n        new Chart(\n          el('student-chart-district')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.district;\n                }),\n\n              datasets:[{\n                label:'Murid',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildStageCharts(){\n      var s=getSelected();\n      var rows=rowsForYear({ignoreStage:true});\n\n      var stages=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.stage;\n            })\n          )\n        )\n        .filter(Boolean)\n        .sort();\n\n      var data=\n        stages.map(function(stage){\n\n          return {\n            stage:labelStage(stage),\n            key:stage,\n\n            total:\n              sumStudents(\n                rows.filter(function(x){\n                  return x.stage===stage;\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      el('student-stage-sub').textContent=\n        s.year+\n        ' \u00b7 '+\n        (\n          s.district==='all'\n            ? 'Semua Daerah'\n            : s.district\n        )+\n        ' \u00b7 Semua Peringkat \u00b7 '+\n        labelSex(s.sex);\n\n      destroyChart('stage');\n\n      charts.stage=\n        new Chart(\n          el('student-chart-stage')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.stage;\n                }),\n\n              datasets:[{\n                label:'Murid',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#f97316',\n                  '#16a34a'\n                ],\n\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n                      return ' '+\n                        c.label+\n                        ': '+\n                        fmt(c.parsed);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var trendRows=\n        selectedRows({ignoreStage:true});\n\n      var years=\n        Array.from(\n          new Set(\n            trendRows.map(yr)\n          )\n        )\n        .sort(function(a,b){\n          return a-b;\n        });\n\n      var datasets=\n        stages.map(function(stage){\n\n          return {\n            label:labelStage(stage),\n\n            data:\n              years.map(function(y){\n\n                return sumStudents(\n                  trendRows.filter(function(r){\n                    return (\n                      yr(r)===y &&\n                      r.stage===stage\n                    );\n                  })\n                );\n\n              }),\n\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:3\n          };\n\n        });\n\n      destroyChart('stageTrend');\n\n      charts.stageTrend=\n        new Chart(\n          el('student-chart-stage-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n              datasets:datasets\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildSexCharts(){\n      var s=getSelected();\n      var rows=rowsForYear({ignoreSex:true});\n\n      var sexes=[\n        'both',\n        'female',\n        'male'\n      ];\n\n      var data=\n        sexes.map(function(sex){\n\n          return {\n            sex:labelSex(sex),\n            key:sex,\n\n            total:\n              sumStudents(\n                rows.filter(function(x){\n                  return x.sex===sex;\n                })\n              )\n          };\n\n        })\n        .filter(function(d){\n          return d.total>0;\n        });\n\n      el('student-sex-sub').textContent=\n        s.year+\n        ' \u00b7 '+\n        (\n          s.district==='all'\n            ? 'Semua Daerah'\n            : s.district\n        )+\n        ' \u00b7 '+\n        labelStage(s.stage)+\n        ' \u00b7 Kedua-dua Jantina, Perempuan dan Lelaki';\n\n      destroyChart('sex');\n\n      charts.sex=\n        new Chart(\n          el('student-chart-sex')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.sex;\n                }),\n\n              datasets:[{\n                label:'Murid',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#f97316',\n                  '#16a34a'\n                ],\n\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n                      return ' '+\n                        c.label+\n                        ': '+\n                        fmt(c.parsed);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var trendRows=\n        selectedRows({ignoreSex:true});\n\n      var years=\n        Array.from(\n          new Set(\n            trendRows.map(yr)\n          )\n        )\n        .sort(function(a,b){\n          return a-b;\n        });\n\n      var datasets=\n        sexes.map(function(sex){\n\n          return {\n            label:labelSex(sex),\n\n            data:\n              years.map(function(y){\n\n                return sumStudents(\n                  trendRows.filter(function(r){\n                    return (\n                      yr(r)===y &&\n                      r.sex===sex\n                    );\n                  })\n                );\n\n              }),\n\n            tension:.35,\n            borderWidth:2.5,\n            pointRadius:3\n          };\n\n        });\n\n      destroyChart('sexTrend');\n\n      charts.sexTrend=\n        new Chart(\n          el('student-chart-sex-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n              datasets:datasets\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildTopBottomCharts(){\n      var s=getSelected();\n\n      var rows=\n        rowsForYear({ignoreDistrict:true})\n        .filter(function(r){\n          return r.district!=='All Districts';\n        });\n\n      var districts=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.district;\n            })\n          )\n        ).sort();\n\n      var data=\n        districts.map(function(d){\n\n          return {\n            district:d,\n\n            total:\n              sumStudents(\n                rows.filter(function(x){\n                  return x.district===d;\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      var top=data.slice(0,10);\n      var bottom=data.slice(-10).reverse();\n\n      el('student-top-sub').textContent=\n        s.year+\n        ' \u00b7 daerah dengan enrolmen murid tertinggi';\n\n      el('student-bottom-sub').textContent=\n        s.year+\n        ' \u00b7 daerah dengan enrolmen murid terendah';\n\n      function makeChart(\n        id,\n        name,\n        data,\n        color\n      ){\n\n        destroyChart(name);\n\n        charts[name]=\n          new Chart(\n            el(id).getContext('2d'),\n            {\n              type:'bar',\n\n              data:{\n                labels:\n                  data.map(function(d){\n                    return d.district;\n                  }),\n\n                datasets:[{\n                  label:'Murid',\n\n                  data:\n                    data.map(function(d){\n                      return d.total;\n                    }),\n\n                  backgroundColor:color,\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n                responsive:true,\n                maintainAspectRatio:false,\n\n                plugins:{\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipY()\n                },\n\n                scales:{\n                  x:{\n                    grid:{\n                      display:false\n                    },\n\n                    ticks:{\n                      maxRotation:45\n                    }\n                  },\n\n                  y:{\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(v){\n                        return fmt(v);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n      }\n\n      makeChart(\n        'student-chart-top',\n        'top',\n        top,\n        'rgba(22,163,74,.78)'\n      );\n\n      makeChart(\n        'student-chart-bottom',\n        'bottom',\n        bottom,\n        'rgba(220,38,38,.78)'\n      );\n    }\n\n    function buildChangeCharts(){\n      var s=getSelected();\n      var current=s.year;\n      var prev=getPrevYear(current);\n\n      if(!prev){\n\n        el('student-change-sub').textContent=\n          current+\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('student-change-trend-sub').textContent=\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n      var rowsCurrent=\n        melakaRows.filter(function(r){\n          return yr(r)===current;\n        });\n\n      var rowsPrev=\n        melakaRows.filter(function(r){\n          return yr(r)===prev;\n        });\n\n      var districts=\n        Array.from(\n          new Set(\n            rowsCurrent.map(function(r){\n              return r.district;\n            })\n          )\n        )\n        .filter(function(d){\n          return d&&d!=='All Districts';\n        })\n        .sort();\n\n      var data=\n        districts.map(function(d){\n\n          var c=\n            sumStudents(\n              rowsCurrent.filter(function(r){\n\n                return (\n                  r.district===d &&\n                  rowMatches(\n                    r,\n                    s,\n                    {ignoreDistrict:true}\n                  )\n                );\n\n              })\n            );\n\n          var p=\n            sumStudents(\n              rowsPrev.filter(function(r){\n\n                return (\n                  r.district===d &&\n                  rowMatches(\n                    r,\n                    s,\n                    {ignoreDistrict:true}\n                  )\n                );\n\n              })\n            );\n\n          return {\n            district:d,\n            change:c-p\n          };\n\n        })\n        .sort(function(a,b){\n          return b.change-a.change;\n        });\n\n      el('student-change-sub').textContent=\n        current+\n        ' berbanding '+\n        prev+\n        ' \u00b7 nilai positif menunjukkan bilangan murid meningkat';\n\n      destroyChart('change');\n\n      charts.change=\n        new Chart(\n          el('student-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.district;\n                }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:\n                  data.map(function(d){\n                    return d.change;\n                  }),\n\n                backgroundColor:'rgba(124,58,237,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return ' Perubahan: '+\n                        (c.parsed.y>0?'+':'')+\n                        fmt(c.parsed.y);\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var selected=\n        selectedRows({});\n\n      var years=\n        Array.from(\n          new Set(\n            selected.map(yr)\n          )\n        )\n        .sort(function(a,b){\n          return a-b;\n        });\n\n      var trend=\n        years.map(function(y,i){\n\n          if(i===0){\n\n            return {\n              year:y,\n              change:null\n            };\n\n          }\n\n          var prevYear=\n            years[i-1];\n\n          var now=\n            sumStudents(\n              selected.filter(function(x){\n                return yr(x)===y;\n              })\n            );\n\n          var before=\n            sumStudents(\n              selected.filter(function(x){\n                return yr(x)===prevYear;\n              })\n            );\n\n          return {\n            year:\n              String(y)+\n              ' berbanding '+\n              String(prevYear),\n\n            change:\n              now-before\n          };\n\n        })\n        .filter(function(d){\n          return d.change!==null;\n        });\n\n      el('student-change-trend-sub').textContent=\n        'Purata perubahan enrolmen murid bagi tempoh data yang tersedia';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend=\n        new Chart(\n          el('student-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                trend.map(function(d){\n                  return d.year;\n                }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:\n                  trend.map(function(d){\n                    return d.change;\n                  }),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return ' Perubahan: '+\n                        (c.parsed.y>0?'+':'')+\n                        fmt(c.parsed.y);\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildStateChart(){\n      var s=getSelected();\n\n      var rows=\n        allRows.filter(function(r){\n\n          return (\n            yr(r)===s.year &&\n            r.district==='All Districts' &&\n            (\n              s.stage==='all' ||\n              r.stage===s.stage\n            ) &&\n            rowMatches(\n              r,\n              {\n                district:'all',\n                stage:s.stage,\n                sex:s.sex\n              },\n              {\n                ignoreDistrict:true\n              }\n            ) &&\n            r.state!=='Malaysia'\n          );\n\n        });\n\n      var states=\n        Array.from(\n          new Set(\n            rows.map(function(r){\n              return r.state;\n            })\n          )\n        ).sort();\n\n      var data=\n        states.map(function(state){\n\n          return {\n            state:state,\n\n            total:\n              sumStudents(\n                rows.filter(function(x){\n                  return x.state===state;\n                })\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      el('student-state-sub').textContent=\n        s.year+\n        ' \u00b7 semua negeri \u00b7 '+\n        labelStage(s.stage)+\n        ' \u00b7 '+\n        labelSex(s.sex);\n\n      destroyChart('state');\n\n      charts.state=\n        new Chart(\n          el('student-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:[{\n                label:'Murid',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.state==='Melaka'\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(37,99,235,.70)';\n\n                  }),\n\n                borderColor:\n                  data.map(function(d){\n\n                    return d.state==='Melaka'\n                      ? '#f97316'\n                      : 'rgba(37,99,235,.70)';\n\n                  }),\n\n                borderWidth:\n                  data.map(function(d){\n\n                    return d.state==='Melaka'\n                      ? 3\n                      : 0;\n\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(c){\n\n                      return c.tick.label==='Melaka'\n                        ? '#f97316'\n                        : '#64748b';\n\n                    },\n\n                    font:function(c){\n\n                      return {\n                        weight:\n                          c.tick.label==='Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          c.tick.label==='Melaka'\n                            ? 13\n                            : 12\n                      };\n\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function activeTab(){\n\n      var a=\n        document.querySelector(\n          '#student-wrap .tbtn.active'\n        );\n\n      return a\n        ? a.getAttribute('data-tab')\n        : 'trend';\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      if(activeTab()==='trend'){\n        buildTrendChart();\n      }\n\n      if(activeTab()==='district'){\n        buildDistrictChart();\n      }\n\n      if(activeTab()==='stage'){\n        buildStageCharts();\n      }\n\n      if(activeTab()==='sex'){\n        buildSexCharts();\n      }\n\n      if(activeTab()==='topbottom'){\n        buildTopBottomCharts();\n      }\n\n      if(activeTab()==='change'){\n        buildChangeCharts();\n      }\n\n      if(activeTab()==='state'){\n        buildStateChart();\n      }\n    }\n\n    document\n      .querySelectorAll(\n        '#student-wrap .tbtn'\n      )\n      .forEach(function(btn){\n\n        btn.addEventListener(\n          'click',\n          function(){\n\n            document\n              .querySelectorAll(\n                '#student-wrap .tbtn'\n              )\n              .forEach(function(b){\n\n                b.classList.remove(\n                  'active'\n                );\n\n              });\n\n            btn.classList.add(\n              'active'\n            );\n\n            [\n              'trend',\n              'district',\n              'stage',\n              'sex',\n              'topbottom',\n              'change',\n              'state'\n            ].forEach(function(t){\n\n              el(\n                'student-tab-'+t\n              ).style.display='none';\n\n            });\n\n            el(\n              'student-tab-'+\n              btn.getAttribute('data-tab')\n            ).style.display='';\n\n            redraw();\n          }\n        );\n      });\n\n    [\n      'student-year',\n      'student-district',\n      'student-stage',\n      'student-sex'\n    ].forEach(function(id){\n\n      el(id).addEventListener(\n        'change',\n        redraw\n      );\n\n    });\n\n    fetch(API_URL)\n\n      .then(function(r){\n\n        if(!r.ok){\n\n          throw new Error(\n            'HTTP '+r.status\n          );\n\n        }\n\n        return r.json();\n      })\n\n      .then(function(data){\n\n        allRows=\n          Array.isArray(data)\n            ? data\n            : [];\n\n        melakaRows=\n          allRows.filter(function(r){\n\n            return String(\n              r.state||''\n            )\n            .trim()\n            .toLowerCase()\n            ===\n            'melaka';\n\n          });\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            'Tiada rekod Melaka ditemui.'\n          );\n\n        }\n\n        populateFilters();\n\n        el('student-loading')\n          .style.display='none';\n\n        el('student-area')\n          .style.display='';\n\n        redraw();\n      })\n\n      .catch(function(e){\n\n        el('student-loading')\n          .innerHTML=\n\n          '<div style=\"color:#dc2626;font-size:16px;font-weight:800;margin-bottom:8px\">'+\n          'Gagal memuatkan data'+\n          '<\/div>'+\n          '<p style=\"font-size:13px;color:#64748b\">'+\n          e.message+\n          '<\/p>';\n\n      });\n\n  }\n\n  if(document.readyState==='loading'){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<div style=\"text-align: center; margin: 40px 0 30px 0;\">\n  <h2 style=\"\n    font-size: 32px;\n    font-weight: 700;\n    color: #1f3c88;\n    margin-bottom: 10px;\n    letter-spacing: 0.5px;\n  \">\n    Dashboard Kategori Keselamatan Awam\n  <\/h2>\n\n  <div style=\"\n    width: 80px;\n    height: 4px;\n    background-color: #1f3c88;\n    margin: 0 auto;\n    border-radius: 4px;\n  \"><\/div>\n<\/div>\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#crime-wrap, #crime-wrap * { box-sizing:border-box; }\n\n#crime-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#crime-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#crime-wrap .hdr,\n#crime-wrap .cc,\n#crime-wrap .sc,\n#crime-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#crime-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#crime-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#crime-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#crime-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#crime-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#crime-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#crime-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#crime-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#crime-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#crime-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#crime-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#crime-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#crime-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#crime-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n}\n\n#crime-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#crime-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#crime-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#crime-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#crime-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#crime-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n}\n\n#crime-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#crime-wrap .chart-box{\n  position:relative;\n  height:370px;\n}\n\n#crime-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#crime-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#crime-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:crime-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes crime-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n  #crime-wrap .filter-bar,\n  #crime-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #crime-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n  #crime-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #crime-wrap .filter-bar,\n  #crime-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #crime-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #crime-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #crime-wrap .chart-box,\n  #crime-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n<div id=\"crime-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Statistik Jenayah Melaka<\/div>\n\n      <h2>Papan Pemuka Jenayah Mengikut Daerah &#038; Jenis Jenayah<\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi bilangan jenayah di Melaka mengikut tahun, daerah polis,\n        kategori jenayah dan jenis jenayah. Data merangkumi jenayah kekerasan\n        dan jenayah harta benda berdasarkan rekod jenayah dalam dataset.\n      <\/p>\n    <\/div>\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"crime-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Daerah Polis<\/label>\n        <select id=\"crime-district\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Kategori Jenayah<\/label>\n        <select id=\"crime-category\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Jenis Jenayah<\/label>\n        <select id=\"crime-type\"><\/select>\n      <\/div>\n\n    <\/div>\n\n    <!-- SUMMARY -->\n    <div class=\"stats\" id=\"crime-stats\" style=\"display:none\">\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"crime-stat-year\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Jumlah Jenayah<\/div>\n        <div class=\"sv\" id=\"crime-stat-total\">\u2014<\/div>\n        <div class=\"su\">kes jenayah<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Daerah Tertinggi<\/div>\n        <div class=\"sv\" id=\"crime-stat-high\">\u2014<\/div>\n        <div class=\"su\" id=\"crime-stat-high-name\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Perubahan<\/div>\n        <div class=\"sv\" id=\"crime-stat-change\">\u2014<\/div>\n        <div class=\"su\" id=\"crime-stat-change-year\">\n          berbanding tahun sebelumnya\n        <\/div>\n      <\/div>\n\n    <\/div>\n\n    <!-- LOADING -->\n    <div class=\"sbox\" id=\"crime-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod jenayah Melaka.<\/div>\n    <\/div>\n\n    <!-- DASHBOARD -->\n    <div id=\"crime-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"district\">Daerah<\/button>\n        <button class=\"tbtn\" data-tab=\"category\">Kategori<\/button>\n        <button class=\"tbtn\" data-tab=\"type\">Jenis Jenayah<\/button>\n        <button class=\"tbtn\" data-tab=\"topbottom\">Tertinggi &#038; Terendah<\/button>\n        <button class=\"tbtn\" data-tab=\"change\">Perubahan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <!-- TREND -->\n      <div id=\"crime-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Jenayah Mengikut Masa<\/div>\n            <div class=\"cs\" id=\"crime-trend-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"crime-chart-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Mengikut Kategori Jenayah<\/div>\n            <div class=\"cs\">\n              Perbandingan trend jenayah kekerasan dan jenayah harta benda.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"crime-chart-category-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- DISTRICT -->\n      <div id=\"crime-tab-district\" style=\"display:none\">\n\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Jenayah Mengikut Daerah Polis<\/div>\n          <div class=\"cs\" id=\"crime-district-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"crime-chart-district\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">Komposisi Kategori Jenayah Mengikut Daerah<\/div>\n          <div class=\"cs\">\n            Perbandingan jenayah kekerasan dan jenayah harta benda bagi setiap daerah polis.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"crime-chart-district-category\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n      <!-- CATEGORY -->\n      <div id=\"crime-tab-category\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Pecahan Mengikut Kategori Jenayah<\/div>\n            <div class=\"cs\" id=\"crime-category-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"crime-chart-category\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Perbandingan Kategori Mengikut Masa<\/div>\n            <div class=\"cs\">\n              Perubahan bilangan kes bagi setiap kategori jenayah dari tahun ke tahun.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"crime-chart-category-history\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- TYPE -->\n      <div id=\"crime-tab-type\" style=\"display:none\">\n\n        <div class=\"cc\">\n          <div class=\"ct\">Pecahan Mengikut Jenis Jenayah<\/div>\n          <div class=\"cs\" id=\"crime-type-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"crime-chart-type\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">Trend Jenis Jenayah Utama<\/div>\n          <div class=\"cs\">\n            Trend sehingga lima jenis jenayah dengan bilangan kes tertinggi bagi tahun dipilih.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"crime-chart-type-trend\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n      <!-- TOP BOTTOM -->\n      <div id=\"crime-tab-topbottom\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Daerah dengan Jenayah Tertinggi<\/div>\n            <div class=\"cs\" id=\"crime-top-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"crime-chart-top\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Daerah dengan Jenayah Terendah<\/div>\n            <div class=\"cs\" id=\"crime-bottom-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"crime-chart-bottom\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- CHANGE -->\n      <div id=\"crime-tab-change\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Perubahan Jenayah Mengikut Daerah<\/div>\n            <div class=\"cs\" id=\"crime-change-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"crime-chart-change\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Perubahan Keseluruhan<\/div>\n            <div class=\"cs\" id=\"crime-change-trend-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"crime-chart-change-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- STATE -->\n      <div id=\"crime-tab-state\" style=\"display:none\">\n\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Jenayah Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"crime-state-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"crime-chart-state\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">Komposisi Kategori Jenayah Mengikut Negeri<\/div>\n          <div class=\"cs\">\n            Melaka ditonjolkan bagi memudahkan perbandingan dengan negeri-negeri lain.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"crime-chart-state-category\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue?id=crime_district&limit=20000';\n\n    var FIXED_STATE = 'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n    Chart.defaults.color = '#64748b';\n    Chart.defaults.borderColor = '#dbeafe';\n    Chart.defaults.font.family = 'Arial, sans-serif';\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(row){\n      return parseInt(\n        String(row.date || '').slice(0,4),\n        10\n      );\n    }\n\n    function num(v){\n      return Number(v) || 0;\n    }\n\n    function fmt(v){\n      return Number(v).toLocaleString(\n        'ms-MY',\n        {maximumFractionDigits:0}\n      );\n    }\n\n    function signed(v){\n      return (v > 0 ? '+' : '') + fmt(v);\n    }\n\n    function pct(v){\n      return (\n        (v > 0 ? '+' : '') +\n        Number(v).toLocaleString(\n          'ms-MY',\n          {maximumFractionDigits:1}\n        ) +\n        '%'\n      );\n    }\n\n    function destroyChart(name){\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    function isAllDistrict(value){\n      return String(value || '').trim().toLowerCase() === 'all';\n    }\n\n    function labelCategory(v){\n      if(v === 'all') return 'Semua Kategori';\n      if(v === 'assault') return 'Jenayah Kekerasan';\n      if(v === 'property') return 'Jenayah Harta Benda';\n      return formatFallback(v);\n    }\n\n    function labelType(v){\n\n      var labels = {\n        all:'Semua Jenis',\n\n        causing_injury:'Mencederakan',\n        murder:'Bunuh',\n        rape:'Rogol',\n\n        robbery_gang_armed:'Rompakan Berkumpulan Bersenjata',\n        robbery_gang_unarmed:'Rompakan Berkumpulan Tanpa Senjata',\n        robbery_solo_armed:'Rompakan Individu Bersenjata',\n        robbery_solo_unarmed:'Rompakan Individu Tanpa Senjata',\n\n        robbery:'Rompakan',\n        assault:'Serangan',\n\n        house_breaking:'Pecah Rumah',\n        break_in:'Pecah Masuk',\n        burglary:'Pecah Rumah',\n\n        theft:'Kecurian',\n        other_theft:'Kecurian Lain',\n\n        vehicle_theft:'Kecurian Kenderaan',\n        theft_vehicle:'Kecurian Kenderaan',\n\n        car_theft:'Kecurian Kereta',\n        theft_car:'Kecurian Kereta',\n\n        motorcycle_theft:'Kecurian Motosikal',\n        theft_motorcycle:'Kecurian Motosikal',\n\n        lorry_theft:'Kecurian Lori',\n        theft_lorry:'Kecurian Lori',\n\n        van_theft:'Kecurian Van',\n        theft_van:'Kecurian Van'\n      };\n\n      if(labels[v]){\n        return labels[v];\n      }\n\n      return translateSnakeCase(v);\n    }\n\n    function translateSnakeCase(value){\n\n      if(!value){\n        return '';\n      }\n\n      var words = {\n        causing:'Menyebabkan',\n        injury:'Kecederaan',\n        murder:'Bunuh',\n        rape:'Rogol',\n        robbery:'Rompakan',\n        gang:'Berkumpulan',\n        armed:'Bersenjata',\n        unarmed:'Tanpa Senjata',\n        solo:'Individu',\n        house:'Rumah',\n        breaking:'Pecah',\n        break:'Pecah',\n        in:'Masuk',\n        burglary:'Pecah Rumah',\n        theft:'Kecurian',\n        vehicle:'Kenderaan',\n        motor:'Motor',\n        motorcycle:'Motosikal',\n        car:'Kereta',\n        lorry:'Lori',\n        van:'Van',\n        other:'Lain-lain',\n        property:'Harta Benda',\n        assault:'Kekerasan'\n      };\n\n      return String(value)\n        .split('_')\n        .map(function(word){\n\n          if(words[word]){\n            return words[word];\n          }\n\n          return word.charAt(0).toUpperCase() +\n            word.slice(1);\n\n        })\n        .join(' ');\n    }\n\n    function formatFallback(value){\n\n      if(!value){\n        return '';\n      }\n\n      return String(value)\n        .replace(\/_\/g,' ')\n        .replace(\/\\b\\w\/g,function(c){\n          return c.toUpperCase();\n        });\n    }\n\n    function getSelected(){\n\n      return {\n        year:parseInt(el('crime-year').value,10),\n        district:el('crime-district').value,\n        category:el('crime-category').value,\n        type:el('crime-type').value\n      };\n\n    }\n\n    \/*\n      --------------------------------------------------\n      CRITICAL AGGREGATION LOGIC\n      --------------------------------------------------\n\n      Dataset contains \"type = all\".\n\n      We should NOT sum:\n      type = all\n      +\n      every detailed crime type\n\n      because this would double count.\n\n      Rules:\n\n      category = all, type = all\n      -> use category totals where type === all\n\n      category = assault, type = all\n      -> assault + type all\n\n      category = property, type = all\n      -> property + type all\n\n      specific type\n      -> use that exact type only\n    *\/\n\n    function crimeFilter(row, category, type){\n\n      if(type !== 'all'){\n        return (\n          row.type === type &&\n          (\n            category === 'all' ||\n            row.category === category\n          )\n        );\n      }\n\n      if(category === 'all'){\n        return (\n          row.type === 'all' &&\n          (\n            row.category === 'assault' ||\n            row.category === 'property'\n          )\n        );\n      }\n\n      return (\n        row.category === category &&\n        row.type === 'all'\n      );\n    }\n\n    function sumRows(rows){\n      return rows.reduce(function(total,row){\n        return total + num(row.crimes);\n      },0);\n    }\n\n    function getStateDistrictRows(\n      source,\n      state,\n      year,\n      district,\n      category,\n      type\n    ){\n\n      return source.filter(function(row){\n\n        var stateMatch =\n          String(row.state || '').trim() === state;\n\n        var yearMatch =\n          yr(row) === year;\n\n        var districtMatch;\n\n        if(district === 'all'){\n          districtMatch = isAllDistrict(row.district);\n        }else{\n          districtMatch = row.district === district;\n        }\n\n        return (\n          stateMatch &&\n          yearMatch &&\n          districtMatch &&\n          crimeFilter(row,category,type)\n        );\n\n      });\n\n    }\n\n    function valueForSelection(\n      source,\n      state,\n      year,\n      district,\n      category,\n      type\n    ){\n\n      return sumRows(\n        getStateDistrictRows(\n          source,\n          state,\n          year,\n          district,\n          category,\n          type\n        )\n      );\n\n    }\n\n    function valueForMelakaDistrict(\n      district,\n      year,\n      category,\n      type\n    ){\n\n      return sumRows(\n        melakaRows.filter(function(row){\n\n          return (\n            yr(row) === year &&\n            row.district === district &&\n            crimeFilter(row,category,type)\n          );\n\n        })\n      );\n\n    }\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows.map(yr)\n        )\n      )\n      .filter(function(y){\n        return !isNaN(y);\n      })\n      .sort(function(a,b){\n        return a-b;\n      });\n\n    }\n\n    function getPrevYear(current){\n\n      var years = availableYears()\n        .filter(function(y){\n          return y < current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n    function getMelakaDistricts(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n              return !isAllDistrict(row.district);\n            })\n            .map(function(row){\n              return row.district;\n            })\n        )\n      )\n      .filter(Boolean)\n      .sort();\n\n    }\n\n    function populateFilters(){\n\n      var years = availableYears()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('crime-year').innerHTML =\n        years.map(function(y){\n          return (\n            '<option value=\"' +\n            y +\n            '\">' +\n            y +\n            '<\/option>'\n          );\n        }).join('');\n\n      if(years.length){\n        el('crime-year').value = years[0];\n      }\n\n      var districts = getMelakaDistricts();\n\n      el('crime-district').innerHTML =\n        '<option value=\"all\">Semua Daerah Polis<\/option>' +\n        districts.map(function(d){\n          return (\n            '<option value=\"' +\n            d +\n            '\">' +\n            d +\n            '<\/option>'\n          );\n        }).join('');\n\n      el('crime-category').innerHTML =\n        '<option value=\"all\">Semua Kategori<\/option>' +\n        '<option value=\"assault\">Jenayah Kekerasan<\/option>' +\n        '<option value=\"property\">Jenayah Harta Benda<\/option>';\n\n      populateTypeFilter();\n    }\n\n    function populateTypeFilter(){\n\n      var category =\n        el('crime-category').value || 'all';\n\n      var rows = melakaRows.filter(function(row){\n\n        return (\n          row.type &&\n          row.type !== 'all' &&\n          (\n            category === 'all' ||\n            row.category === category\n          )\n        );\n\n      });\n\n      var types = Array.from(\n        new Set(\n          rows.map(function(row){\n            return row.type;\n          })\n        )\n      ).sort(function(a,b){\n        return labelType(a).localeCompare(\n          labelType(b),\n          'ms'\n        );\n      });\n\n      var current =\n        el('crime-type').value;\n\n      el('crime-type').innerHTML =\n        '<option value=\"all\">Semua Jenis Jenayah<\/option>' +\n        types.map(function(type){\n\n          return (\n            '<option value=\"' +\n            type +\n            '\">' +\n            labelType(type) +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      if(\n        current &&\n        types.indexOf(current) !== -1\n      ){\n        el('crime-type').value = current;\n      }else{\n        el('crime-type').value = 'all';\n      }\n    }\n\n    function updateCards(){\n\n      var s = getSelected();\n\n      var total =\n        valueForSelection(\n          melakaRows,\n          FIXED_STATE,\n          s.year,\n          s.district,\n          s.category,\n          s.type\n        );\n\n      var districts = getMelakaDistricts();\n\n      var districtData =\n        districts.map(function(district){\n\n          return {\n            district:district,\n            total:valueForMelakaDistrict(\n              district,\n              s.year,\n              s.category,\n              s.type\n            )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      var high = districtData.length\n        ? districtData[0]\n        : null;\n\n      var prev = getPrevYear(s.year);\n\n      var prevTotal =\n        prev !== null\n          ? valueForSelection(\n              melakaRows,\n              FIXED_STATE,\n              prev,\n              s.district,\n              s.category,\n              s.type\n            )\n          : null;\n\n      var change =\n        prev !== null\n          ? total-prevTotal\n          : null;\n\n      el('crime-stat-year').textContent =\n        s.year || '\u2014';\n\n      el('crime-stat-total').textContent =\n        fmt(total);\n\n      el('crime-stat-high').textContent =\n        high\n          ? fmt(high.total)\n          : '\u2014';\n\n      el('crime-stat-high-name').textContent =\n        high\n          ? high.district\n          : '\u2014';\n\n      el('crime-stat-change').textContent =\n        change !== null\n          ? signed(change)\n          : '\u2014';\n\n      el('crime-stat-change-year').textContent =\n        prev !== null\n          ? 'berbanding ' + prev\n          : 'tiada tahun sebelumnya';\n\n      el('crime-stats').style.display = '';\n    }\n\n    function tooltipY(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(c.parsed.y) +\n              ' kes'\n            );\n\n          }\n        }\n      };\n\n    }\n\n    function currentSubtitle(){\n\n      var s = getSelected();\n\n      return (\n        s.year +\n        ' \u00b7 ' +\n        (\n          s.district === 'all'\n            ? 'Semua Daerah Polis'\n            : s.district\n        ) +\n        ' \u00b7 ' +\n        labelCategory(s.category) +\n        ' \u00b7 ' +\n        (\n          s.type === 'all'\n            ? 'Semua Jenis Jenayah'\n            : labelType(s.type)\n        )\n      );\n\n    }\n\n    \/*\n      ==============================================\n      TREND\n      ==============================================\n    *\/\n\n    function buildTrendCharts(){\n\n      var s = getSelected();\n\n      var years = availableYears();\n\n      var trend = years.map(function(year){\n\n        return {\n          year:year,\n\n          total:\n            valueForSelection(\n              melakaRows,\n              FIXED_STATE,\n              year,\n              s.district,\n              s.category,\n              s.type\n            )\n        };\n\n      });\n\n      el('crime-trend-sub').textContent =\n        (\n          s.district === 'all'\n            ? 'Semua Daerah Polis'\n            : s.district\n        ) +\n        ' \u00b7 ' +\n        labelCategory(s.category) +\n        ' \u00b7 ' +\n        (\n          s.type === 'all'\n            ? 'Semua Jenis Jenayah'\n            : labelType(s.type)\n        );\n\n      destroyChart('trend');\n\n      charts.trend =\n        new Chart(\n          el('crime-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                trend.map(function(d){\n                  return d.year;\n                }),\n\n              datasets:[{\n                label:'Jumlah Jenayah',\n\n                data:\n                  trend.map(function(d){\n                    return d.total;\n                  }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var categoryDatasets = [\n        {\n          key:'assault',\n          label:'Jenayah Kekerasan',\n          border:'#dc2626',\n          bg:'rgba(220,38,38,.10)'\n        },\n        {\n          key:'property',\n          label:'Jenayah Harta Benda',\n          border:'#f97316',\n          bg:'rgba(249,115,22,.10)'\n        }\n      ];\n\n      destroyChart('categoryTrend');\n\n      charts.categoryTrend =\n        new Chart(\n          el('crime-chart-category-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n\n              datasets:\n                categoryDatasets.map(function(cat){\n\n                  return {\n                    label:cat.label,\n\n                    data:\n                      years.map(function(year){\n\n                        return valueForSelection(\n                          melakaRows,\n                          FIXED_STATE,\n                          year,\n                          s.district,\n                          cat.key,\n                          'all'\n                        );\n\n                      }),\n\n                    borderColor:cat.border,\n                    backgroundColor:cat.bg,\n                    tension:.35,\n                    borderWidth:2.5,\n                    pointRadius:3\n                  };\n\n                })\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/*\n      ==============================================\n      DISTRICT\n      ==============================================\n    *\/\n\n    function buildDistrictCharts(){\n\n      var s = getSelected();\n\n      var districts =\n        getMelakaDistricts();\n\n      var data =\n        districts.map(function(district){\n\n          return {\n            district:district,\n\n            total:\n              valueForMelakaDistrict(\n                district,\n                s.year,\n                s.category,\n                s.type\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      el('crime-district-sub').textContent =\n        currentSubtitle();\n\n      destroyChart('district');\n\n      charts.district =\n        new Chart(\n          el('crime-chart-district')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.district;\n                }),\n\n              datasets:[{\n                label:'Jumlah Jenayah',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var districtLabels =\n        data.map(function(d){\n          return d.district;\n        });\n\n      destroyChart('districtCategory');\n\n      charts.districtCategory =\n        new Chart(\n          el('crime-chart-district-category')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:districtLabels,\n\n              datasets:[\n                {\n                  label:'Jenayah Kekerasan',\n\n                  data:\n                    districtLabels.map(function(d){\n\n                      return valueForMelakaDistrict(\n                        d,\n                        s.year,\n                        'assault',\n                        'all'\n                      );\n\n                    }),\n\n                  backgroundColor:'rgba(220,38,38,.78)',\n                  borderRadius:6\n                },\n\n                {\n                  label:'Jenayah Harta Benda',\n\n                  data:\n                    districtLabels.map(function(d){\n\n                      return valueForMelakaDistrict(\n                        d,\n                        s.year,\n                        'property',\n                        'all'\n                      );\n\n                    }),\n\n                  backgroundColor:'rgba(249,115,22,.78)',\n                  borderRadius:6\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  stacked:true,\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/*\n      ==============================================\n      CATEGORY\n      ==============================================\n    *\/\n\n    function buildCategoryCharts(){\n\n      var s = getSelected();\n\n      var categories = [\n        {\n          key:'assault',\n          label:'Jenayah Kekerasan'\n        },\n        {\n          key:'property',\n          label:'Jenayah Harta Benda'\n        }\n      ];\n\n      var data =\n        categories.map(function(cat){\n\n          return {\n            label:cat.label,\n\n            total:\n              valueForSelection(\n                melakaRows,\n                FIXED_STATE,\n                s.year,\n                s.district,\n                cat.key,\n                'all'\n              )\n          };\n\n        });\n\n      el('crime-category-sub').textContent =\n        s.year +\n        ' \u00b7 ' +\n        (\n          s.district === 'all'\n            ? 'Semua Daerah Polis'\n            : s.district\n        );\n\n      destroyChart('category');\n\n      charts.category =\n        new Chart(\n          el('crime-chart-category')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.label;\n                }),\n\n              datasets:[{\n                label:'Jumlah Jenayah',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:[\n                  '#dc2626',\n                  '#f97316'\n                ],\n\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data.reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n                      var share =\n                        total\n                          ? (c.parsed\/total)*100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed) +\n                        ' kes (' +\n                        share.toLocaleString(\n                          'ms-MY',\n                          {maximumFractionDigits:1}\n                        ) +\n                        '%)'\n                      );\n\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var years = availableYears();\n\n      destroyChart('categoryHistory');\n\n      charts.categoryHistory =\n        new Chart(\n          el('crime-chart-category-history')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n\n              datasets:[\n                {\n                  label:'Jenayah Kekerasan',\n\n                  data:\n                    years.map(function(year){\n\n                      return valueForSelection(\n                        melakaRows,\n                        FIXED_STATE,\n                        year,\n                        s.district,\n                        'assault',\n                        'all'\n                      );\n\n                    }),\n\n                  borderColor:'#dc2626',\n                  backgroundColor:'rgba(220,38,38,.10)',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                },\n\n                {\n                  label:'Jenayah Harta Benda',\n\n                  data:\n                    years.map(function(year){\n\n                      return valueForSelection(\n                        melakaRows,\n                        FIXED_STATE,\n                        year,\n                        s.district,\n                        'property',\n                        'all'\n                      );\n\n                    }),\n\n                  borderColor:'#f97316',\n                  backgroundColor:'rgba(249,115,22,.10)',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/*\n      ==============================================\n      TYPE\n      ==============================================\n    *\/\n\n    function getSpecificTypes(category){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n\n              return (\n                row.type &&\n                row.type !== 'all' &&\n                (\n                  category === 'all' ||\n                  row.category === category\n                )\n              );\n\n            })\n            .map(function(row){\n              return row.type;\n            })\n        )\n      );\n\n    }\n\n    function typeValue(\n      year,\n      district,\n      type\n    ){\n\n      return valueForSelection(\n        melakaRows,\n        FIXED_STATE,\n        year,\n        district,\n        'all',\n        type\n      );\n\n    }\n\n    function buildTypeCharts(){\n\n      var s = getSelected();\n\n      var types =\n        getSpecificTypes(s.category);\n\n      var data =\n        types.map(function(type){\n\n          return {\n            type:type,\n            label:labelType(type),\n\n            total:\n              typeValue(\n                s.year,\n                s.district,\n                type\n              )\n          };\n\n        })\n        .filter(function(d){\n          return d.total > 0;\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      el('crime-type-sub').textContent =\n        s.year +\n        ' \u00b7 ' +\n        (\n          s.district === 'all'\n            ? 'Semua Daerah Polis'\n            : s.district\n        ) +\n        ' \u00b7 ' +\n        labelCategory(s.category);\n\n      destroyChart('type');\n\n      charts.type =\n        new Chart(\n          el('crime-chart-type')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.label;\n                }),\n\n              datasets:[{\n                label:'Jumlah Jenayah',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:'rgba(124,58,237,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return (\n                        ' Jumlah Jenayah: ' +\n                        fmt(c.parsed.x) +\n                        ' kes'\n                      );\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var topTypes =\n        data.slice(0,5);\n\n      var years =\n        availableYears();\n\n      destroyChart('typeTrend');\n\n      charts.typeTrend =\n        new Chart(\n          el('crime-chart-type-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n\n              datasets:\n                topTypes.map(function(item,index){\n\n                  var colors = [\n                    '#2563eb',\n                    '#dc2626',\n                    '#f97316',\n                    '#16a34a',\n                    '#7c3aed'\n                  ];\n\n                  return {\n                    label:item.label,\n\n                    data:\n                      years.map(function(year){\n\n                        return typeValue(\n                          year,\n                          s.district,\n                          item.type\n                        );\n\n                      }),\n\n                    borderColor:\n                      colors[index % colors.length],\n\n                    tension:.35,\n                    borderWidth:2.5,\n                    pointRadius:3\n                  };\n\n                })\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/*\n      ==============================================\n      TOP \/ BOTTOM\n      ==============================================\n    *\/\n\n    function buildTopBottomCharts(){\n\n      var s = getSelected();\n\n      var districts =\n        getMelakaDistricts();\n\n      var data =\n        districts.map(function(district){\n\n          return {\n            district:district,\n\n            total:\n              valueForMelakaDistrict(\n                district,\n                s.year,\n                s.category,\n                s.type\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      var maximum =\n        Math.min(5,data.length);\n\n      var top =\n        data.slice(0,maximum);\n\n      var bottom =\n        data.slice(-maximum).reverse();\n\n      el('crime-top-sub').textContent =\n        s.year +\n        ' \u00b7 daerah polis dengan bilangan jenayah tertinggi';\n\n      el('crime-bottom-sub').textContent =\n        s.year +\n        ' \u00b7 daerah polis dengan bilangan jenayah terendah';\n\n      function makeChart(\n        id,\n        name,\n        dataset,\n        color\n      ){\n\n        destroyChart(name);\n\n        charts[name] =\n          new Chart(\n            el(id).getContext('2d'),\n            {\n              type:'bar',\n\n              data:{\n                labels:\n                  dataset.map(function(d){\n                    return d.district;\n                  }),\n\n                datasets:[{\n                  label:'Jumlah Jenayah',\n\n                  data:\n                    dataset.map(function(d){\n                      return d.total;\n                    }),\n\n                  backgroundColor:color,\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n                responsive:true,\n                maintainAspectRatio:false,\n\n                plugins:{\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipY()\n                },\n\n                scales:{\n                  x:{\n                    grid:{\n                      display:false\n                    },\n\n                    ticks:{\n                      maxRotation:45\n                    }\n                  },\n\n                  y:{\n                    beginAtZero:true,\n\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(v){\n                        return fmt(v);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n      }\n\n      makeChart(\n        'crime-chart-top',\n        'top',\n        top,\n        'rgba(220,38,38,.78)'\n      );\n\n      makeChart(\n        'crime-chart-bottom',\n        'bottom',\n        bottom,\n        'rgba(22,163,74,.78)'\n      );\n    }\n\n    \/*\n      ==============================================\n      CHANGE\n      ==============================================\n    *\/\n\n    function buildChangeCharts(){\n\n      var s = getSelected();\n\n      var current = s.year;\n      var prev = getPrevYear(current);\n\n      if(prev === null){\n\n        el('crime-change-sub').textContent =\n          current +\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('crime-change-trend-sub').textContent =\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n      var districts =\n        getMelakaDistricts();\n\n      var data =\n        districts.map(function(district){\n\n          var now =\n            valueForMelakaDistrict(\n              district,\n              current,\n              s.category,\n              s.type\n            );\n\n          var before =\n            valueForMelakaDistrict(\n              district,\n              prev,\n              s.category,\n              s.type\n            );\n\n          return {\n            district:district,\n            change:now-before\n          };\n\n        })\n        .sort(function(a,b){\n          return b.change-a.change;\n        });\n\n      el('crime-change-sub').textContent =\n        current +\n        ' berbanding ' +\n        prev +\n        ' \u00b7 nilai positif menunjukkan peningkatan jenayah';\n\n      destroyChart('change');\n\n      charts.change =\n        new Chart(\n          el('crime-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.district;\n                }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:\n                  data.map(function(d){\n                    return d.change;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.change >= 0\n                      ? 'rgba(220,38,38,.78)'\n                      : 'rgba(22,163,74,.78)';\n\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signed(c.parsed.y) +\n                        ' kes'\n                      );\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var years =\n        availableYears();\n\n      var trend = [];\n\n      years.forEach(function(year,index){\n\n        if(index === 0){\n          return;\n        }\n\n        var previousYear =\n          years[index-1];\n\n        var now =\n          valueForSelection(\n            melakaRows,\n            FIXED_STATE,\n            year,\n            s.district,\n            s.category,\n            s.type\n          );\n\n        var before =\n          valueForSelection(\n            melakaRows,\n            FIXED_STATE,\n            previousYear,\n            s.district,\n            s.category,\n            s.type\n          );\n\n        trend.push({\n          label:\n            year +\n            ' berbanding ' +\n            previousYear,\n\n          change:\n            now-before,\n\n          percentage:\n            before\n              ? ((now-before)\/before)*100\n              : null\n        });\n\n      });\n\n      el('crime-change-trend-sub').textContent =\n        'Perubahan jumlah jenayah bagi setiap tempoh data yang tersedia';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend =\n        new Chart(\n          el('crime-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                trend.map(function(d){\n                  return d.label;\n                }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:\n                  trend.map(function(d){\n                    return d.change;\n                  }),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      var item =\n                        trend[c.dataIndex];\n\n                      var text =\n                        ' Perubahan: ' +\n                        signed(c.parsed.y) +\n                        ' kes';\n\n                      if(\n                        item &&\n                        item.percentage !== null\n                      ){\n                        text +=\n                          ' (' +\n                          pct(item.percentage) +\n                          ')';\n                      }\n\n                      return text;\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/*\n      ==============================================\n      STATE\n      ==============================================\n    *\/\n\n    function stateValue(\n      state,\n      year,\n      category,\n      type\n    ){\n\n      return sumRows(\n        allRows.filter(function(row){\n\n          return (\n            row.state === state &&\n            yr(row) === year &&\n            isAllDistrict(row.district) &&\n            crimeFilter(row,category,type)\n          );\n\n        })\n      );\n\n    }\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .filter(function(row){\n\n              return (\n                row.state &&\n                row.state !== 'Malaysia'\n              );\n\n            })\n            .map(function(row){\n              return row.state;\n            })\n        )\n      )\n      .sort();\n\n    }\n\n    function buildStateCharts(){\n\n      var s = getSelected();\n\n      var states =\n        getStates();\n\n      var data =\n        states.map(function(state){\n\n          return {\n            state:state,\n\n            total:\n              stateValue(\n                state,\n                s.year,\n                s.category,\n                s.type\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      el('crime-state-sub').textContent =\n        s.year +\n        ' \u00b7 semua negeri \u00b7 ' +\n        labelCategory(s.category) +\n        ' \u00b7 ' +\n        (\n          s.type === 'all'\n            ? 'Semua Jenis Jenayah'\n            : labelType(s.type)\n        );\n\n      destroyChart('state');\n\n      charts.state =\n        new Chart(\n          el('crime-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:[{\n                label:'Jumlah Jenayah',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(37,99,235,.70)';\n\n                  }),\n\n                borderColor:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? '#f97316'\n                      : 'rgba(37,99,235,.70)';\n\n                  }),\n\n                borderWidth:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 3\n                      : 0;\n\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label === 'Melaka'\n                          ? '#f97316'\n                          : '#64748b'\n                      );\n\n                    },\n\n                    font:function(context){\n\n                      return {\n                        weight:\n                          context.tick.label === 'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label === 'Melaka'\n                            ? 13\n                            : 12\n                      };\n\n                    }\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var rankedStates =\n        data.map(function(d){\n          return d.state;\n        });\n\n      destroyChart('stateCategory');\n\n      charts.stateCategory =\n        new Chart(\n          el('crime-chart-state-category')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:rankedStates,\n\n              datasets:[\n                {\n                  label:'Jenayah Kekerasan',\n\n                  data:\n                    rankedStates.map(function(state){\n\n                      return stateValue(\n                        state,\n                        s.year,\n                        'assault',\n                        'all'\n                      );\n\n                    }),\n\n                  backgroundColor:'rgba(220,38,38,.75)',\n                  borderRadius:5\n                },\n\n                {\n                  label:'Jenayah Harta Benda',\n\n                  data:\n                    rankedStates.map(function(state){\n\n                      return stateValue(\n                        state,\n                        s.year,\n                        'property',\n                        'all'\n                      );\n\n                    }),\n\n                  backgroundColor:'rgba(249,115,22,.75)',\n                  borderRadius:5\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label === 'Melaka'\n                          ? '#f97316'\n                          : '#64748b'\n                      );\n\n                    },\n\n                    font:function(context){\n\n                      return {\n                        weight:\n                          context.tick.label === 'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n\n                    }\n                  }\n                },\n\n                y:{\n                  stacked:true,\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/*\n      ==============================================\n      TAB CONTROL\n      ==============================================\n    *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#crime-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute('data-tab')\n        : 'trend';\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      var tab = activeTab();\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'district'){\n        buildDistrictCharts();\n      }\n\n      if(tab === 'category'){\n        buildCategoryCharts();\n      }\n\n      if(tab === 'type'){\n        buildTypeCharts();\n      }\n\n      if(tab === 'topbottom'){\n        buildTopBottomCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n    document\n      .querySelectorAll(\n        '#crime-wrap .tbtn'\n      )\n      .forEach(function(btn){\n\n        btn.addEventListener(\n          'click',\n          function(){\n\n            document\n              .querySelectorAll(\n                '#crime-wrap .tbtn'\n              )\n              .forEach(function(b){\n\n                b.classList.remove(\n                  'active'\n                );\n\n              });\n\n            btn.classList.add(\n              'active'\n            );\n\n            [\n              'trend',\n              'district',\n              'category',\n              'type',\n              'topbottom',\n              'change',\n              'state'\n            ]\n            .forEach(function(tab){\n\n              el(\n                'crime-tab-' + tab\n              ).style.display = 'none';\n\n            });\n\n            el(\n              'crime-tab-' +\n              btn.getAttribute('data-tab')\n            ).style.display = '';\n\n            redraw();\n\n          }\n        );\n\n      });\n\n    \/*\n      ==============================================\n      FILTER EVENTS\n      ==============================================\n    *\/\n\n    el('crime-category')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateTypeFilter();\n          redraw();\n\n        }\n      );\n\n    [\n      'crime-year',\n      'crime-district',\n      'crime-type'\n    ]\n    .forEach(function(id){\n\n      el(id).addEventListener(\n        'change',\n        redraw\n      );\n\n    });\n\n    \/*\n      ==============================================\n      FETCH API\n      ==============================================\n    *\/\n\n    fetch(API_URL)\n\n      .then(function(response){\n\n        if(!response.ok){\n          throw new Error(\n            'HTTP ' + response.status\n          );\n        }\n\n        return response.json();\n      })\n\n      .then(function(data){\n\n        allRows =\n          Array.isArray(data)\n            ? data\n            : [];\n\n        melakaRows =\n          allRows.filter(function(row){\n\n            return (\n              String(\n                row.state || ''\n              )\n              .trim()\n              .toLowerCase()\n              ===\n              'melaka'\n            );\n\n          });\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            'Tiada rekod jenayah Melaka ditemui.'\n          );\n\n        }\n\n        populateFilters();\n\n        el('crime-loading')\n          .style.display = 'none';\n\n        el('crime-area')\n          .style.display = '';\n\n        redraw();\n\n      })\n\n      .catch(function(error){\n\n        el('crime-loading')\n          .innerHTML =\n\n          '<div style=\"' +\n          'color:#dc2626;' +\n          'font-size:16px;' +\n          'font-weight:800;' +\n          'margin-bottom:8px' +\n          '\">' +\n          'Gagal memuatkan data' +\n          '<\/div>' +\n\n          '<p style=\"' +\n          'font-size:13px;' +\n          'color:#64748b' +\n          '\">' +\n          error.message +\n          '<\/p>';\n\n      });\n\n  }\n\n  if(document.readyState === 'loading'){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#prison-wrap, #prison-wrap * { box-sizing:border-box; }\n\n#prison-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#prison-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#prison-wrap .hdr,\n#prison-wrap .cc,\n#prison-wrap .sc,\n#prison-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#prison-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#prison-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#prison-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#prison-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#prison-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(3,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#prison-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#prison-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#prison-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#prison-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#prison-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#prison-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#prison-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#prison-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#prison-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n}\n\n#prison-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#prison-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#prison-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#prison-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#prison-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#prison-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n}\n\n#prison-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#prison-wrap .chart-box{\n  position:relative;\n  height:370px;\n}\n\n#prison-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#prison-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#prison-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:prison-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes prison-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n\n  #prison-wrap .filter-bar,\n  #prison-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #prison-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #prison-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #prison-wrap .filter-bar,\n  #prison-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #prison-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #prison-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #prison-wrap .chart-box,\n  #prison-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n<div id=\"prison-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Banduan Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Banduan Mengikut Pusat Penjara dan Jantina\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi bilangan banduan di Melaka mengikut tahun,\n        pusat penjara dan jantina. Data menunjukkan bilangan banduan\n        pada 31 Disember bagi setiap tahun yang tersedia.\n      <\/p>\n\n    <\/div>\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"prison-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Pusat Penjara<\/label>\n        <select id=\"prison-centre\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Jantina<\/label>\n        <select id=\"prison-sex\"><\/select>\n      <\/div>\n\n    <\/div>\n\n    <!-- SUMMARY CARDS -->\n    <div\n      class=\"stats\"\n      id=\"prison-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n        <div class=\"sl\">\n          Tahun Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"prison-stat-year\"\n        >\n          \u2014\n        <\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">\n          Jumlah Banduan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"prison-stat-total\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          orang banduan\n        <\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">\n          Pusat Penjara Tertinggi\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"prison-stat-high\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"prison-stat-high-name\"\n        >\n          \u2014\n        <\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">\n          Perubahan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"prison-stat-change\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"prison-stat-change-year\"\n        >\n          berbanding tahun sebelumnya\n        <\/div>\n      <\/div>\n\n    <\/div>\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"prison-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod banduan Melaka.\n      <\/div>\n\n    <\/div>\n\n    <!-- DASHBOARD -->\n    <div\n      id=\"prison-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TAB -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"centre\"\n        >\n          Pusat Penjara\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"sex\"\n        >\n          Jantina\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"composition\"\n        >\n          Komposisi\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"topbottom\"\n        >\n          Tertinggi &#038; Terendah\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n      <\/div>\n\n      <!-- =========================\n           TREND\n      ========================== -->\n      <div id=\"prison-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Bilangan Banduan Mengikut Masa\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"prison-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"prison-chart-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Banduan Mengikut Jantina\n            <\/div>\n\n            <div class=\"cs\">\n              Perbandingan jumlah banduan lelaki dan perempuan dari tahun ke tahun.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"prison-chart-sex-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =========================\n           PRISON CENTRE\n      ========================== -->\n      <div\n        id=\"prison-tab-centre\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Banduan Mengikut Pusat Penjara\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"prison-centre-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"prison-chart-centre\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Banduan Lelaki dan Perempuan Mengikut Pusat Penjara\n          <\/div>\n\n          <div class=\"cs\">\n            Komposisi bilangan banduan lelaki dan perempuan bagi setiap pusat penjara.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"prison-chart-centre-sex\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =========================\n           SEX\n      ========================== -->\n      <div\n        id=\"prison-tab-sex\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pecahan Banduan Mengikut Jantina\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"prison-sex-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"prison-chart-sex\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Peratusan Banduan Mengikut Jantina\n            <\/div>\n\n            <div class=\"cs\">\n              Perbandingan peratus banduan lelaki dan perempuan.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"prison-chart-sex-percentage\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =========================\n           COMPOSITION\n      ========================== -->\n      <div\n        id=\"prison-tab-composition\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Komposisi Jantina Mengikut Pusat Penjara\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"prison-composition-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"prison-chart-composition\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Peratus Komposisi Jantina Mengikut Pusat Penjara\n          <\/div>\n\n          <div class=\"cs\">\n            Pecahan peratus banduan lelaki dan perempuan bagi setiap pusat penjara.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"prison-chart-composition-percent\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =========================\n           TOP & BOTTOM\n      ========================== -->\n      <div\n        id=\"prison-tab-topbottom\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pusat Penjara dengan Banduan Tertinggi\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"prison-top-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"prison-chart-top\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pusat Penjara dengan Banduan Terendah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"prison-bottom-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"prison-chart-bottom\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =========================\n           CHANGE\n      ========================== -->\n      <div\n        id=\"prison-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Banduan Mengikut Pusat Penjara\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"prison-change-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"prison-chart-change\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Perubahan Keseluruhan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"prison-change-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"prison-chart-change-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =========================\n           STATE\n      ========================== -->\n      <div\n        id=\"prison-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Banduan Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"prison-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"prison-chart-state\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Komposisi Jantina Banduan Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan banduan lelaki dan perempuan mengikut negeri.\n            Melaka ditonjolkan untuk memudahkan perbandingan.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"prison-chart-state-sex\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=prisoners_prison&limit=700';\n\n    var FIXED_STATE = 'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n    Chart.defaults.color = '#64748b';\n    Chart.defaults.borderColor = '#dbeafe';\n    Chart.defaults.font.family = 'Arial, sans-serif';\n\n    \/* =========================\n       BASIC HELPERS\n    ========================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(row){\n      return parseInt(\n        String(row.date || '').slice(0,4),\n        10\n      );\n    }\n\n    function num(v){\n      return Number(v) || 0;\n    }\n\n    function fmt(v){\n      return Number(v).toLocaleString(\n        'ms-MY',\n        {\n          maximumFractionDigits:0\n        }\n      );\n    }\n\n    function signed(v){\n      return (\n        (v > 0 ? '+' : '') +\n        fmt(v)\n      );\n    }\n\n    function pct(v){\n      return Number(v).toLocaleString(\n        'ms-MY',\n        {\n          minimumFractionDigits:1,\n          maximumFractionDigits:1\n        }\n      ) + '%';\n    }\n\n    function signedPct(v){\n\n      return (\n        (v > 0 ? '+' : '') +\n        Number(v).toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:1,\n            maximumFractionDigits:1\n          }\n        ) +\n        '%'\n      );\n\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n\n    }\n\n    function labelSex(value){\n\n      if(value === 'both'){\n        return 'Kedua-dua Jantina';\n      }\n\n      if(value === 'male'){\n        return 'Lelaki';\n      }\n\n      if(value === 'female'){\n        return 'Perempuan';\n      }\n\n      return value;\n\n    }\n\n    \/* =========================\n       DATA HELPERS\n    ========================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows.map(yr)\n        )\n      )\n      .filter(function(year){\n        return !isNaN(year);\n      })\n      .sort(function(a,b){\n        return a-b;\n      });\n\n    }\n\n    function getPrevYear(current){\n\n      var years =\n        availableYears()\n        .filter(function(year){\n          return year < current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n    function getMelakaPrisons(){\n\n      return Array.from(\n        new Set(\n          melakaRows.map(function(row){\n            return row.prison;\n          })\n        )\n      )\n      .filter(Boolean)\n      .sort();\n\n    }\n\n    function getSelected(){\n\n      return {\n        year:parseInt(\n          el('prison-year').value,\n          10\n        ),\n\n        prison:\n          el('prison-centre').value,\n\n        sex:\n          el('prison-sex').value\n      };\n\n    }\n\n    \/*\n      IMPORTANT\n\n      sex = \"both\" is already the total.\n\n      Therefore:\n      - for total prisoners, use ONLY sex = both\n        when \"Kedua-dua Jantina\" is selected.\n      - never calculate:\n        both + male + female\n    *\/\n\n    function sumRows(rows){\n\n      return rows.reduce(\n        function(total,row){\n\n          return (\n            total +\n            num(row.prisoners)\n          );\n\n        },\n        0\n      );\n\n    }\n\n    function valueForSelection(\n      source,\n      state,\n      year,\n      prison,\n      sex\n    ){\n\n      return sumRows(\n        source.filter(function(row){\n\n          var stateMatch =\n            String(row.state || '').trim() === state;\n\n          var yearMatch =\n            yr(row) === year;\n\n          var prisonMatch =\n            (\n              prison === 'all' ||\n              row.prison === prison\n            );\n\n          var sexMatch =\n            row.sex === sex;\n\n          return (\n            stateMatch &#038;&#038;\n            yearMatch &#038;&#038;\n            prisonMatch &#038;&#038;\n            sexMatch\n          );\n\n        })\n      );\n\n    }\n\n    function valueForMelakaPrison(\n      prison,\n      year,\n      sex\n    ){\n\n      return sumRows(\n        melakaRows.filter(function(row){\n\n          return (\n            row.prison === prison &#038;&#038;\n            yr(row) === year &#038;&#038;\n            row.sex === sex\n          );\n\n        })\n      );\n\n    }\n\n    function stateValue(\n      state,\n      year,\n      sex\n    ){\n\n      return sumRows(\n        allRows.filter(function(row){\n\n          return (\n            row.state === state &#038;&#038;\n            yr(row) === year &#038;&#038;\n            row.sex === sex\n          );\n\n        })\n      );\n\n    }\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows.map(function(row){\n            return row.state;\n          })\n        )\n      )\n      .filter(Boolean)\n      .sort();\n\n    }\n\n    \/* =========================\n       FILTERS\n    ========================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('prison-year').innerHTML =\n        years.map(function(year){\n\n          return (\n            '<option value=\"' +\n            year +\n            '\">' +\n            year +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      if(years.length){\n        el('prison-year').value =\n          years[0];\n      }\n\n      var prisons =\n        getMelakaPrisons();\n\n      el('prison-centre').innerHTML =\n        '<option value=\"all\">Semua Pusat Penjara<\/option>' +\n\n        prisons.map(function(prison){\n\n          return (\n            '<option value=\"' +\n            prison +\n            '\">' +\n            prison +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      el('prison-sex').innerHTML =\n        '<option value=\"both\">Kedua-dua Jantina<\/option>' +\n        '<option value=\"female\">Perempuan<\/option>' +\n        '<option value=\"male\">Lelaki<\/option>';\n\n    }\n\n    \/* =========================\n       SUMMARY CARDS\n    ========================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n      var total =\n        valueForSelection(\n          melakaRows,\n          FIXED_STATE,\n          s.year,\n          s.prison,\n          s.sex\n        );\n\n      var prisons =\n        getMelakaPrisons();\n\n      var prisonData =\n        prisons.map(function(prison){\n\n          return {\n            prison:prison,\n\n            total:\n              valueForMelakaPrison(\n                prison,\n                s.year,\n                s.sex\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      var high =\n        prisonData.length\n          ? prisonData[0]\n          : null;\n\n      var prev =\n        getPrevYear(s.year);\n\n      var prevTotal =\n        prev !== null\n          ? valueForSelection(\n              melakaRows,\n              FIXED_STATE,\n              prev,\n              s.prison,\n              s.sex\n            )\n          : null;\n\n      var change =\n        prev !== null\n          ? total-prevTotal\n          : null;\n\n      el('prison-stat-year').textContent =\n        s.year || '\u2014';\n\n      el('prison-stat-total').textContent =\n        fmt(total);\n\n      el('prison-stat-high').textContent =\n        high\n          ? fmt(high.total)\n          : '\u2014';\n\n      el('prison-stat-high-name').textContent =\n        high\n          ? high.prison\n          : '\u2014';\n\n      el('prison-stat-change').textContent =\n        change !== null\n          ? signed(change)\n          : '\u2014';\n\n      el('prison-stat-change-year').textContent =\n        prev !== null\n          ? 'berbanding ' + prev\n          : 'tiada tahun sebelumnya';\n\n      el('prison-stats').style.display =\n        '';\n\n    }\n\n    \/* =========================\n       TOOLTIP\n    ========================== *\/\n\n    function tooltipY(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(c.parsed.y) +\n              ' banduan'\n            );\n\n          }\n\n        }\n      };\n\n    }\n\n    \/* =========================\n       TREND\n    ========================== *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var years =\n        availableYears();\n\n      var data =\n        years.map(function(year){\n\n          return {\n            year:year,\n\n            total:\n              valueForSelection(\n                melakaRows,\n                FIXED_STATE,\n                year,\n                s.prison,\n                s.sex\n              )\n          };\n\n        });\n\n      el('prison-trend-sub').textContent =\n        (\n          s.prison === 'all'\n            ? 'Semua Pusat Penjara'\n            : s.prison\n        ) +\n        ' \u00b7 ' +\n        labelSex(s.sex);\n\n      destroyChart('trend');\n\n      charts.trend =\n        new Chart(\n          el('prison-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.year;\n                }),\n\n              datasets:[{\n                label:'Jumlah Banduan',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('sexTrend');\n\n      charts.sexTrend =\n        new Chart(\n          el('prison-chart-sex-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n\n              datasets:[\n                {\n                  label:'Lelaki',\n\n                  data:\n                    years.map(function(year){\n\n                      return valueForSelection(\n                        melakaRows,\n                        FIXED_STATE,\n                        year,\n                        s.prison,\n                        'male'\n                      );\n\n                    }),\n\n                  borderColor:'#2563eb',\n                  backgroundColor:'rgba(37,99,235,.10)',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                },\n\n                {\n                  label:'Perempuan',\n\n                  data:\n                    years.map(function(year){\n\n                      return valueForSelection(\n                        melakaRows,\n                        FIXED_STATE,\n                        year,\n                        s.prison,\n                        'female'\n                      );\n\n                    }),\n\n                  borderColor:'#f97316',\n                  backgroundColor:'rgba(249,115,22,.10)',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n    }\n\n    \/* =========================\n       CENTRE\n    ========================== *\/\n\n    function buildCentreCharts(){\n\n      var s =\n        getSelected();\n\n      var prisons =\n        getMelakaPrisons();\n\n      var data =\n        prisons.map(function(prison){\n\n          return {\n            prison:prison,\n\n            total:\n              valueForMelakaPrison(\n                prison,\n                s.year,\n                s.sex\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      el('prison-centre-sub').textContent =\n        s.year +\n        ' \u00b7 Semua Pusat Penjara \u00b7 ' +\n        labelSex(s.sex);\n\n      destroyChart('centre');\n\n      charts.centre =\n        new Chart(\n          el('prison-chart-centre')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.prison;\n                }),\n\n              datasets:[{\n                label:'Banduan',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var labels =\n        data.map(function(d){\n          return d.prison;\n        });\n\n      destroyChart('centreSex');\n\n      charts.centreSex =\n        new Chart(\n          el('prison-chart-centre-sex')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:labels,\n\n              datasets:[\n                {\n                  label:'Lelaki',\n\n                  data:\n                    labels.map(function(prison){\n\n                      return valueForMelakaPrison(\n                        prison,\n                        s.year,\n                        'male'\n                      );\n\n                    }),\n\n                  backgroundColor:'rgba(37,99,235,.78)',\n                  borderRadius:6\n                },\n\n                {\n                  label:'Perempuan',\n\n                  data:\n                    labels.map(function(prison){\n\n                      return valueForMelakaPrison(\n                        prison,\n                        s.year,\n                        'female'\n                      );\n\n                    }),\n\n                  backgroundColor:'rgba(249,115,22,.78)',\n                  borderRadius:6\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  stacked:true,\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n    }\n\n    \/* =========================\n       SEX\n    ========================== *\/\n\n    function buildSexCharts(){\n\n      var s =\n        getSelected();\n\n      var male =\n        valueForSelection(\n          melakaRows,\n          FIXED_STATE,\n          s.year,\n          s.prison,\n          'male'\n        );\n\n      var female =\n        valueForSelection(\n          melakaRows,\n          FIXED_STATE,\n          s.year,\n          s.prison,\n          'female'\n        );\n\n      var total =\n        male+female;\n\n      el('prison-sex-sub').textContent =\n        s.year +\n        ' \u00b7 ' +\n        (\n          s.prison === 'all'\n            ? 'Semua Pusat Penjara'\n            : s.prison\n        );\n\n      destroyChart('sex');\n\n      charts.sex =\n        new Chart(\n          el('prison-chart-sex')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:[\n                'Lelaki',\n                'Perempuan'\n              ],\n\n              datasets:[{\n                label:'Banduan',\n\n                data:[\n                  male,\n                  female\n                ],\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#f97316'\n                ],\n\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      var share =\n                        total\n                          ? (c.parsed\/total)*100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed) +\n                        ' (' +\n                        pct(share) +\n                        ')'\n                      );\n\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('sexPercentage');\n\n      charts.sexPercentage =\n        new Chart(\n          el('prison-chart-sex-percentage')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:[\n                'Lelaki',\n                'Perempuan'\n              ],\n\n              datasets:[{\n                label:'Peratus Banduan',\n\n                data:[\n                  total\n                    ? (male\/total)*100\n                    : 0,\n\n                  total\n                    ? (female\/total)*100\n                    : 0\n                ],\n\n                backgroundColor:[\n                  'rgba(37,99,235,.78)',\n                  'rgba(249,115,22,.78)'\n                ],\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return (\n                        ' Peratus: ' +\n                        pct(c.parsed.y)\n                      );\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n                  max:100,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return v+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n    }\n\n    \/* =========================\n       COMPOSITION\n    ========================== *\/\n\n    function buildCompositionCharts(){\n\n      var s =\n        getSelected();\n\n      var prisons =\n        getMelakaPrisons();\n\n      var male =\n        prisons.map(function(prison){\n\n          return valueForMelakaPrison(\n            prison,\n            s.year,\n            'male'\n          );\n\n        });\n\n      var female =\n        prisons.map(function(prison){\n\n          return valueForMelakaPrison(\n            prison,\n            s.year,\n            'female'\n          );\n\n        });\n\n      el('prison-composition-sub').textContent =\n        s.year +\n        ' \u00b7 komposisi banduan lelaki dan perempuan';\n\n      destroyChart('composition');\n\n      charts.composition =\n        new Chart(\n          el('prison-chart-composition')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:prisons,\n\n              datasets:[\n                {\n                  label:'Lelaki',\n                  data:male,\n                  backgroundColor:'rgba(37,99,235,.78)',\n                  borderRadius:5\n                },\n\n                {\n                  label:'Perempuan',\n                  data:female,\n                  backgroundColor:'rgba(249,115,22,.78)',\n                  borderRadius:5\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  stacked:true,\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var malePercent = [];\n      var femalePercent = [];\n\n      prisons.forEach(\n        function(prison,index){\n\n          var total =\n            male[index] +\n            female[index];\n\n          malePercent.push(\n            total\n              ? (male[index]\/total)*100\n              : 0\n          );\n\n          femalePercent.push(\n            total\n              ? (female[index]\/total)*100\n              : 0\n          );\n\n        }\n      );\n\n      destroyChart('compositionPercent');\n\n      charts.compositionPercent =\n        new Chart(\n          el('prison-chart-composition-percent')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:prisons,\n\n              datasets:[\n                {\n                  label:'Lelaki',\n                  data:malePercent,\n                  backgroundColor:'rgba(37,99,235,.78)'\n                },\n\n                {\n                  label:'Perempuan',\n                  data:femalePercent,\n                  backgroundColor:'rgba(249,115,22,.78)'\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return (\n                        ' ' +\n                        c.dataset.label +\n                        ': ' +\n                        pct(c.parsed.y)\n                      );\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  stacked:true,\n                  beginAtZero:true,\n                  max:100,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return v+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n    }\n\n    \/* =========================\n       TOP & BOTTOM\n    ========================== *\/\n\n    function buildTopBottomCharts(){\n\n      var s =\n        getSelected();\n\n      var prisons =\n        getMelakaPrisons();\n\n      var data =\n        prisons.map(function(prison){\n\n          return {\n            prison:prison,\n\n            total:\n              valueForMelakaPrison(\n                prison,\n                s.year,\n                s.sex\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      var limit =\n        Math.min(\n          5,\n          data.length\n        );\n\n      var top =\n        data.slice(0,limit);\n\n      var bottom =\n        data.slice(-limit)\n        .reverse();\n\n      el('prison-top-sub').textContent =\n        s.year +\n        ' \u00b7 pusat penjara dengan bilangan banduan tertinggi';\n\n      el('prison-bottom-sub').textContent =\n        s.year +\n        ' \u00b7 pusat penjara dengan bilangan banduan terendah';\n\n      function makeChart(\n        id,\n        name,\n        dataset,\n        color\n      ){\n\n        destroyChart(name);\n\n        charts[name] =\n          new Chart(\n            el(id)\n              .getContext('2d'),\n            {\n              type:'bar',\n\n              data:{\n                labels:\n                  dataset.map(function(d){\n                    return d.prison;\n                  }),\n\n                datasets:[{\n                  label:'Banduan',\n\n                  data:\n                    dataset.map(function(d){\n                      return d.total;\n                    }),\n\n                  backgroundColor:color,\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n                responsive:true,\n                maintainAspectRatio:false,\n\n                plugins:{\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipY()\n                },\n\n                scales:{\n                  x:{\n                    grid:{\n                      display:false\n                    },\n\n                    ticks:{\n                      maxRotation:45\n                    }\n                  },\n\n                  y:{\n                    beginAtZero:true,\n\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(v){\n                        return fmt(v);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n\n      }\n\n      makeChart(\n        'prison-chart-top',\n        'top',\n        top,\n        'rgba(220,38,38,.78)'\n      );\n\n      makeChart(\n        'prison-chart-bottom',\n        'bottom',\n        bottom,\n        'rgba(22,163,74,.78)'\n      );\n\n    }\n\n    \/* =========================\n       CHANGE\n    ========================== *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n      var current =\n        s.year;\n\n      var prev =\n        getPrevYear(current);\n\n      if(prev === null){\n\n        el('prison-change-sub')\n          .textContent =\n          current +\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('prison-change-trend-sub')\n          .textContent =\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n      var prisons =\n        getMelakaPrisons();\n\n      var data =\n        prisons.map(function(prison){\n\n          var now =\n            valueForMelakaPrison(\n              prison,\n              current,\n              s.sex\n            );\n\n          var before =\n            valueForMelakaPrison(\n              prison,\n              prev,\n              s.sex\n            );\n\n          return {\n            prison:prison,\n            change:now-before\n          };\n\n        })\n        .sort(function(a,b){\n          return b.change-a.change;\n        });\n\n      el('prison-change-sub').textContent =\n        current +\n        ' berbanding ' +\n        prev +\n        ' \u00b7 nilai positif menunjukkan bilangan banduan meningkat';\n\n      destroyChart('change');\n\n      charts.change =\n        new Chart(\n          el('prison-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.prison;\n                }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:\n                  data.map(function(d){\n                    return d.change;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.change >= 0\n                      ? 'rgba(220,38,38,.78)'\n                      : 'rgba(22,163,74,.78)';\n\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signed(c.parsed.y) +\n                        ' banduan'\n                      );\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var years =\n        availableYears();\n\n      var trend = [];\n\n      years.forEach(\n        function(year,index){\n\n          if(index === 0){\n            return;\n          }\n\n          var previousYear =\n            years[index-1];\n\n          var now =\n            valueForSelection(\n              melakaRows,\n              FIXED_STATE,\n              year,\n              s.prison,\n              s.sex\n            );\n\n          var before =\n            valueForSelection(\n              melakaRows,\n              FIXED_STATE,\n              previousYear,\n              s.prison,\n              s.sex\n            );\n\n          trend.push({\n\n            label:\n              year +\n              ' berbanding ' +\n              previousYear,\n\n            change:\n              now-before,\n\n            percentage:\n              before\n                ? ((now-before)\/before)*100\n                : null\n\n          });\n\n        }\n      );\n\n      el('prison-change-trend-sub')\n        .textContent =\n        'Perubahan bilangan banduan bagi setiap tempoh data yang tersedia';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend =\n        new Chart(\n          el('prison-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                trend.map(function(d){\n                  return d.label;\n                }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:\n                  trend.map(function(d){\n                    return d.change;\n                  }),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      var item =\n                        trend[c.dataIndex];\n\n                      var text =\n                        ' Perubahan: ' +\n                        signed(c.parsed.y) +\n                        ' banduan';\n\n                      if(\n                        item &&\n                        item.percentage !== null\n                      ){\n\n                        text +=\n                          ' (' +\n                          signedPct(\n                            item.percentage\n                          ) +\n                          ')';\n\n                      }\n\n                      return text;\n\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n    }\n\n    \/* =========================\n       STATE\n    ========================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n      var states =\n        getStates();\n\n      var data =\n        states.map(function(state){\n\n          return {\n            state:state,\n\n            total:\n              stateValue(\n                state,\n                s.year,\n                s.sex\n              )\n          };\n\n        })\n        .sort(function(a,b){\n          return b.total-a.total;\n        });\n\n      el('prison-state-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 semua negeri \u00b7 ' +\n        labelSex(s.sex);\n\n      destroyChart('state');\n\n      charts.state =\n        new Chart(\n          el('prison-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:[{\n                label:'Banduan',\n\n                data:\n                  data.map(function(d){\n                    return d.total;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(37,99,235,.70)';\n\n                  }),\n\n                borderColor:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? '#f97316'\n                      : 'rgba(37,99,235,.70)';\n\n                  }),\n\n                borderWidth:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 3\n                      : 0;\n\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label === 'Melaka'\n                          ? '#f97316'\n                          : '#64748b'\n                      );\n\n                    },\n\n                    font:function(context){\n\n                      return {\n                        weight:\n                          context.tick.label === 'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label === 'Melaka'\n                            ? 13\n                            : 12\n                      };\n\n                    }\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var rankedStates =\n        data.map(function(d){\n          return d.state;\n        });\n\n      destroyChart('stateSex');\n\n      charts.stateSex =\n        new Chart(\n          el('prison-chart-state-sex')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:rankedStates,\n\n              datasets:[\n                {\n                  label:'Lelaki',\n\n                  data:\n                    rankedStates.map(\n                      function(state){\n\n                        return stateValue(\n                          state,\n                          s.year,\n                          'male'\n                        );\n\n                      }\n                    ),\n\n                  backgroundColor:'rgba(37,99,235,.78)',\n                  borderRadius:5\n                },\n\n                {\n                  label:'Perempuan',\n\n                  data:\n                    rankedStates.map(\n                      function(state){\n\n                        return stateValue(\n                          state,\n                          s.year,\n                          'female'\n                        );\n\n                      }\n                    ),\n\n                  backgroundColor:'rgba(249,115,22,.78)',\n                  borderRadius:5\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipY()\n              },\n\n              scales:{\n                x:{\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label === 'Melaka'\n                          ? '#f97316'\n                          : '#64748b'\n                      );\n\n                    },\n\n                    font:function(context){\n\n                      return {\n                        weight:\n                          context.tick.label === 'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n\n                    }\n                  }\n                },\n\n                y:{\n                  stacked:true,\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n    }\n\n    \/* =========================\n       ACTIVE TAB\n    ========================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#prison-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      var tab =\n        activeTab();\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'centre'){\n        buildCentreCharts();\n      }\n\n      if(tab === 'sex'){\n        buildSexCharts();\n      }\n\n      if(tab === 'composition'){\n        buildCompositionCharts();\n      }\n\n      if(tab === 'topbottom'){\n        buildTopBottomCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n\n    }\n\n    \/* =========================\n       TAB EVENTS\n    ========================== *\/\n\n    document\n      .querySelectorAll(\n        '#prison-wrap .tbtn'\n      )\n      .forEach(function(btn){\n\n        btn.addEventListener(\n          'click',\n          function(){\n\n            document\n              .querySelectorAll(\n                '#prison-wrap .tbtn'\n              )\n              .forEach(function(b){\n\n                b.classList.remove(\n                  'active'\n                );\n\n              });\n\n            btn.classList.add(\n              'active'\n            );\n\n            [\n              'trend',\n              'centre',\n              'sex',\n              'composition',\n              'topbottom',\n              'change',\n              'state'\n            ]\n            .forEach(function(tab){\n\n              el(\n                'prison-tab-' +\n                tab\n              ).style.display =\n                'none';\n\n            });\n\n            el(\n              'prison-tab-' +\n              btn.getAttribute(\n                'data-tab'\n              )\n            ).style.display =\n              '';\n\n            redraw();\n\n          }\n        );\n\n      });\n\n    \/* =========================\n       FILTER EVENTS\n    ========================== *\/\n\n    [\n      'prison-year',\n      'prison-centre',\n      'prison-sex'\n    ]\n    .forEach(function(id){\n\n      el(id)\n        .addEventListener(\n          'change',\n          redraw\n        );\n\n    });\n\n    \/* =========================\n       FETCH API\n    ========================== *\/\n\n    fetch(API_URL)\n\n      .then(function(response){\n\n        if(!response.ok){\n\n          throw new Error(\n            'HTTP ' +\n            response.status\n          );\n\n        }\n\n        return response.json();\n\n      })\n\n      .then(function(data){\n\n        allRows =\n          Array.isArray(data)\n            ? data\n            : [];\n\n        melakaRows =\n          allRows.filter(\n            function(row){\n\n              return (\n                String(\n                  row.state || ''\n                )\n                .trim()\n                .toLowerCase()\n                ===\n                'melaka'\n              );\n\n            }\n          );\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            'Tiada rekod banduan Melaka ditemui.'\n          );\n\n        }\n\n        populateFilters();\n\n        el('prison-loading')\n          .style.display =\n          'none';\n\n        el('prison-area')\n          .style.display =\n          '';\n\n        redraw();\n\n      })\n\n      .catch(function(error){\n\n        el('prison-loading')\n          .innerHTML =\n\n          '<div style=\"' +\n          'color:#dc2626;' +\n          'font-size:16px;' +\n          'font-weight:800;' +\n          'margin-bottom:8px' +\n          '\">' +\n\n          'Gagal memuatkan data' +\n\n          '<\/div>' +\n\n          '<p style=\"' +\n          'font-size:13px;' +\n          'color:#64748b' +\n          '\">' +\n\n          error.message +\n\n          '<\/p>';\n\n      });\n\n  }\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<div style=\"text-align: center; margin: 40px 0 30px 0;\">\n  <h2 style=\"\n    font-size: 32px;\n    font-weight: 700;\n    color: #1f3c88;\n    margin-bottom: 10px;\n    letter-spacing: 0.5px;\n  \">\n    Dashboard Kategori Pasaran Buruh\n  <\/h2>\n\n  <div style=\"\n    width: 80px;\n    height: 4px;\n    background-color: #1f3c88;\n    margin: 0 auto;\n    border-radius: 4px;\n  \"><\/div>\n<\/div>\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#lfs-wrap, #lfs-wrap * { box-sizing:border-box; }\n\n#lfs-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#lfs-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#lfs-wrap .hdr,\n#lfs-wrap .cc,\n#lfs-wrap .sc,\n#lfs-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#lfs-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#lfs-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#lfs-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#lfs-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#lfs-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(3,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#lfs-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#lfs-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#lfs-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#lfs-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#lfs-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#lfs-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#lfs-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#lfs-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#lfs-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n}\n\n#lfs-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#lfs-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#lfs-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#lfs-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#lfs-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#lfs-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n}\n\n#lfs-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#lfs-wrap .chart-box{\n  position:relative;\n  height:370px;\n}\n\n#lfs-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#lfs-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#lfs-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:lfs-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes lfs-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n  #lfs-wrap .filter-bar,\n  #lfs-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #lfs-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n  #lfs-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #lfs-wrap .filter-bar,\n  #lfs-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #lfs-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #lfs-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #lfs-wrap .chart-box,\n  #lfs-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n<div id=\"lfs-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">Statistik Tenaga Buruh Melaka<\/div>\n\n      <h2>Papan Pemuka Statistik Tenaga Buruh Utama Tahunan Mengikut Daerah<\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi statistik utama tenaga buruh di Melaka mengikut tahun dan daerah,\n        termasuk saiz tenaga buruh, guna tenaga, pengangguran, penduduk di luar tenaga buruh,\n        kadar pengangguran, kadar penyertaan tenaga buruh dan nisbah guna tenaga kepada penduduk.\n      <\/p>\n    <\/div>\n\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"lfs-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Daerah<\/label>\n        <select id=\"lfs-district\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Ukuran Utama<\/label>\n        <select id=\"lfs-metric\">\n          <option value=\"lf\">Tenaga Buruh<\/option>\n          <option value=\"lf_employed\">Penduduk Bekerja<\/option>\n          <option value=\"lf_unemployed\">Penganggur<\/option>\n          <option value=\"lf_outside\">Di Luar Tenaga Buruh<\/option>\n          <option value=\"u_rate\">Kadar Pengangguran<\/option>\n          <option value=\"p_rate\">Kadar Penyertaan<\/option>\n          <option value=\"ep_ratio\">Nisbah Guna Tenaga-Penduduk<\/option>\n        <\/select>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"stats\" id=\"lfs-stats\" style=\"display:none\">\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"lfs-stat-year\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tenaga Buruh<\/div>\n        <div class=\"sv\" id=\"lfs-stat-lf\">\u2014<\/div>\n        <div class=\"su\">ribu orang<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Kadar Pengangguran<\/div>\n        <div class=\"sv\" id=\"lfs-stat-u\">\u2014<\/div>\n        <div class=\"su\">peratus<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Kadar Penyertaan<\/div>\n        <div class=\"sv\" id=\"lfs-stat-p\">\u2014<\/div>\n        <div class=\"su\">peratus<\/div>\n      <\/div>\n\n    <\/div>\n\n    <div class=\"sbox\" id=\"lfs-loading\">\n      <div class=\"spin\"><\/div>\n      <strong>Memuatkan data&#8230;<\/strong>\n      <div>Sedang mendapatkan rekod tenaga buruh Melaka.<\/div>\n    <\/div>\n\n    <div id=\"lfs-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n        <button class=\"tbtn active\" data-tab=\"trend\">Trend<\/button>\n        <button class=\"tbtn\" data-tab=\"district\">Daerah<\/button>\n        <button class=\"tbtn\" data-tab=\"status\">Status Tenaga Buruh<\/button>\n        <button class=\"tbtn\" data-tab=\"rates\">Kadar<\/button>\n        <button class=\"tbtn\" data-tab=\"ratio\">Nisbah<\/button>\n        <button class=\"tbtn\" data-tab=\"change\">Perubahan<\/button>\n        <button class=\"tbtn\" data-tab=\"state\">Negeri<\/button>\n      <\/div>\n\n      <div id=\"lfs-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Ukuran Utama Mengikut Masa<\/div>\n            <div class=\"cs\" id=\"lfs-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Tenaga Buruh dan Guna Tenaga<\/div>\n            <div class=\"cs\">Perbandingan tenaga buruh, penduduk bekerja dan penganggur dari tahun ke tahun.<\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-labour-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <div id=\"lfs-tab-district\" style=\"display:none\">\n\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Mengikut Daerah<\/div>\n          <div class=\"cs\" id=\"lfs-district-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"lfs-chart-district\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">Kadar Pengangguran Mengikut Daerah<\/div>\n          <div class=\"cs\" id=\"lfs-district-u-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"lfs-chart-district-u\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n      <div id=\"lfs-tab-status\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Komposisi Status Tenaga Buruh<\/div>\n            <div class=\"cs\" id=\"lfs-status-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-status\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Komposisi Penduduk Usia Bekerja<\/div>\n            <div class=\"cs\">Penduduk bekerja, penganggur dan individu di luar tenaga buruh.<\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-pop-composition\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <div id=\"lfs-tab-rates\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Kadar Pengangguran<\/div>\n            <div class=\"cs\" id=\"lfs-u-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-u-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Kadar Penyertaan Tenaga Buruh<\/div>\n            <div class=\"cs\" id=\"lfs-p-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-p-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <div id=\"lfs-tab-ratio\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Nisbah Guna Tenaga kepada Penduduk Mengikut Daerah<\/div>\n            <div class=\"cs\" id=\"lfs-ratio-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-ratio\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Perbandingan Kadar Utama<\/div>\n            <div class=\"cs\">Kadar penyertaan, kadar pengangguran dan nisbah guna tenaga-penduduk.<\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-rate-compare\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <div id=\"lfs-tab-change\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">Perubahan Ukuran Utama Mengikut Daerah<\/div>\n            <div class=\"cs\" id=\"lfs-change-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-change\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">Trend Perubahan Tahunan<\/div>\n            <div class=\"cs\" id=\"lfs-change-trend-sub\"><\/div>\n            <div class=\"chart-box-sm\">\n              <canvas id=\"lfs-chart-change-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <div id=\"lfs-tab-state\" style=\"display:none\">\n\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Mengikut Negeri<\/div>\n          <div class=\"cs\" id=\"lfs-state-sub\"><\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"lfs-chart-state\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">Perbandingan Kadar Pengangguran Mengikut Negeri<\/div>\n          <div class=\"cs\">Melaka ditonjolkan bagi memudahkan perbandingan dengan negeri-negeri lain.<\/div>\n          <div class=\"chart-box\">\n            <canvas id=\"lfs-chart-state-u\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=lfs_district&limit=1200';\n\n    var FIXED_STATE = 'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n    Chart.defaults.color = '#64748b';\n    Chart.defaults.borderColor = '#dbeafe';\n    Chart.defaults.font.family = 'Arial, sans-serif';\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(row){\n      return parseInt(\n        String(row.date || '').slice(0,4),\n        10\n      );\n    }\n\n    function num(v){\n      return v === null || v === undefined || v === ''\n        ? null\n        : Number(v);\n    }\n\n    function valid(v){\n      return v !== null && !isNaN(v);\n    }\n\n    function fmt(v, digits){\n\n      if(!valid(v)){\n        return '\u2014';\n      }\n\n      return Number(v).toLocaleString(\n        'ms-MY',\n        {\n          minimumFractionDigits:digits || 0,\n          maximumFractionDigits:digits || 0\n        }\n      );\n    }\n\n    function fmtK(v){\n      return valid(v)\n        ? fmt(v,1) + ' ribu'\n        : '\u2014';\n    }\n\n    function fmtPct(v){\n      return valid(v)\n        ? fmt(v,1) + '%'\n        : '\u2014';\n    }\n\n    function signed(v,digits){\n\n      if(!valid(v)){\n        return '\u2014';\n      }\n\n      return (\n        (v > 0 ? '+' : '') +\n        fmt(v,digits || 1)\n      );\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows.map(yr)\n        )\n      )\n      .filter(function(y){\n        return !isNaN(y);\n      })\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n    function getPrevYear(current){\n\n      var years =\n        availableYears()\n        .filter(function(y){\n          return y < current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n    function getDistricts(){\n\n      return Array.from(\n        new Set(\n          melakaRows.map(function(row){\n            return row.district;\n          })\n        )\n      )\n      .filter(Boolean)\n      .sort();\n    }\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows.map(function(row){\n            return row.state;\n          })\n        )\n      )\n      .filter(Boolean)\n      .sort();\n    }\n\n    function getSelected(){\n\n      return {\n        year:parseInt(el('lfs-year').value,10),\n        district:el('lfs-district').value,\n        metric:el('lfs-metric').value\n      };\n    }\n\n    function metricLabel(metric){\n\n      var labels = {\n        lf:'Tenaga Buruh',\n        lf_employed:'Penduduk Bekerja',\n        lf_unemployed:'Penganggur',\n        lf_outside:'Di Luar Tenaga Buruh',\n        u_rate:'Kadar Pengangguran',\n        p_rate:'Kadar Penyertaan',\n        ep_ratio:'Nisbah Guna Tenaga-Penduduk'\n      };\n\n      return labels[metric] || metric;\n    }\n\n    function isRateMetric(metric){\n\n      return (\n        metric === 'u_rate' ||\n        metric === 'p_rate' ||\n        metric === 'ep_ratio'\n      );\n    }\n\n    function metricUnit(metric){\n\n      return isRateMetric(metric)\n        ? '%'\n        : 'ribu orang';\n    }\n\n    function rowFor(\n      source,\n      state,\n      district,\n      year\n    ){\n\n      return source.find(function(row){\n\n        return (\n          row.state === state &#038;&#038;\n          row.district === district &#038;&#038;\n          yr(row) === year\n        );\n\n      }) || null;\n    }\n\n    function valueFor(\n      state,\n      district,\n      year,\n      metric\n    ){\n\n      if(district !== 'all'){\n\n        var row =\n          rowFor(\n            allRows,\n            state,\n            district,\n            year\n          );\n\n        return row\n          ? num(row[metric])\n          : null;\n      }\n\n      var rows =\n        allRows.filter(function(row){\n\n          return (\n            row.state === state &#038;&#038;\n            yr(row) === year\n          );\n\n        });\n\n      if(!rows.length){\n        return null;\n      }\n\n      if(isRateMetric(metric)){\n\n        var vals =\n          rows\n          .map(function(row){\n            return num(row[metric]);\n          })\n          .filter(valid);\n\n        if(!vals.length){\n          return null;\n        }\n\n        return vals.reduce(function(a,b){\n          return a+b;\n        },0) \/ vals.length;\n      }\n\n      var values =\n        rows\n        .map(function(row){\n          return num(row[metric]);\n        })\n        .filter(valid);\n\n      if(!values.length){\n        return null;\n      }\n\n      return values.reduce(function(a,b){\n        return a+b;\n      },0);\n    }\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('lfs-year').innerHTML =\n        years.map(function(year){\n\n          return (\n            '<option value=\"' +\n            year +\n            '\">' +\n            year +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      if(years.length){\n        el('lfs-year').value =\n          years[0];\n      }\n\n      var districts =\n        getDistricts();\n\n      el('lfs-district').innerHTML =\n        '<option value=\"all\">Semua Daerah<\/option>' +\n        districts.map(function(d){\n\n          return (\n            '<option value=\"' +\n            d +\n            '\">' +\n            d +\n            '<\/option>'\n          );\n\n        }).join('');\n    }\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n      var lf =\n        valueFor(\n          FIXED_STATE,\n          s.district,\n          s.year,\n          'lf'\n        );\n\n      var u =\n        valueFor(\n          FIXED_STATE,\n          s.district,\n          s.year,\n          'u_rate'\n        );\n\n      var p =\n        valueFor(\n          FIXED_STATE,\n          s.district,\n          s.year,\n          'p_rate'\n        );\n\n      el('lfs-stat-year').textContent =\n        s.year || '\u2014';\n\n      el('lfs-stat-lf').textContent =\n        valid(lf)\n          ? fmt(lf,1)\n          : '\u2014';\n\n      el('lfs-stat-u').textContent =\n        valid(u)\n          ? fmt(u,1)+'%'\n          : '\u2014';\n\n      el('lfs-stat-p').textContent =\n        valid(p)\n          ? fmt(p,1)+'%'\n          : '\u2014';\n\n      el('lfs-stats').style.display =\n        '';\n    }\n\n    function tooltipMetric(metric){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            var value =\n              c.parsed.y;\n\n            if(isRateMetric(metric)){\n\n              return (\n                ' ' +\n                c.dataset.label +\n                ': ' +\n                fmt(value,1) +\n                '%'\n              );\n            }\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(value,1) +\n              ' ribu orang'\n            );\n          }\n        }\n      };\n    }\n\n    function tooltipHorizontal(metric){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            var value =\n              c.parsed.x;\n\n            if(isRateMetric(metric)){\n\n              return (\n                ' ' +\n                c.dataset.label +\n                ': ' +\n                fmt(value,1) +\n                '%'\n              );\n            }\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(value,1) +\n              ' ribu orang'\n            );\n          }\n        }\n      };\n    }\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var years =\n        availableYears();\n\n      var data =\n        years.map(function(year){\n\n          return valueFor(\n            FIXED_STATE,\n            s.district,\n            year,\n            s.metric\n          );\n\n        });\n\n      el('lfs-trend-sub').textContent =\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n        ' \u00b7 ' +\n        metricLabel(s.metric);\n\n      destroyChart('trend');\n\n      charts.trend =\n        new Chart(\n          el('lfs-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n\n              datasets:[{\n                label:metricLabel(s.metric),\n                data:data,\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric(s.metric)\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'}\n                },\n\n                y:{\n                  beginAtZero:false,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n\n                      return isRateMetric(s.metric)\n                        ? fmt(v,1)+'%'\n                        : fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('labourTrend');\n\n      charts.labourTrend =\n        new Chart(\n          el('lfs-chart-labour-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n\n              datasets:[\n                {\n                  label:'Tenaga Buruh',\n                  data:years.map(function(year){\n                    return valueFor(FIXED_STATE,s.district,year,'lf');\n                  }),\n                  borderColor:'#2563eb',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                },\n\n                {\n                  label:'Penduduk Bekerja',\n                  data:years.map(function(year){\n                    return valueFor(FIXED_STATE,s.district,year,'lf_employed');\n                  }),\n                  borderColor:'#16a34a',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                },\n\n                {\n                  label:'Penganggur',\n                  data:years.map(function(year){\n                    return valueFor(FIXED_STATE,s.district,year,'lf_unemployed');\n                  }),\n                  borderColor:'#dc2626',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n              interaction:{mode:'index',intersect:false},\n\n              plugins:{\n                legend:{position:'bottom'},\n                tooltip:tooltipMetric('lf')\n              },\n\n              scales:{\n                x:{grid:{color:'#dbeafe'}},\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildDistrictCharts(){\n\n      var s =\n        getSelected();\n\n      var districts =\n        getDistricts();\n\n      var data =\n        districts.map(function(d){\n\n          return {\n            district:d,\n            value:valueFor(\n              FIXED_STATE,\n              d,\n              s.year,\n              s.metric\n            )\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('lfs-district-sub').textContent =\n        s.year +\n        ' \u00b7 ' +\n        metricLabel(s.metric);\n\n      destroyChart('district');\n\n      charts.district =\n        new Chart(\n          el('lfs-chart-district')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:data.map(function(d){\n                return d.district;\n              }),\n\n              datasets:[{\n                label:metricLabel(s.metric),\n\n                data:data.map(function(d){\n                  return d.value;\n                }),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric(s.metric)\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n                  ticks:{maxRotation:45}\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n\n                      return isRateMetric(s.metric)\n                        ? fmt(v,1)+'%'\n                        : fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var uData =\n        districts.map(function(d){\n\n          return {\n            district:d,\n            value:valueFor(\n              FIXED_STATE,\n              d,\n              s.year,\n              'u_rate'\n            )\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('lfs-district-u-sub').textContent =\n        s.year +\n        ' \u00b7 kadar pengangguran mengikut daerah';\n\n      destroyChart('districtU');\n\n      charts.districtU =\n        new Chart(\n          el('lfs-chart-district-u')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:uData.map(function(d){\n                return d.district;\n              }),\n\n              datasets:[{\n                label:'Kadar Pengangguran',\n\n                data:uData.map(function(d){\n                  return d.value;\n                }),\n\n                backgroundColor:'rgba(220,38,38,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric('u_rate')\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n                  ticks:{maxRotation:45}\n                },\n\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildStatusCharts(){\n\n      var s =\n        getSelected();\n\n      var employed =\n        valueFor(\n          FIXED_STATE,\n          s.district,\n          s.year,\n          'lf_employed'\n        ) || 0;\n\n      var unemployed =\n        valueFor(\n          FIXED_STATE,\n          s.district,\n          s.year,\n          'lf_unemployed'\n        ) || 0;\n\n      var outside =\n        valueFor(\n          FIXED_STATE,\n          s.district,\n          s.year,\n          'lf_outside'\n        ) || 0;\n\n      el('lfs-status-sub').textContent =\n        s.year +\n        ' \u00b7 ' +\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        );\n\n      destroyChart('status');\n\n      charts.status =\n        new Chart(\n          el('lfs-chart-status')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:[\n                'Penduduk Bekerja',\n                'Penganggur'\n              ],\n\n              datasets:[{\n                label:'Tenaga Buruh',\n                data:[employed,unemployed],\n                backgroundColor:[\n                  '#16a34a',\n                  '#dc2626'\n                ],\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{position:'bottom'},\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      var total =\n                        employed+unemployed;\n\n                      var share =\n                        total\n                          ? (c.parsed\/total)*100\n                          : 0;\n\n                      return (\n                        ' '+\n                        c.label+\n                        ': '+\n                        fmt(c.parsed,1)+\n                        ' ribu ('+\n                        fmt(share,1)+\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('populationComposition');\n\n      charts.populationComposition =\n        new Chart(\n          el('lfs-chart-pop-composition')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:[\n                'Penduduk Bekerja',\n                'Penganggur',\n                'Di Luar Tenaga Buruh'\n              ],\n\n              datasets:[{\n                label:'Penduduk',\n                data:[\n                  employed,\n                  unemployed,\n                  outside\n                ],\n                backgroundColor:[\n                  '#16a34a',\n                  '#dc2626',\n                  '#64748b'\n                ],\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n              plugins:{\n                legend:{position:'bottom'},\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n                  callbacks:{\n                    label:function(c){\n                      return (\n                        ' '+\n                        c.label+\n                        ': '+\n                        fmt(c.parsed,1)+\n                        ' ribu'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildRateCharts(){\n\n      var s =\n        getSelected();\n\n      var years =\n        availableYears();\n\n      el('lfs-u-trend-sub').textContent =\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n        ' \u00b7 kadar pengangguran';\n\n      destroyChart('uTrend');\n\n      charts.uTrend =\n        new Chart(\n          el('lfs-chart-u-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n\n              datasets:[{\n                label:'Kadar Pengangguran',\n\n                data:years.map(function(year){\n                  return valueFor(\n                    FIXED_STATE,\n                    s.district,\n                    year,\n                    'u_rate'\n                  );\n                }),\n\n                borderColor:'#dc2626',\n                backgroundColor:'rgba(220,38,38,.12)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric('u_rate')\n              },\n\n              scales:{\n                x:{grid:{color:'#dbeafe'}},\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      el('lfs-p-trend-sub').textContent =\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n        ' \u00b7 kadar penyertaan';\n\n      destroyChart('pTrend');\n\n      charts.pTrend =\n        new Chart(\n          el('lfs-chart-p-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:years,\n\n              datasets:[{\n                label:'Kadar Penyertaan',\n\n                data:years.map(function(year){\n                  return valueFor(\n                    FIXED_STATE,\n                    s.district,\n                    year,\n                    'p_rate'\n                  );\n                }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.12)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric('p_rate')\n              },\n\n              scales:{\n                x:{grid:{color:'#dbeafe'}},\n                y:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildRatioCharts(){\n\n      var s =\n        getSelected();\n\n      var districts =\n        getDistricts();\n\n      var data =\n        districts.map(function(d){\n\n          return {\n            district:d,\n            value:valueFor(\n              FIXED_STATE,\n              d,\n              s.year,\n              'ep_ratio'\n            )\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('lfs-ratio-sub').textContent =\n        s.year +\n        ' \u00b7 semua daerah';\n\n      destroyChart('ratio');\n\n      charts.ratio =\n        new Chart(\n          el('lfs-chart-ratio')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:data.map(function(d){\n                return d.district;\n              }),\n\n              datasets:[{\n                label:'Nisbah Guna Tenaga-Penduduk',\n\n                data:data.map(function(d){\n                  return d.value;\n                }),\n\n                backgroundColor:'rgba(124,58,237,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipHorizontal('ep_ratio')\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{display:false}\n                }\n              }\n            }\n          }\n        );\n\n      var p =\n        valueFor(FIXED_STATE,s.district,s.year,'p_rate');\n\n      var u =\n        valueFor(FIXED_STATE,s.district,s.year,'u_rate');\n\n      var ep =\n        valueFor(FIXED_STATE,s.district,s.year,'ep_ratio');\n\n      destroyChart('rateCompare');\n\n      charts.rateCompare =\n        new Chart(\n          el('lfs-chart-rate-compare')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:[\n                'Kadar Penyertaan',\n                'Kadar Pengangguran',\n                'Nisbah Guna Tenaga-Penduduk'\n              ],\n\n              datasets:[{\n                label:'Peratus',\n\n                data:[\n                  p,\n                  u,\n                  ep\n                ],\n\n                backgroundColor:[\n                  'rgba(37,99,235,.78)',\n                  'rgba(220,38,38,.78)',\n                  'rgba(124,58,237,.78)'\n                ],\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n                  callbacks:{\n                    label:function(c){\n                      return (\n                        ' '+\n                        c.label+\n                        ': '+\n                        fmt(c.parsed.y,1)+\n                        '%'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{grid:{display:false}},\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n      var prev =\n        getPrevYear(s.year);\n\n      if(prev === null){\n\n        el('lfs-change-sub').textContent =\n          s.year +\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('lfs-change-trend-sub').textContent =\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n      var districts =\n        getDistricts();\n\n      var data =\n        districts.map(function(d){\n\n          var now =\n            valueFor(\n              FIXED_STATE,\n              d,\n              s.year,\n              s.metric\n            );\n\n          var before =\n            valueFor(\n              FIXED_STATE,\n              d,\n              prev,\n              s.metric\n            );\n\n          return {\n            district:d,\n            change:\n              valid(now) && valid(before)\n                ? now-before\n                : null\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.change);\n        })\n        .sort(function(a,b){\n          return b.change-a.change;\n        });\n\n      el('lfs-change-sub').textContent =\n        s.year +\n        ' berbanding ' +\n        prev +\n        ' \u00b7 ' +\n        metricLabel(s.metric);\n\n      destroyChart('change');\n\n      charts.change =\n        new Chart(\n          el('lfs-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:data.map(function(d){\n                return d.district;\n              }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:data.map(function(d){\n                  return d.change;\n                }),\n\n                backgroundColor:data.map(function(d){\n                  return d.change >= 0\n                    ? 'rgba(37,99,235,.78)'\n                    : 'rgba(220,38,38,.78)';\n                }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return isRateMetric(s.metric)\n                        ? (\n                            ' Perubahan: '+\n                            signed(c.parsed.y,1)+\n                            ' mata peratusan'\n                          )\n                        : (\n                            ' Perubahan: '+\n                            signed(c.parsed.y,1)+\n                            ' ribu orang'\n                          );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n                  ticks:{maxRotation:45}\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var years =\n        availableYears();\n\n      var trend = [];\n\n      years.forEach(function(year,index){\n\n        if(index === 0){\n          return;\n        }\n\n        var previousYear =\n          years[index-1];\n\n        var now =\n          valueFor(\n            FIXED_STATE,\n            s.district,\n            year,\n            s.metric\n          );\n\n        var before =\n          valueFor(\n            FIXED_STATE,\n            s.district,\n            previousYear,\n            s.metric\n          );\n\n        if(valid(now) && valid(before)){\n\n          trend.push({\n            label:\n              year +\n              ' berbanding ' +\n              previousYear,\n\n            change:\n              now-before\n          });\n        }\n      });\n\n      el('lfs-change-trend-sub').textContent =\n        'Perubahan '+\n        metricLabel(s.metric)+\n        ' bagi setiap tempoh data yang tersedia';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend =\n        new Chart(\n          el('lfs-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:trend.map(function(d){\n                return d.label;\n              }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:trend.map(function(d){\n                  return d.change;\n                }),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return isRateMetric(s.metric)\n                        ? (\n                            ' Perubahan: '+\n                            signed(c.parsed.y,1)+\n                            ' mata peratusan'\n                          )\n                        : (\n                            ' Perubahan: '+\n                            signed(c.parsed.y,1)+\n                            ' ribu orang'\n                          );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{grid:{color:'#dbeafe'}},\n                y:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function stateMetricValue(\n      state,\n      year,\n      metric\n    ){\n\n      var rows =\n        allRows.filter(function(row){\n\n          return (\n            row.state === state &&\n            yr(row) === year\n          );\n\n        });\n\n      if(!rows.length){\n        return null;\n      }\n\n      if(isRateMetric(metric)){\n\n        var values =\n          rows\n          .map(function(row){\n            return num(row[metric]);\n          })\n          .filter(valid);\n\n        if(!values.length){\n          return null;\n        }\n\n        return values.reduce(function(a,b){\n          return a+b;\n        },0) \/ values.length;\n      }\n\n      var vals =\n        rows\n        .map(function(row){\n          return num(row[metric]);\n        })\n        .filter(valid);\n\n      if(!vals.length){\n        return null;\n      }\n\n      return vals.reduce(function(a,b){\n        return a+b;\n      },0);\n    }\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n      var states =\n        getStates();\n\n      var data =\n        states.map(function(state){\n\n          return {\n            state:state,\n            value:stateMetricValue(\n              state,\n              s.year,\n              s.metric\n            )\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('lfs-state-sub').textContent =\n        s.year +\n        ' \u00b7 semua negeri \u00b7 ' +\n        metricLabel(s.metric);\n\n      destroyChart('state');\n\n      charts.state =\n        new Chart(\n          el('lfs-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:data.map(function(d){\n                return d.state;\n              }),\n\n              datasets:[{\n                label:metricLabel(s.metric),\n\n                data:data.map(function(d){\n                  return d.value;\n                }),\n\n                backgroundColor:data.map(function(d){\n                  return d.state === 'Melaka'\n                    ? 'rgba(249,115,22,.95)'\n                    : 'rgba(37,99,235,.70)';\n                }),\n\n                borderColor:data.map(function(d){\n                  return d.state === 'Melaka'\n                    ? '#f97316'\n                    : 'rgba(37,99,235,.70)';\n                }),\n\n                borderWidth:data.map(function(d){\n                  return d.state === 'Melaka'\n                    ? 3\n                    : 0;\n                }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric(s.metric)\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(c){\n                      return c.tick.label === 'Melaka'\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(c){\n                      return {\n                        weight:\n                          c.tick.label === 'Melaka'\n                            ? '800'\n                            : '400',\n                        size:\n                          c.tick.label === 'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return isRateMetric(s.metric)\n                        ? fmt(v,1)+'%'\n                        : fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var uData =\n        states.map(function(state){\n\n          return {\n            state:state,\n            value:stateMetricValue(\n              state,\n              s.year,\n              'u_rate'\n            )\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      destroyChart('stateU');\n\n      charts.stateU =\n        new Chart(\n          el('lfs-chart-state-u')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:uData.map(function(d){\n                return d.state;\n              }),\n\n              datasets:[{\n                label:'Kadar Pengangguran',\n\n                data:uData.map(function(d){\n                  return d.value;\n                }),\n\n                backgroundColor:uData.map(function(d){\n                  return d.state === 'Melaka'\n                    ? 'rgba(249,115,22,.95)'\n                    : 'rgba(220,38,38,.70)';\n                }),\n\n                borderColor:uData.map(function(d){\n                  return d.state === 'Melaka'\n                    ? '#f97316'\n                    : 'rgba(220,38,38,.70)';\n                }),\n\n                borderWidth:uData.map(function(d){\n                  return d.state === 'Melaka'\n                    ? 3\n                    : 0;\n                }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric('u_rate')\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(c){\n                      return c.tick.label === 'Melaka'\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(c){\n                      return {\n                        weight:\n                          c.tick.label === 'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#lfs-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute('data-tab')\n        : 'trend';\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      var tab =\n        activeTab();\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'district'){\n        buildDistrictCharts();\n      }\n\n      if(tab === 'status'){\n        buildStatusCharts();\n      }\n\n      if(tab === 'rates'){\n        buildRateCharts();\n      }\n\n      if(tab === 'ratio'){\n        buildRatioCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n    document\n      .querySelectorAll(\n        '#lfs-wrap .tbtn'\n      )\n      .forEach(function(btn){\n\n        btn.addEventListener(\n          'click',\n          function(){\n\n            document\n              .querySelectorAll(\n                '#lfs-wrap .tbtn'\n              )\n              .forEach(function(b){\n                b.classList.remove('active');\n              });\n\n            btn.classList.add('active');\n\n            [\n              'trend',\n              'district',\n              'status',\n              'rates',\n              'ratio',\n              'change',\n              'state'\n            ]\n            .forEach(function(tab){\n\n              el(\n                'lfs-tab-' + tab\n              ).style.display = 'none';\n\n            });\n\n            el(\n              'lfs-tab-' +\n              btn.getAttribute('data-tab')\n            ).style.display = '';\n\n            redraw();\n          }\n        );\n      });\n\n    [\n      'lfs-year',\n      'lfs-district',\n      'lfs-metric'\n    ]\n    .forEach(function(id){\n\n      el(id)\n        .addEventListener(\n          'change',\n          redraw\n        );\n    });\n\n    fetch(API_URL)\n\n      .then(function(response){\n\n        if(!response.ok){\n          throw new Error(\n            'HTTP ' + response.status\n          );\n        }\n\n        return response.json();\n      })\n\n      .then(function(data){\n\n        allRows =\n          Array.isArray(data)\n            ? data\n            : [];\n\n        melakaRows =\n          allRows.filter(function(row){\n\n            return (\n              String(\n                row.state || ''\n              )\n              .trim()\n              .toLowerCase()\n              ===\n              'melaka'\n            );\n\n          });\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            'Tiada rekod tenaga buruh Melaka ditemui.'\n          );\n        }\n\n        populateFilters();\n\n        el('lfs-loading')\n          .style.display =\n          'none';\n\n        el('lfs-area')\n          .style.display =\n          '';\n\n        redraw();\n      })\n\n      .catch(function(error){\n\n        el('lfs-loading')\n          .innerHTML =\n\n          '<div style=\"' +\n          'color:#dc2626;' +\n          'font-size:16px;' +\n          'font-weight:800;' +\n          'margin-bottom:8px' +\n          '\">' +\n          'Gagal memuatkan data' +\n          '<\/div>' +\n\n          '<p style=\"' +\n          'font-size:13px;' +\n          'color:#64748b' +\n          '\">' +\n          error.message +\n          '<\/p>';\n      });\n  }\n\n  if(document.readyState === 'loading'){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#lfsq-wrap, #lfsq-wrap * { box-sizing:border-box; }\n\n#lfsq-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#lfsq-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#lfsq-wrap .hdr,\n#lfsq-wrap .cc,\n#lfsq-wrap .sc,\n#lfsq-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#lfsq-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#lfsq-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#lfsq-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#lfsq-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#lfsq-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(3,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#lfsq-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#lfsq-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#lfsq-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#lfsq-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#lfsq-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#lfsq-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#lfsq-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#lfsq-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#lfsq-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n}\n\n#lfsq-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#lfsq-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#lfsq-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#lfsq-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#lfsq-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#lfsq-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n}\n\n#lfsq-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#lfsq-wrap .chart-box{\n  position:relative;\n  height:370px;\n}\n\n#lfsq-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#lfsq-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#lfsq-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:lfsq-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes lfsq-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n\n  #lfsq-wrap .filter-bar,\n  #lfsq-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #lfsq-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #lfsq-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #lfsq-wrap .filter-bar,\n  #lfsq-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #lfsq-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #lfsq-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #lfsq-wrap .chart-box,\n  #lfsq-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n<div id=\"lfsq-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Tenaga Buruh Suku Tahunan Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Statistik Tenaga Buruh Utama Suku Tahunan Mengikut Negeri\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi statistik utama tenaga buruh Melaka secara suku tahunan,\n        termasuk saiz tenaga buruh, penduduk bekerja, penganggur, penduduk di luar\n        tenaga buruh, kadar pengangguran dan kadar penyertaan tenaga buruh.\n      <\/p>\n\n    <\/div>\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"lfsq-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Suku Tahun<\/label>\n        <select id=\"lfsq-quarter\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Ukuran Utama<\/label>\n\n        <select id=\"lfsq-metric\">\n          <option value=\"lf\">Tenaga Buruh<\/option>\n          <option value=\"lf_employed\">Penduduk Bekerja<\/option>\n          <option value=\"lf_unemployed\">Penganggur<\/option>\n          <option value=\"lf_outside\">Di Luar Tenaga Buruh<\/option>\n          <option value=\"u_rate\">Kadar Pengangguran<\/option>\n          <option value=\"p_rate\">Kadar Penyertaan<\/option>\n        <\/select>\n      <\/div>\n\n    <\/div>\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"lfsq-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tempoh Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"lfsq-stat-period\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tenaga Buruh\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"lfsq-stat-lf\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          ribu orang\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Kadar Pengangguran\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"lfsq-stat-u\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          peratus\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Kadar Penyertaan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"lfsq-stat-p\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          peratus\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"lfsq-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod tenaga buruh suku tahunan Melaka.\n      <\/div>\n\n    <\/div>\n\n    <!-- AREA -->\n    <div\n      id=\"lfsq-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TABS -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"quarter\"\n        >\n          Suku Tahunan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"status\"\n        >\n          Status Tenaga Buruh\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"rates\"\n        >\n          Kadar\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"seasonal\"\n        >\n          Corak Suku\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n      <\/div>\n\n      <!-- ======================\n           TREND\n      ======================= -->\n      <div id=\"lfsq-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Ukuran Utama Mengikut Masa\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"lfsq-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Tenaga Buruh dan Guna Tenaga\n            <\/div>\n\n            <div class=\"cs\">\n              Perbandingan tenaga buruh, penduduk bekerja dan penganggur bagi setiap suku tahun.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-labour-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- ======================\n           QUARTER\n      ======================= -->\n      <div\n        id=\"lfsq-tab-quarter\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Suku Tahun dalam Tahun Dipilih\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"lfsq-quarter-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"lfsq-chart-quarter\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Komposisi Tenaga Buruh Mengikut Suku Tahun\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan penduduk bekerja dan penganggur bagi setiap suku tahun.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"lfsq-chart-quarter-status\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- ======================\n           STATUS\n      ======================= -->\n      <div\n        id=\"lfsq-tab-status\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komposisi Status Tenaga Buruh\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"lfsq-status-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-status\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komposisi Penduduk Usia Bekerja\n            <\/div>\n\n            <div class=\"cs\">\n              Penduduk bekerja, penganggur dan penduduk di luar tenaga buruh.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-population\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- ======================\n           RATES\n      ======================= -->\n      <div\n        id=\"lfsq-tab-rates\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Kadar Pengangguran\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"lfsq-u-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-u\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Kadar Penyertaan Tenaga Buruh\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"lfsq-p-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-p\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- ======================\n           SEASONAL\n      ======================= -->\n      <div\n        id=\"lfsq-tab-seasonal\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Purata Mengikut Suku Tahun\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"lfsq-seasonal-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-seasonal\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Purata Kadar Pengangguran Mengikut Suku\n            <\/div>\n\n            <div class=\"cs\">\n              Purata kadar pengangguran bagi Suku 1 hingga Suku 4 sepanjang tempoh data.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-seasonal-u\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- ======================\n           CHANGE\n      ======================= -->\n      <div\n        id=\"lfsq-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Berbanding Suku Sebelumnya\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"lfsq-change-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-change\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Perubahan Suku Tahunan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"lfsq-change-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"lfsq-chart-change-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- ======================\n           STATE\n      ======================= -->\n      <div\n        id=\"lfsq-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"lfsq-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"lfsq-chart-state\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Kadar Pengangguran Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Melaka ditonjolkan bagi memudahkan perbandingan dengan negeri-negeri lain.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"lfsq-chart-state-u\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=lfs_qtr_state&limit=600';\n\n    var FIXED_STATE = 'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n    Chart.defaults.color = '#64748b';\n    Chart.defaults.borderColor = '#dbeafe';\n    Chart.defaults.font.family = 'Arial, sans-serif';\n\n    \/* ======================\n       BASIC\n    ======================= *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function num(v){\n\n      if(\n        v === null ||\n        v === undefined ||\n        v === ''\n      ){\n        return null;\n      }\n\n      var n =\n        Number(v);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n    function valid(v){\n      return (\n        v !== null &&\n        v !== undefined &&\n        !isNaN(v)\n      );\n    }\n\n    function fmt(v,digits){\n\n      if(!valid(v)){\n        return '\u2014';\n      }\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n      return Number(v)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:d,\n            maximumFractionDigits:d\n          }\n        );\n    }\n\n    function signed(v){\n\n      if(!valid(v)){\n        return '\u2014';\n      }\n\n      return (\n        (v > 0 ? '+' : '') +\n        fmt(v,1)\n      );\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    function parseDate(row){\n\n      var text =\n        String(row.date || '');\n\n      if(\n        !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(text)\n      ){\n        return null;\n      }\n\n      var parts =\n        text.split('-');\n\n      return {\n        year:parseInt(parts[0],10),\n        month:parseInt(parts[1],10),\n        day:parseInt(parts[2],10)\n      };\n    }\n\n    function rowYear(row){\n\n      var p =\n        parseDate(row);\n\n      return p\n        ? p.year\n        : null;\n    }\n\n    function rowQuarter(row){\n\n      var p =\n        parseDate(row);\n\n      if(!p){\n        return null;\n      }\n\n      if(p.month <= 3){\n        return 1;\n      }\n\n      if(p.month <= 6){\n        return 2;\n      }\n\n      if(p.month <= 9){\n        return 3;\n      }\n\n      return 4;\n    }\n\n    function quarterLabel(q){\n      return 'Suku ' + q;\n    }\n\n    function periodLabel(row){\n\n      return (\n        quarterLabel(\n          rowQuarter(row)\n        ) +\n        ' ' +\n        rowYear(row)\n      );\n    }\n\n    function periodKey(row){\n\n      var y =\n        rowYear(row);\n\n      var q =\n        rowQuarter(row);\n\n      return (\n        y * 10 +\n        q\n      );\n    }\n\n    function metricLabel(metric){\n\n      var labels = {\n        lf:'Tenaga Buruh',\n        lf_employed:'Penduduk Bekerja',\n        lf_unemployed:'Penganggur',\n        lf_outside:'Di Luar Tenaga Buruh',\n        u_rate:'Kadar Pengangguran',\n        p_rate:'Kadar Penyertaan'\n      };\n\n      return labels[metric] || metric;\n    }\n\n    function isRateMetric(metric){\n\n      return (\n        metric === 'u_rate' ||\n        metric === 'p_rate'\n      );\n    }\n\n    function metricText(value,metric){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      if(isRateMetric(metric)){\n        return fmt(value,1) + '%';\n      }\n\n      return fmt(value,1) + ' ribu orang';\n    }\n\n    \/* ======================\n       DATA\n    ======================= *\/\n\n    function getMelakaRowsSorted(){\n\n      return melakaRows\n        .slice()\n        .sort(function(a,b){\n          return periodKey(a)-periodKey(b);\n        });\n    }\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(rowYear)\n            .filter(function(y){\n              return y !== null;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n    function quartersForYear(year){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n              return rowYear(row) === year;\n            })\n            .map(rowQuarter)\n            .filter(function(q){\n              return q !== null;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n    function getSelected(){\n\n      return {\n        year:\n          parseInt(\n            el('lfsq-year').value,\n            10\n          ),\n\n        quarter:\n          parseInt(\n            el('lfsq-quarter').value,\n            10\n          ),\n\n        metric:\n          el('lfsq-metric').value\n      };\n    }\n\n    function selectedMelakaRow(){\n\n      var s =\n        getSelected();\n\n      return (\n        melakaRows.find(\n          function(row){\n\n            return (\n              rowYear(row) === s.year &#038;&#038;\n              rowQuarter(row) === s.quarter\n            );\n\n          }\n        ) || null\n      );\n    }\n\n    function rowForStatePeriod(\n      state,\n      year,\n      quarter\n    ){\n\n      return (\n        allRows.find(\n          function(row){\n\n            return (\n              row.state === state &#038;&#038;\n              rowYear(row) === year &#038;&#038;\n              rowQuarter(row) === quarter\n            );\n\n          }\n        ) || null\n      );\n    }\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort();\n    }\n\n    function getPreviousMelakaRow(\n      year,\n      quarter\n    ){\n\n      var rows =\n        getMelakaRowsSorted();\n\n      var index =\n        rows.findIndex(\n          function(row){\n\n            return (\n              rowYear(row) === year &#038;&#038;\n              rowQuarter(row) === quarter\n            );\n\n          }\n        );\n\n      if(index <= 0){\n        return null;\n      }\n\n      return rows[index-1];\n    }\n\n    \/* ======================\n       FILTER\n    ======================= *\/\n\n    function populateYearFilter(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('lfsq-year').innerHTML =\n        years.map(function(year){\n\n          return (\n            '<option value=\"' +\n            year +\n            '\">' +\n            year +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      if(years.length){\n        el('lfsq-year').value =\n          years[0];\n      }\n\n      populateQuarterFilter();\n    }\n\n    function populateQuarterFilter(){\n\n      var year =\n        parseInt(\n          el('lfsq-year').value,\n          10\n        );\n\n      var quarters =\n        quartersForYear(year);\n\n      var current =\n        parseInt(\n          el('lfsq-quarter').value,\n          10\n        );\n\n      el('lfsq-quarter').innerHTML =\n        quarters.map(function(q){\n\n          return (\n            '<option value=\"' +\n            q +\n            '\">' +\n            quarterLabel(q) +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      if(\n        current &&\n        quarters.indexOf(current) !== -1\n      ){\n        el('lfsq-quarter').value =\n          current;\n      }else if(quarters.length){\n\n        el('lfsq-quarter').value =\n          quarters[quarters.length-1];\n      }\n    }\n\n    \/* ======================\n       SUMMARY\n    ======================= *\/\n\n    function updateCards(){\n\n      var row =\n        selectedMelakaRow();\n\n      if(!row){\n        return;\n      }\n\n      el('lfsq-stat-period').textContent =\n        periodLabel(row);\n\n      el('lfsq-stat-lf').textContent =\n        valid(num(row.lf))\n          ? fmt(num(row.lf),1)\n          : '\u2014';\n\n      el('lfsq-stat-u').textContent =\n        valid(num(row.u_rate))\n          ? fmt(num(row.u_rate),1)+'%'\n          : '\u2014';\n\n      el('lfsq-stat-p').textContent =\n        valid(num(row.p_rate))\n          ? fmt(num(row.p_rate),1)+'%'\n          : '\u2014';\n\n      el('lfsq-stats').style.display =\n        '';\n    }\n\n    \/* ======================\n       TOOLTIP\n    ======================= *\/\n\n    function tooltipMetric(metric){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            var value =\n              c.parsed.y;\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              metricText(\n                value,\n                metric\n              )\n            );\n          }\n        }\n      };\n    }\n\n    \/* ======================\n       TREND\n    ======================= *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        getMelakaRowsSorted();\n\n      var labels =\n        rows.map(periodLabel);\n\n      el('lfsq-trend-sub').textContent =\n        'Melaka \u00b7 ' +\n        metricLabel(s.metric);\n\n      destroyChart('trend');\n\n      charts.trend =\n        new Chart(\n          el('lfsq-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  rows.map(function(row){\n                    return num(\n                      row[s.metric]\n                    );\n                  }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipMetric(\n                    s.metric\n                  )\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:12\n                  }\n                },\n\n                y:{\n                  beginAtZero:\n                    isRateMetric(\n                      s.metric\n                    ),\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n\n                      return isRateMetric(\n                        s.metric\n                      )\n                        ? fmt(v,1)+'%'\n                        : fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('labourTrend');\n\n      charts.labourTrend =\n        new Chart(\n          el('lfsq-chart-labour-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:labels,\n\n              datasets:[\n                {\n                  label:'Tenaga Buruh',\n\n                  data:\n                    rows.map(function(row){\n                      return num(row.lf);\n                    }),\n\n                  borderColor:'#2563eb',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:2\n                },\n\n                {\n                  label:'Penduduk Bekerja',\n\n                  data:\n                    rows.map(function(row){\n                      return num(row.lf_employed);\n                    }),\n\n                  borderColor:'#16a34a',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:2\n                },\n\n                {\n                  label:'Penganggur',\n\n                  data:\n                    rows.map(function(row){\n                      return num(row.lf_unemployed);\n                    }),\n\n                  borderColor:'#dc2626',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:2\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipMetric('lf')\n              },\n\n              scales:{\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:12\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ======================\n       QUARTER\n    ======================= *\/\n\n    function buildQuarterCharts(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        melakaRows\n        .filter(function(row){\n          return rowYear(row) === s.year;\n        })\n        .sort(function(a,b){\n          return rowQuarter(a)-rowQuarter(b);\n        });\n\n      el('lfsq-quarter-sub').textContent =\n        s.year +\n        ' \u00b7 ' +\n        metricLabel(s.metric);\n\n      destroyChart('quarter');\n\n      charts.quarter =\n        new Chart(\n          el('lfsq-chart-quarter')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                rows.map(function(row){\n                  return quarterLabel(\n                    rowQuarter(row)\n                  );\n                }),\n\n              datasets:[{\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  rows.map(function(row){\n                    return num(\n                      row[s.metric]\n                    );\n                  }),\n\n                backgroundColor:\n                  rows.map(function(row){\n\n                    return (\n                      rowQuarter(row) ===\n                      s.quarter\n                    )\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(37,99,235,.75)';\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric(s.metric)\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false}\n                },\n\n                y:{\n                  beginAtZero:\n                    isRateMetric(\n                      s.metric\n                    ),\n\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n\n                      return isRateMetric(s.metric)\n                        ? fmt(v,1)+'%'\n                        : fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('quarterStatus');\n\n      charts.quarterStatus =\n        new Chart(\n          el('lfsq-chart-quarter-status')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                rows.map(function(row){\n                  return quarterLabel(\n                    rowQuarter(row)\n                  );\n                }),\n\n              datasets:[\n                {\n                  label:'Penduduk Bekerja',\n\n                  data:\n                    rows.map(function(row){\n                      return num(\n                        row.lf_employed\n                      );\n                    }),\n\n                  backgroundColor:'rgba(22,163,74,.78)',\n                  borderRadius:5\n                },\n\n                {\n                  label:'Penganggur',\n\n                  data:\n                    rows.map(function(row){\n                      return num(\n                        row.lf_unemployed\n                      );\n                    }),\n\n                  backgroundColor:'rgba(220,38,38,.78)',\n                  borderRadius:5\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipMetric('lf')\n              },\n\n              scales:{\n                x:{\n                  stacked:true,\n                  grid:{display:false}\n                },\n\n                y:{\n                  stacked:true,\n                  beginAtZero:true,\n\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ======================\n       STATUS\n    ======================= *\/\n\n    function buildStatusCharts(){\n\n      var row =\n        selectedMelakaRow();\n\n      if(!row){\n        return;\n      }\n\n      var employed =\n        num(row.lf_employed) || 0;\n\n      var unemployed =\n        num(row.lf_unemployed) || 0;\n\n      var outside =\n        num(row.lf_outside) || 0;\n\n      el('lfsq-status-sub').textContent =\n        periodLabel(row) +\n        ' \u00b7 Melaka';\n\n      destroyChart('status');\n\n      charts.status =\n        new Chart(\n          el('lfsq-chart-status')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:[\n                'Penduduk Bekerja',\n                'Penganggur'\n              ],\n\n              datasets:[{\n                label:'Tenaga Buruh',\n\n                data:[\n                  employed,\n                  unemployed\n                ],\n\n                backgroundColor:[\n                  '#16a34a',\n                  '#dc2626'\n                ],\n\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      var total =\n                        employed+\n                        unemployed;\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' ribu (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('population');\n\n      charts.population =\n        new Chart(\n          el('lfsq-chart-population')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n              labels:[\n                'Penduduk Bekerja',\n                'Penganggur',\n                'Di Luar Tenaga Buruh'\n              ],\n\n              datasets:[{\n                label:'Penduduk',\n\n                data:[\n                  employed,\n                  unemployed,\n                  outside\n                ],\n\n                backgroundColor:[\n                  '#16a34a',\n                  '#dc2626',\n                  '#64748b'\n                ],\n\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' ribu'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ======================\n       RATES\n    ======================= *\/\n\n    function buildRateCharts(){\n\n      var rows =\n        getMelakaRowsSorted();\n\n      var labels =\n        rows.map(periodLabel);\n\n      el('lfsq-u-sub').textContent =\n        'Melaka \u00b7 kadar pengangguran suku tahunan';\n\n      el('lfsq-p-sub').textContent =\n        'Melaka \u00b7 kadar penyertaan tenaga buruh suku tahunan';\n\n      destroyChart('uTrend');\n\n      charts.uTrend =\n        new Chart(\n          el('lfsq-chart-u')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:'Kadar Pengangguran',\n\n                data:\n                  rows.map(function(row){\n                    return num(row.u_rate);\n                  }),\n\n                borderColor:'#dc2626',\n                backgroundColor:'rgba(220,38,38,.12)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric('u_rate')\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:12\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('pTrend');\n\n      charts.pTrend =\n        new Chart(\n          el('lfsq-chart-p')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:'Kadar Penyertaan',\n\n                data:\n                  rows.map(function(row){\n                    return num(row.p_rate);\n                  }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.12)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric('p_rate')\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:12\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ======================\n       SEASONAL\n    ======================= *\/\n\n    function averageByQuarter(metric){\n\n      var result = [];\n\n      [1,2,3,4]\n        .forEach(function(q){\n\n          var values =\n            melakaRows\n            .filter(function(row){\n\n              return (\n                rowQuarter(row) === q &&\n                valid(\n                  num(row[metric])\n                )\n              );\n\n            })\n            .map(function(row){\n              return num(row[metric]);\n            });\n\n          result.push(\n            values.length\n              ? values.reduce(\n                  function(a,b){\n                    return a+b;\n                  },\n                  0\n                ) \/ values.length\n              : null\n          );\n\n        });\n\n      return result;\n    }\n\n    function buildSeasonalCharts(){\n\n      var s =\n        getSelected();\n\n      el('lfsq-seasonal-sub').textContent =\n        'Purata ' +\n        metricLabel(s.metric) +\n        ' mengikut suku tahun sepanjang tempoh data';\n\n      destroyChart('seasonal');\n\n      charts.seasonal =\n        new Chart(\n          el('lfsq-chart-seasonal')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:[\n                'Suku 1',\n                'Suku 2',\n                'Suku 3',\n                'Suku 4'\n              ],\n\n              datasets:[{\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  averageByQuarter(\n                    s.metric\n                  ),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric(s.metric)\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false}\n                },\n\n                y:{\n                  beginAtZero:\n                    isRateMetric(s.metric),\n\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n\n                      return isRateMetric(s.metric)\n                        ? fmt(v,1)+'%'\n                        : fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('seasonalU');\n\n      charts.seasonalU =\n        new Chart(\n          el('lfsq-chart-seasonal-u')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:[\n                'Suku 1',\n                'Suku 2',\n                'Suku 3',\n                'Suku 4'\n              ],\n\n              datasets:[{\n                label:'Kadar Pengangguran',\n\n                data:\n                  averageByQuarter(\n                    'u_rate'\n                  ),\n\n                backgroundColor:'rgba(220,38,38,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric('u_rate')\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false}\n                },\n\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ======================\n       CHANGE\n    ======================= *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n      var current =\n        selectedMelakaRow();\n\n      if(!current){\n        return;\n      }\n\n      var previous =\n        getPreviousMelakaRow(\n          s.year,\n          s.quarter\n        );\n\n      if(!previous){\n\n        el('lfsq-change-sub').textContent =\n          periodLabel(current) +\n          ' \u00b7 Tiada suku sebelumnya tersedia.';\n\n        el('lfsq-change-trend-sub').textContent =\n          'Tiada tempoh sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n      var metrics = [\n        'lf',\n        'lf_employed',\n        'lf_unemployed',\n        'lf_outside'\n      ];\n\n      var changes =\n        metrics.map(function(metric){\n\n          var now =\n            num(current[metric]);\n\n          var before =\n            num(previous[metric]);\n\n          return (\n            valid(now) &&\n            valid(before)\n          )\n            ? now-before\n            : null;\n\n        });\n\n      el('lfsq-change-sub').textContent =\n        periodLabel(current) +\n        ' berbanding ' +\n        periodLabel(previous);\n\n      destroyChart('change');\n\n      charts.change =\n        new Chart(\n          el('lfsq-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:[\n                'Tenaga Buruh',\n                'Penduduk Bekerja',\n                'Penganggur',\n                'Di Luar Tenaga Buruh'\n              ],\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:changes,\n\n                backgroundColor:\n                  changes.map(function(value){\n\n                    return value >= 0\n                      ? 'rgba(37,99,235,.78)'\n                      : 'rgba(220,38,38,.78)';\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signed(\n                          c.parsed.y\n                        ) +\n                        ' ribu orang'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false}\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var rows =\n        getMelakaRowsSorted();\n\n      var trend = [];\n\n      rows.forEach(\n        function(row,index){\n\n          if(index === 0){\n            return;\n          }\n\n          var prev =\n            rows[index-1];\n\n          var now =\n            num(row[s.metric]);\n\n          var before =\n            num(prev[s.metric]);\n\n          if(\n            valid(now) &&\n            valid(before)\n          ){\n\n            trend.push({\n              label:\n                periodLabel(row),\n\n              change:\n                now-before\n            });\n          }\n\n        }\n      );\n\n      el('lfsq-change-trend-sub').textContent =\n        'Perubahan ' +\n        metricLabel(s.metric) +\n        ' berbanding suku sebelumnya';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend =\n        new Chart(\n          el('lfsq-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                trend.map(function(d){\n                  return d.label;\n                }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:\n                  trend.map(function(d){\n                    return d.change;\n                  }),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n                    label:function(c){\n\n                      return isRateMetric(\n                        s.metric\n                      )\n                        ? (\n                            ' Perubahan: ' +\n                            signed(\n                              c.parsed.y\n                            ) +\n                            ' mata peratusan'\n                          )\n                        : (\n                            ' Perubahan: ' +\n                            signed(\n                              c.parsed.y\n                            ) +\n                            ' ribu orang'\n                          );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:12\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ======================\n       STATE\n    ======================= *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n      var states =\n        getStates();\n\n      var data =\n        states.map(function(state){\n\n          var row =\n            rowForStatePeriod(\n              state,\n              s.year,\n              s.quarter\n            );\n\n          return {\n            state:state,\n\n            value:\n              row\n                ? num(\n                    row[s.metric]\n                  )\n                : null\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('lfsq-state-sub').textContent =\n        quarterLabel(\n          s.quarter\n        ) +\n        ' ' +\n        s.year +\n        ' \u00b7 semua negeri \u00b7 ' +\n        metricLabel(\n          s.metric\n        );\n\n      destroyChart('state');\n\n      charts.state =\n        new Chart(\n          el('lfsq-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:[{\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  data.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(37,99,235,.70)';\n                  }),\n\n                borderColor:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? '#f97316'\n                      : 'rgba(37,99,235,.70)';\n                  }),\n\n                borderWidth:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 3\n                      : 0;\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric(s.metric)\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(c){\n\n                      return c.tick.label === 'Melaka'\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(c){\n\n                      return {\n                        weight:\n                          c.tick.label === 'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          c.tick.label === 'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  beginAtZero:\n                    isRateMetric(\n                      s.metric\n                    ),\n\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n\n                      return isRateMetric(\n                        s.metric\n                      )\n                        ? fmt(v,1)+'%'\n                        : fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var uData =\n        states.map(function(state){\n\n          var row =\n            rowForStatePeriod(\n              state,\n              s.year,\n              s.quarter\n            );\n\n          return {\n            state:state,\n\n            value:\n              row\n                ? num(\n                    row.u_rate\n                  )\n                : null\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      destroyChart('stateU');\n\n      charts.stateU =\n        new Chart(\n          el('lfsq-chart-state-u')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                uData.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:[{\n                label:'Kadar Pengangguran',\n\n                data:\n                  uData.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:\n                  uData.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(220,38,38,.68)';\n                  }),\n\n                borderColor:\n                  uData.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? '#f97316'\n                      : 'rgba(220,38,38,.68)';\n                  }),\n\n                borderWidth:\n                  uData.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 3\n                      : 0;\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipMetric('u_rate')\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(c){\n\n                      return c.tick.label === 'Melaka'\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(c){\n\n                      return {\n                        weight:\n                          c.tick.label === 'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ======================\n       REDRAW\n    ======================= *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#lfsq-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute('data-tab')\n        : 'trend';\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      var tab =\n        activeTab();\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'quarter'){\n        buildQuarterCharts();\n      }\n\n      if(tab === 'status'){\n        buildStatusCharts();\n      }\n\n      if(tab === 'rates'){\n        buildRateCharts();\n      }\n\n      if(tab === 'seasonal'){\n        buildSeasonalCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n    \/* ======================\n       TAB EVENT\n    ======================= *\/\n\n    document\n      .querySelectorAll(\n        '#lfsq-wrap .tbtn'\n      )\n      .forEach(function(btn){\n\n        btn.addEventListener(\n          'click',\n          function(){\n\n            document\n              .querySelectorAll(\n                '#lfsq-wrap .tbtn'\n              )\n              .forEach(function(b){\n                b.classList.remove(\n                  'active'\n                );\n              });\n\n            btn.classList.add(\n              'active'\n            );\n\n            [\n              'trend',\n              'quarter',\n              'status',\n              'rates',\n              'seasonal',\n              'change',\n              'state'\n            ]\n            .forEach(function(tab){\n\n              el(\n                'lfsq-tab-' + tab\n              ).style.display =\n                'none';\n            });\n\n            el(\n              'lfsq-tab-' +\n              btn.getAttribute(\n                'data-tab'\n              )\n            ).style.display =\n              '';\n\n            redraw();\n          }\n        );\n      });\n\n    \/* ======================\n       FILTER EVENT\n    ======================= *\/\n\n    el('lfsq-year')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateQuarterFilter();\n          redraw();\n        }\n      );\n\n    [\n      'lfsq-quarter',\n      'lfsq-metric'\n    ]\n    .forEach(function(id){\n\n      el(id)\n        .addEventListener(\n          'change',\n          redraw\n        );\n    });\n\n    \/* ======================\n       FETCH\n    ======================= *\/\n\n    fetch(API_URL)\n\n      .then(function(response){\n\n        if(!response.ok){\n\n          throw new Error(\n            'HTTP ' +\n            response.status\n          );\n        }\n\n        return response.json();\n      })\n\n      .then(function(data){\n\n        allRows =\n          Array.isArray(data)\n            ? data\n            : [];\n\n        melakaRows =\n          allRows.filter(\n            function(row){\n\n              return (\n                String(\n                  row.state || ''\n                )\n                .trim()\n                .toLowerCase()\n                ===\n                'melaka'\n              );\n            }\n          );\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            'Tiada rekod tenaga buruh suku tahunan Melaka ditemui.'\n          );\n        }\n\n        populateYearFilter();\n\n        el('lfsq-loading')\n          .style.display =\n          'none';\n\n        el('lfsq-area')\n          .style.display =\n          '';\n\n        redraw();\n      })\n\n      .catch(function(error){\n\n        el('lfsq-loading')\n          .innerHTML =\n\n          '<div style=\"' +\n          'color:#dc2626;' +\n          'font-size:16px;' +\n          'font-weight:800;' +\n          'margin-bottom:8px' +\n          '\">' +\n\n          'Gagal memuatkan data' +\n\n          '<\/div>' +\n\n          '<p style=\"' +\n          'font-size:13px;' +\n          'color:#64748b' +\n          '\">' +\n\n          error.message +\n\n          '<\/p>';\n      });\n\n  }\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<div style=\"text-align: center; margin: 40px 0 30px 0;\">\n  <h2 style=\"\n    font-size: 32px;\n    font-weight: 700;\n    color: #1f3c88;\n    margin-bottom: 10px;\n    letter-spacing: 0.5px;\n  \">\n    Dashboard Kategori Harga\n  <\/h2>\n\n  <div style=\"\n    width: 80px;\n    height: 4px;\n    background-color: #1f3c88;\n    margin: 0 auto;\n    border-radius: 4px;\n  \"><\/div>\n<\/div>\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#cpi-wrap, #cpi-wrap * { box-sizing:border-box; }\n\n#cpi-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#cpi-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#cpi-wrap .hdr,\n#cpi-wrap .cc,\n#cpi-wrap .sc,\n#cpi-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#cpi-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#cpi-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#cpi-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#cpi-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#cpi-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(3,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#cpi-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#cpi-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#cpi-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#cpi-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#cpi-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#cpi-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#cpi-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#cpi-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#cpi-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n}\n\n#cpi-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#cpi-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#cpi-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#cpi-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#cpi-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#cpi-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n}\n\n#cpi-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#cpi-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#cpi-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#cpi-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#cpi-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:cpi-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes cpi-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n  #cpi-wrap .filter-bar,\n  #cpi-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #cpi-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n  #cpi-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #cpi-wrap .filter-bar,\n  #cpi-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #cpi-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #cpi-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #cpi-wrap .chart-box,\n  #cpi-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n<div id=\"cpi-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Indeks Harga Pengguna Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Harga Pengguna Berdasarkan CPI Bulanan\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi Indeks Harga Pengguna (CPI) bulanan bagi Melaka\n        mengikut kumpulan barangan dan perkhidmatan. Indeks menggunakan\n        asas 2010 = 100 dan turut digunakan untuk melihat perubahan harga\n        bulan ke bulan serta inflasi tahun ke tahun.\n      <\/p>\n\n    <\/div>\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"cpi-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Bulan<\/label>\n        <select id=\"cpi-month\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Divisyen CPI<\/label>\n        <select id=\"cpi-division\"><\/select>\n      <\/div>\n\n    <\/div>\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"cpi-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tempoh Dipilih<\/div>\n        <div class=\"sv\" id=\"cpi-stat-period\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Indeks CPI<\/div>\n        <div class=\"sv\" id=\"cpi-stat-index\">\u2014<\/div>\n        <div class=\"su\">asas 2010 = 100<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Perubahan Bulanan<\/div>\n        <div class=\"sv\" id=\"cpi-stat-mom\">\u2014<\/div>\n        <div class=\"su\" id=\"cpi-stat-mom-sub\">berbanding bulan sebelumnya<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Inflasi Tahunan<\/div>\n        <div class=\"sv\" id=\"cpi-stat-yoy\">\u2014<\/div>\n        <div class=\"su\" id=\"cpi-stat-yoy-sub\">berbanding bulan sama tahun sebelumnya<\/div>\n      <\/div>\n\n    <\/div>\n\n    <!-- LOADING -->\n    <div class=\"sbox\" id=\"cpi-loading\">\n\n      <div class=\"spin\"><\/div>\n\n      <strong>Memuatkan data&#8230;<\/strong>\n\n      <div>\n        Sedang mendapatkan rekod Indeks Harga Pengguna Melaka.\n      <\/div>\n\n    <\/div>\n\n    <!-- DASHBOARD -->\n    <div id=\"cpi-area\" style=\"display:none\">\n\n      <div class=\"tab-bar\">\n\n        <button class=\"tbtn active\" data-tab=\"trend\">\n          Trend\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"monthly\">\n          Bulanan\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"division\">\n          Divisyen\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"inflation\">\n          Inflasi\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"seasonal\">\n          Corak Bulanan\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"change\">\n          Perubahan\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"state\">\n          Negeri\n        <\/button>\n\n      <\/div>\n\n      <!-- TREND -->\n      <div id=\"cpi-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Indeks CPI Mengikut Masa\n            <\/div>\n\n            <div class=\"cs\" id=\"cpi-trend-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"cpi-chart-trend\"><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Inflasi Tahun ke Tahun\n            <\/div>\n\n            <div class=\"cs\">\n              Kadar perubahan CPI berbanding bulan yang sama pada tahun sebelumnya.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"cpi-chart-yoy-trend\"><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- MONTHLY -->\n      <div id=\"cpi-tab-monthly\" style=\"display:none\">\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            CPI Mengikut Bulan dalam Tahun Dipilih\n          <\/div>\n\n          <div class=\"cs\" id=\"cpi-monthly-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"cpi-chart-monthly\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perubahan CPI Bulan ke Bulan\n          <\/div>\n\n          <div class=\"cs\">\n            Perubahan peratus indeks berbanding bulan sebelumnya.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"cpi-chart-monthly-change\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- DIVISION -->\n      <div id=\"cpi-tab-division\" style=\"display:none\">\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan CPI Mengikut Divisyen\n          <\/div>\n\n          <div class=\"cs\" id=\"cpi-division-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"cpi-chart-division\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Inflasi Tahunan Mengikut Divisyen\n          <\/div>\n\n          <div class=\"cs\">\n            Kadar perubahan CPI bagi setiap kumpulan barangan dan perkhidmatan\n            berbanding bulan sama pada tahun sebelumnya.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"cpi-chart-division-yoy\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- INFLATION -->\n      <div id=\"cpi-tab-inflation\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Inflasi Bulanan\n            <\/div>\n\n            <div class=\"cs\" id=\"cpi-inflation-mom-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"cpi-chart-mom\"><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Inflasi Tahunan\n            <\/div>\n\n            <div class=\"cs\" id=\"cpi-inflation-yoy-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"cpi-chart-yoy\"><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- SEASONAL -->\n      <div id=\"cpi-tab-seasonal\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Purata CPI Mengikut Bulan\n            <\/div>\n\n            <div class=\"cs\" id=\"cpi-seasonal-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"cpi-chart-seasonal\"><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Purata Inflasi Mengikut Bulan\n            <\/div>\n\n            <div class=\"cs\">\n              Purata inflasi tahun ke tahun bagi Januari hingga Disember sepanjang tempoh data.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"cpi-chart-seasonal-yoy\"><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- CHANGE -->\n      <div id=\"cpi-tab-change\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan CPI Mengikut Divisyen\n            <\/div>\n\n            <div class=\"cs\" id=\"cpi-change-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"cpi-chart-change\"><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Perubahan Bulanan\n            <\/div>\n\n            <div class=\"cs\" id=\"cpi-change-trend-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"cpi-chart-change-trend\"><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- STATE -->\n      <div id=\"cpi-tab-state\" style=\"display:none\">\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan CPI Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\" id=\"cpi-state-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"cpi-chart-state\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Inflasi Tahunan Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Melaka ditonjolkan untuk memudahkan perbandingan dengan negeri-negeri lain.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"cpi-chart-state-yoy\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=cpi_state&limit=50000';\n\n    var FIXED_STATE = 'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n    Chart.defaults.color = '#64748b';\n    Chart.defaults.borderColor = '#dbeafe';\n    Chart.defaults.font.family = 'Arial, sans-serif';\n\n    \/* ===================================\n       DIVISION LABELS\n    ==================================== *\/\n\n    var DIVISION_LABELS = {\n\n      overall:\n        'Keseluruhan',\n\n      '01':\n        'Makanan dan Minuman Bukan Alkohol',\n\n      '02':\n        'Minuman Alkohol dan Tembakau',\n\n      '03':\n        'Pakaian dan Kasut',\n\n      '04':\n        'Perumahan, Air, Elektrik, Gas dan Bahan Api Lain',\n\n      '05':\n        'Hiasan, Perkakasan dan Penyelenggaraan Isi Rumah',\n\n      '06':\n        'Kesihatan',\n\n      '07':\n        'Pengangkutan',\n\n      '08':\n        'Maklumat dan Komunikasi',\n\n      '09':\n        'Rekreasi, Sukan dan Kebudayaan',\n\n      '10':\n        'Perkhidmatan Pendidikan',\n\n      '11':\n        'Perkhidmatan Restoran dan Penginapan',\n\n      '12':\n        'Insurans dan Perkhidmatan Kewangan',\n\n      '13':\n        'Penjagaan Diri, Perlindungan Sosial dan Pelbagai Barangan dan Perkhidmatan'\n    };\n\n    \/* ===================================\n       BASIC HELPERS\n    ==================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function num(v){\n\n      if(\n        v === null ||\n        v === undefined ||\n        v === ''\n      ){\n        return null;\n      }\n\n      var n = Number(v);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n    function valid(v){\n\n      return (\n        v !== null &&\n        v !== undefined &&\n        !isNaN(v)\n      );\n    }\n\n    function fmt(v,digits){\n\n      if(!valid(v)){\n        return '\u2014';\n      }\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n      return Number(v).toLocaleString(\n        'ms-MY',\n        {\n          minimumFractionDigits:d,\n          maximumFractionDigits:d\n        }\n      );\n    }\n\n    function signedPct(v){\n\n      if(!valid(v)){\n        return '\u2014';\n      }\n\n      return (\n        (v > 0 ? '+' : '') +\n        fmt(v,1) +\n        '%'\n      );\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    function parseDate(row){\n\n      var text =\n        String(row.date || '');\n\n      if(\n        !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(text)\n      ){\n        return null;\n      }\n\n      var parts =\n        text.split('-');\n\n      return {\n        year:parseInt(parts[0],10),\n        month:parseInt(parts[1],10),\n        day:parseInt(parts[2],10)\n      };\n    }\n\n    function rowYear(row){\n\n      var p =\n        parseDate(row);\n\n      return p\n        ? p.year\n        : null;\n    }\n\n    function rowMonth(row){\n\n      var p =\n        parseDate(row);\n\n      return p\n        ? p.month\n        : null;\n    }\n\n    function periodKey(row){\n\n      var y =\n        rowYear(row);\n\n      var m =\n        rowMonth(row);\n\n      return (\n        y * 100 +\n        m\n      );\n    }\n\n    function monthName(month){\n\n      var names = [\n        '',\n        'Januari',\n        'Februari',\n        'Mac',\n        'April',\n        'Mei',\n        'Jun',\n        'Julai',\n        'Ogos',\n        'September',\n        'Oktober',\n        'November',\n        'Disember'\n      ];\n\n      return names[month] || '';\n    }\n\n    function shortMonth(month){\n\n      var names = [\n        '',\n        'Jan',\n        'Feb',\n        'Mac',\n        'Apr',\n        'Mei',\n        'Jun',\n        'Jul',\n        'Ogo',\n        'Sep',\n        'Okt',\n        'Nov',\n        'Dis'\n      ];\n\n      return names[month] || '';\n    }\n\n    function periodLabel(row){\n\n      return (\n        shortMonth(\n          rowMonth(row)\n        ) +\n        ' ' +\n        rowYear(row)\n      );\n    }\n\n    function fullPeriodLabel(year,month){\n\n      return (\n        monthName(month) +\n        ' ' +\n        year\n      );\n    }\n\n    function divisionLabel(code){\n\n      return (\n        DIVISION_LABELS[code] ||\n        (\n          code === 'overall'\n            ? 'Keseluruhan'\n            : 'Divisyen ' + code\n        )\n      );\n    }\n\n    function percentChange(current,previous){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n      return (\n        (\n          current -\n          previous\n        ) \/\n        previous\n      ) * 100;\n    }\n\n    \/* ===================================\n       DATA HELPERS\n    ==================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(rowYear)\n            .filter(function(y){\n              return y !== null;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n    function monthsForYear(year){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n              return rowYear(row) === year;\n            })\n            .map(rowMonth)\n            .filter(function(month){\n              return month !== null;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n    function getDivisions(){\n\n      var values =\n        Array.from(\n          new Set(\n            melakaRows\n              .map(function(row){\n                return String(\n                  row.division || ''\n                );\n              })\n              .filter(Boolean)\n          )\n        );\n\n      return values.sort(\n        function(a,b){\n\n          if(a === 'overall'){\n            return -1;\n          }\n\n          if(b === 'overall'){\n            return 1;\n          }\n\n          return a.localeCompare(b);\n        }\n      );\n    }\n\n    function getSelected(){\n\n      return {\n        year:\n          parseInt(\n            el('cpi-year').value,\n            10\n          ),\n\n        month:\n          parseInt(\n            el('cpi-month').value,\n            10\n          ),\n\n        division:\n          el('cpi-division').value\n      };\n    }\n\n    function rowFor(\n      source,\n      state,\n      year,\n      month,\n      division\n    ){\n\n      return (\n        source.find(\n          function(row){\n\n            return (\n              row.state === state &&\n              rowYear(row) === year &&\n              rowMonth(row) === month &&\n              String(row.division) ===\n              String(division)\n            );\n\n          }\n        ) || null\n      );\n    }\n\n    function melakaRow(\n      year,\n      month,\n      division\n    ){\n\n      return rowFor(\n        melakaRows,\n        FIXED_STATE,\n        year,\n        month,\n        division\n      );\n    }\n\n    function getDivisionRowsSorted(division){\n\n      return melakaRows\n        .filter(function(row){\n\n          return (\n            String(row.division) ===\n            String(division)\n          );\n\n        })\n        .slice()\n        .sort(function(a,b){\n          return periodKey(a)-periodKey(b);\n        });\n    }\n\n    function getPreviousRow(\n      year,\n      month,\n      division\n    ){\n\n      var rows =\n        getDivisionRowsSorted(\n          division\n        );\n\n      var index =\n        rows.findIndex(\n          function(row){\n\n            return (\n              rowYear(row) === year &&\n              rowMonth(row) === month\n            );\n\n          }\n        );\n\n      if(index <= 0){\n        return null;\n      }\n\n      return rows[index-1];\n    }\n\n    function getPreviousYearRow(\n      year,\n      month,\n      division\n    ){\n\n      return melakaRow(\n        year-1,\n        month,\n        division\n      );\n    }\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort();\n    }\n\n    \/* ===================================\n       FILTERS\n    ==================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('cpi-year').innerHTML =\n        years.map(function(year){\n\n          return (\n            '<option value=\"' +\n            year +\n            '\">' +\n            year +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      if(years.length){\n        el('cpi-year').value =\n          years[0];\n      }\n\n      populateMonthFilter();\n\n      var divisions =\n        getDivisions();\n\n      el('cpi-division').innerHTML =\n        divisions.map(function(code){\n\n          return (\n            '<option value=\"' +\n            code +\n            '\">' +\n            (\n              code === 'overall'\n                ? 'Keseluruhan'\n                : code +\n                  ' - ' +\n                  divisionLabel(code)\n            ) +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      if(\n        divisions.indexOf(\n          'overall'\n        ) !== -1\n      ){\n        el('cpi-division').value =\n          'overall';\n      }\n    }\n\n    function populateMonthFilter(){\n\n      var year =\n        parseInt(\n          el('cpi-year').value,\n          10\n        );\n\n      var months =\n        monthsForYear(year);\n\n      var current =\n        parseInt(\n          el('cpi-month').value,\n          10\n        );\n\n      el('cpi-month').innerHTML =\n        months.map(function(month){\n\n          return (\n            '<option value=\"' +\n            month +\n            '\">' +\n            monthName(month) +\n            '<\/option>'\n          );\n\n        }).join('');\n\n      if(\n        current &&\n        months.indexOf(current) !== -1\n      ){\n\n        el('cpi-month').value =\n          current;\n\n      }else if(months.length){\n\n        el('cpi-month').value =\n          months[\n            months.length-1\n          ];\n      }\n    }\n\n    \/* ===================================\n       SUMMARY CARDS\n    ==================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n      var current =\n        melakaRow(\n          s.year,\n          s.month,\n          s.division\n        );\n\n      if(!current){\n        return;\n      }\n\n      var currentIndex =\n        num(current.index);\n\n      var previous =\n        getPreviousRow(\n          s.year,\n          s.month,\n          s.division\n        );\n\n      var previousIndex =\n        previous\n          ? num(previous.index)\n          : null;\n\n      var yearAgo =\n        getPreviousYearRow(\n          s.year,\n          s.month,\n          s.division\n        );\n\n      var yearAgoIndex =\n        yearAgo\n          ? num(yearAgo.index)\n          : null;\n\n      var mom =\n        percentChange(\n          currentIndex,\n          previousIndex\n        );\n\n      var yoy =\n        percentChange(\n          currentIndex,\n          yearAgoIndex\n        );\n\n      el('cpi-stat-period')\n        .textContent =\n        fullPeriodLabel(\n          s.year,\n          s.month\n        );\n\n      el('cpi-stat-index')\n        .textContent =\n        fmt(\n          currentIndex,\n          1\n        );\n\n      el('cpi-stat-mom')\n        .textContent =\n        valid(mom)\n          ? signedPct(mom)\n          : '\u2014';\n\n      el('cpi-stat-yoy')\n        .textContent =\n        valid(yoy)\n          ? signedPct(yoy)\n          : '\u2014';\n\n      el('cpi-stat-mom-sub')\n        .textContent =\n        previous\n          ? (\n              'berbanding ' +\n              fullPeriodLabel(\n                rowYear(previous),\n                rowMonth(previous)\n              )\n            )\n          : 'tiada bulan sebelumnya';\n\n      el('cpi-stat-yoy-sub')\n        .textContent =\n        yearAgo\n          ? (\n              'berbanding ' +\n              fullPeriodLabel(\n                rowYear(yearAgo),\n                rowMonth(yearAgo)\n              )\n            )\n          : 'tiada tempoh tahun sebelumnya';\n\n      el('cpi-stats').style.display =\n        '';\n    }\n\n    \/* ===================================\n       TOOLTIP\n    ==================================== *\/\n\n    function tooltipIndex(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.y,\n                1\n              )\n            );\n          }\n        }\n      };\n    }\n\n    function tooltipPercent(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              signedPct(\n                c.parsed.y\n              )\n            );\n          }\n        }\n      };\n    }\n\n    function tooltipHorizontalIndex(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            return (\n              ' Indeks CPI: ' +\n              fmt(\n                c.parsed.x,\n                1\n              )\n            );\n          }\n        }\n      };\n    }\n\n    function tooltipHorizontalPercent(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n          label:function(c){\n\n            return (\n              ' Inflasi: ' +\n              signedPct(\n                c.parsed.x\n              )\n            );\n          }\n        }\n      };\n    }\n\n    \/* ===================================\n       TREND\n    ==================================== *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        getDivisionRowsSorted(\n          s.division\n        );\n\n      var labels =\n        rows.map(periodLabel);\n\n      el('cpi-trend-sub')\n        .textContent =\n        'Melaka \u00b7 ' +\n        divisionLabel(\n          s.division\n        );\n\n      destroyChart('trend');\n\n      charts.trend =\n        new Chart(\n          el('cpi-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:'Indeks CPI',\n\n                data:\n                  rows.map(function(row){\n                    return num(\n                      row.index\n                    );\n                  }),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipIndex()\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:14\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var yoyData =\n        rows.map(function(row){\n\n          var previous =\n            melakaRow(\n              rowYear(row)-1,\n              rowMonth(row),\n              s.division\n            );\n\n          return previous\n            ? percentChange(\n                num(row.index),\n                num(previous.index)\n              )\n            : null;\n        });\n\n      destroyChart('yoyTrend');\n\n      charts.yoyTrend =\n        new Chart(\n          el('cpi-chart-yoy-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:'Inflasi Tahunan',\n\n                data:yoyData,\n\n                borderColor:'#dc2626',\n                backgroundColor:'rgba(220,38,38,.12)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:14\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ===================================\n       MONTHLY\n    ==================================== *\/\n\n    function buildMonthlyCharts(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        melakaRows\n        .filter(function(row){\n\n          return (\n            rowYear(row) === s.year &&\n            String(row.division) ===\n            String(s.division)\n          );\n\n        })\n        .sort(function(a,b){\n          return rowMonth(a)-rowMonth(b);\n        });\n\n      el('cpi-monthly-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 ' +\n        divisionLabel(\n          s.division\n        );\n\n      destroyChart('monthly');\n\n      charts.monthly =\n        new Chart(\n          el('cpi-chart-monthly')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                rows.map(function(row){\n                  return monthName(\n                    rowMonth(row)\n                  );\n                }),\n\n              datasets:[{\n                label:'Indeks CPI',\n\n                data:\n                  rows.map(function(row){\n                    return num(\n                      row.index\n                    );\n                  }),\n\n                backgroundColor:\n                  rows.map(function(row){\n\n                    return (\n                      rowMonth(row) ===\n                      s.month\n                    )\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(37,99,235,.75)';\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipIndex()\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n                  ticks:{maxRotation:45}\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var monthlyChanges =\n        rows.map(function(row){\n\n          var prev =\n            getPreviousRow(\n              rowYear(row),\n              rowMonth(row),\n              s.division\n            );\n\n          return prev\n            ? percentChange(\n                num(row.index),\n                num(prev.index)\n              )\n            : null;\n        });\n\n      destroyChart('monthlyChange');\n\n      charts.monthlyChange =\n        new Chart(\n          el('cpi-chart-monthly-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                rows.map(function(row){\n                  return monthName(\n                    rowMonth(row)\n                  );\n                }),\n\n              datasets:[{\n                label:'Perubahan Bulanan',\n\n                data:monthlyChanges,\n\n                backgroundColor:\n                  monthlyChanges.map(\n                    function(value){\n\n                      return (\n                        valid(value) &&\n                        value < 0\n                      )\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false}\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ===================================\n       DIVISION COMPARISON\n    ==================================== *\/\n\n    function buildDivisionCharts(){\n\n      var s =\n        getSelected();\n\n      var divisions =\n        getDivisions()\n        .filter(function(code){\n          return code !== 'overall';\n        });\n\n      var data =\n        divisions.map(function(code){\n\n          var row =\n            melakaRow(\n              s.year,\n              s.month,\n              code\n            );\n\n          return {\n            code:code,\n            label:divisionLabel(code),\n\n            value:\n              row\n                ? num(row.index)\n                : null\n          };\n\n        })\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('cpi-division-sub')\n        .textContent =\n        fullPeriodLabel(\n          s.year,\n          s.month\n        ) +\n        ' \u00b7 Melaka';\n\n      destroyChart('division');\n\n      charts.division =\n        new Chart(\n          el('cpi-chart-division')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return (\n                    d.code +\n                    ' - ' +\n                    d.label\n                  );\n                }),\n\n              datasets:[{\n                label:'Indeks CPI',\n\n                data:\n                  data.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipHorizontalIndex()\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{display:false}\n                }\n              }\n            }\n          }\n        );\n\n      var yoy =\n        divisions.map(function(code){\n\n          var now =\n            melakaRow(\n              s.year,\n              s.month,\n              code\n            );\n\n          var before =\n            melakaRow(\n              s.year-1,\n              s.month,\n              code\n            );\n\n          return {\n            code:code,\n            label:divisionLabel(code),\n\n            value:\n              now &#038;&#038; before\n                ? percentChange(\n                    num(now.index),\n                    num(before.index)\n                  )\n                : null\n          };\n\n        })\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      destroyChart('divisionYoy');\n\n      charts.divisionYoy =\n        new Chart(\n          el('cpi-chart-division-yoy')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                yoy.map(function(d){\n                  return (\n                    d.code +\n                    ' - ' +\n                    d.label\n                  );\n                }),\n\n              datasets:[{\n                label:'Inflasi Tahunan',\n\n                data:\n                  yoy.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:\n                  yoy.map(function(d){\n\n                    return d.value >= 0\n                      ? 'rgba(220,38,38,.78)'\n                      : 'rgba(22,163,74,.78)';\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipHorizontalPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{display:false}\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ===================================\n       INFLATION\n    ==================================== *\/\n\n    function buildInflationCharts(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        getDivisionRowsSorted(\n          s.division\n        );\n\n      var labels =\n        rows.map(periodLabel);\n\n      var mom =\n        rows.map(\n          function(row,index){\n\n            if(index === 0){\n              return null;\n            }\n\n            return percentChange(\n              num(row.index),\n              num(\n                rows[index-1].index\n              )\n            );\n          }\n        );\n\n      var yoy =\n        rows.map(function(row){\n\n          var prev =\n            melakaRow(\n              rowYear(row)-1,\n              rowMonth(row),\n              s.division\n            );\n\n          return prev\n            ? percentChange(\n                num(row.index),\n                num(prev.index)\n              )\n            : null;\n        });\n\n      el('cpi-inflation-mom-sub')\n        .textContent =\n        'Melaka \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 perubahan bulan ke bulan';\n\n      el('cpi-inflation-yoy-sub')\n        .textContent =\n        'Melaka \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 perubahan tahun ke tahun';\n\n      destroyChart('mom');\n\n      charts.mom =\n        new Chart(\n          el('cpi-chart-mom')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:'Inflasi Bulanan',\n\n                data:mom,\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:12\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('yoy');\n\n      charts.yoy =\n        new Chart(\n          el('cpi-chart-yoy')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:'Inflasi Tahunan',\n\n                data:yoy,\n\n                borderColor:'#dc2626',\n                backgroundColor:'rgba(220,38,38,.12)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:12\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ===================================\n       SEASONAL\n    ==================================== *\/\n\n    function averageByMonth(\n      division,\n      mode\n    ){\n\n      var result = [];\n\n      for(\n        var month=1;\n        month<=12;\n        month++\n      ){\n\n        var rows =\n          melakaRows.filter(\n            function(row){\n\n              return (\n                rowMonth(row) === month &#038;&#038;\n                String(row.division) ===\n                String(division)\n              );\n            }\n          );\n\n        var values = [];\n\n        rows.forEach(function(row){\n\n          if(mode === 'index'){\n\n            var value =\n              num(row.index);\n\n            if(valid(value)){\n              values.push(value);\n            }\n\n          }else{\n\n            var previous =\n              melakaRow(\n                rowYear(row)-1,\n                month,\n                division\n              );\n\n            if(previous){\n\n              var change =\n                percentChange(\n                  num(row.index),\n                  num(previous.index)\n                );\n\n              if(valid(change)){\n                values.push(change);\n              }\n            }\n          }\n        });\n\n        result.push(\n          values.length\n            ? values.reduce(\n                function(a,b){\n                  return a+b;\n                },\n                0\n              ) \/ values.length\n            : null\n        );\n      }\n\n      return result;\n    }\n\n    function buildSeasonalCharts(){\n\n      var s =\n        getSelected();\n\n      var labels = [\n        'Januari',\n        'Februari',\n        'Mac',\n        'April',\n        'Mei',\n        'Jun',\n        'Julai',\n        'Ogos',\n        'September',\n        'Oktober',\n        'November',\n        'Disember'\n      ];\n\n      el('cpi-seasonal-sub')\n        .textContent =\n        'Purata CPI \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 sepanjang tempoh data';\n\n      destroyChart('seasonal');\n\n      charts.seasonal =\n        new Chart(\n          el('cpi-chart-seasonal')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:'Purata CPI',\n\n                data:\n                  averageByMonth(\n                    s.division,\n                    'index'\n                  ),\n\n                backgroundColor:'rgba(37,99,235,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipIndex()\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n                  ticks:{maxRotation:45}\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('seasonalYoy');\n\n      charts.seasonalYoy =\n        new Chart(\n          el('cpi-chart-seasonal-yoy')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:labels,\n\n              datasets:[{\n                label:'Purata Inflasi Tahunan',\n\n                data:\n                  averageByMonth(\n                    s.division,\n                    'yoy'\n                  ),\n\n                backgroundColor:'rgba(220,38,38,.78)',\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n                  ticks:{maxRotation:45}\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ===================================\n       CHANGE\n    ==================================== *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n      var divisions =\n        getDivisions()\n        .filter(function(code){\n          return code !== 'overall';\n        });\n\n      var data =\n        divisions.map(function(code){\n\n          var now =\n            melakaRow(\n              s.year,\n              s.month,\n              code\n            );\n\n          var prev =\n            getPreviousRow(\n              s.year,\n              s.month,\n              code\n            );\n\n          return {\n            code:code,\n            label:divisionLabel(code),\n\n            change:\n              now &#038;&#038; prev\n                ? percentChange(\n                    num(now.index),\n                    num(prev.index)\n                  )\n                : null\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.change);\n        })\n        .sort(function(a,b){\n          return b.change-a.change;\n        });\n\n      el('cpi-change-sub')\n        .textContent =\n        fullPeriodLabel(\n          s.year,\n          s.month\n        ) +\n        ' \u00b7 perubahan bulan ke bulan';\n\n      destroyChart('change');\n\n      charts.change =\n        new Chart(\n          el('cpi-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return (\n                    d.code +\n                    ' - ' +\n                    d.label\n                  );\n                }),\n\n              datasets:[{\n                label:'Perubahan',\n\n                data:\n                  data.map(function(d){\n                    return d.change;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.change >= 0\n                      ? 'rgba(220,38,38,.78)'\n                      : 'rgba(22,163,74,.78)';\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipHorizontalPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{display:false}\n                }\n              }\n            }\n          }\n        );\n\n      var rows =\n        getDivisionRowsSorted(\n          s.division\n        );\n\n      var trend = [];\n\n      rows.forEach(\n        function(row,index){\n\n          if(index === 0){\n            return;\n          }\n\n          trend.push({\n            label:periodLabel(row),\n\n            change:\n              percentChange(\n                num(row.index),\n                num(\n                  rows[index-1].index\n                )\n              )\n          });\n        }\n      );\n\n      el('cpi-change-trend-sub')\n        .textContent =\n        'Perubahan bulanan \u00b7 ' +\n        divisionLabel(\n          s.division\n        );\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend =\n        new Chart(\n          el('cpi-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n              labels:\n                trend.map(function(d){\n                  return d.label;\n                }),\n\n              datasets:[{\n                label:'Perubahan Bulanan',\n\n                data:\n                  trend.map(function(d){\n                    return d.change;\n                  }),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{color:'#dbeafe'},\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:12\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ===================================\n       STATE\n    ==================================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n      var states =\n        getStates();\n\n      var data =\n        states.map(function(state){\n\n          var row =\n            rowFor(\n              allRows,\n              state,\n              s.year,\n              s.month,\n              s.division\n            );\n\n          return {\n            state:state,\n\n            value:\n              row\n                ? num(row.index)\n                : null\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('cpi-state-sub')\n        .textContent =\n        fullPeriodLabel(\n          s.year,\n          s.month\n        ) +\n        ' \u00b7 semua negeri \u00b7 ' +\n        divisionLabel(\n          s.division\n        );\n\n      destroyChart('state');\n\n      charts.state =\n        new Chart(\n          el('cpi-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                data.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:[{\n                label:'Indeks CPI',\n\n                data:\n                  data.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 'rgba(249,115,22,.95)'\n                      : 'rgba(37,99,235,.70)';\n                  }),\n\n                borderColor:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? '#f97316'\n                      : 'rgba(37,99,235,.70)';\n                  }),\n\n                borderWidth:\n                  data.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 3\n                      : 0;\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipIndex()\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(c){\n\n                      return c.tick.label === 'Melaka'\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(c){\n\n                      return {\n                        weight:\n                          c.tick.label === 'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          c.tick.label === 'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var yoyData =\n        states.map(function(state){\n\n          var now =\n            rowFor(\n              allRows,\n              state,\n              s.year,\n              s.month,\n              s.division\n            );\n\n          var before =\n            rowFor(\n              allRows,\n              state,\n              s.year-1,\n              s.month,\n              s.division\n            );\n\n          return {\n            state:state,\n\n            value:\n              now && before\n                ? percentChange(\n                    num(now.index),\n                    num(before.index)\n                  )\n                : null\n          };\n\n        })\n        .filter(function(d){\n          return valid(d.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      destroyChart('stateYoy');\n\n      charts.stateYoy =\n        new Chart(\n          el('cpi-chart-state-yoy')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n              labels:\n                yoyData.map(function(d){\n                  return d.state;\n                }),\n\n              datasets:[{\n                label:'Inflasi Tahunan',\n\n                data:\n                  yoyData.map(function(d){\n                    return d.value;\n                  }),\n\n                backgroundColor:\n                  yoyData.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 'rgba(249,115,22,.95)'\n                      : (\n                          d.value >= 0\n                            ? 'rgba(220,38,38,.68)'\n                            : 'rgba(22,163,74,.68)'\n                        );\n                  }),\n\n                borderColor:\n                  yoyData.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? '#f97316'\n                      : 'transparent';\n                  }),\n\n                borderWidth:\n                  yoyData.map(function(d){\n\n                    return d.state === 'Melaka'\n                      ? 3\n                      : 0;\n                  }),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{display:false},\n                tooltip:tooltipPercent()\n              },\n\n              scales:{\n                x:{\n                  grid:{display:false},\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(c){\n\n                      return c.tick.label === 'Melaka'\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(c){\n\n                      return {\n                        weight:\n                          c.tick.label === 'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(v){\n                      return fmt(v,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n    \/* ===================================\n       TAB CONTROL\n    ==================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#cpi-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      var tab =\n        activeTab();\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'monthly'){\n        buildMonthlyCharts();\n      }\n\n      if(tab === 'division'){\n        buildDivisionCharts();\n      }\n\n      if(tab === 'inflation'){\n        buildInflationCharts();\n      }\n\n      if(tab === 'seasonal'){\n        buildSeasonalCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n    document\n      .querySelectorAll(\n        '#cpi-wrap .tbtn'\n      )\n      .forEach(function(btn){\n\n        btn.addEventListener(\n          'click',\n          function(){\n\n            document\n              .querySelectorAll(\n                '#cpi-wrap .tbtn'\n              )\n              .forEach(function(b){\n\n                b.classList.remove(\n                  'active'\n                );\n              });\n\n            btn.classList.add(\n              'active'\n            );\n\n            [\n              'trend',\n              'monthly',\n              'division',\n              'inflation',\n              'seasonal',\n              'change',\n              'state'\n            ]\n            .forEach(function(tab){\n\n              el(\n                'cpi-tab-' + tab\n              ).style.display =\n                'none';\n            });\n\n            el(\n              'cpi-tab-' +\n              btn.getAttribute(\n                'data-tab'\n              )\n            ).style.display =\n              '';\n\n            redraw();\n          }\n        );\n      });\n\n    \/* ===================================\n       FILTER EVENTS\n    ==================================== *\/\n\n    el('cpi-year')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateMonthFilter();\n          redraw();\n        }\n      );\n\n    [\n      'cpi-month',\n      'cpi-division'\n    ]\n    .forEach(function(id){\n\n      el(id)\n        .addEventListener(\n          'change',\n          redraw\n        );\n    });\n\n    \/* ===================================\n       API\n    ==================================== *\/\n\n    fetch(API_URL)\n\n      .then(function(response){\n\n        if(!response.ok){\n\n          throw new Error(\n            'HTTP ' +\n            response.status\n          );\n        }\n\n        return response.json();\n      })\n\n      .then(function(data){\n\n        allRows =\n          Array.isArray(data)\n            ? data\n            : [];\n\n        melakaRows =\n          allRows.filter(\n            function(row){\n\n              return (\n                String(\n                  row.state || ''\n                )\n                .trim()\n                .toLowerCase()\n                ===\n                'melaka'\n              );\n            }\n          );\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            'Tiada rekod CPI Melaka ditemui.'\n          );\n        }\n\n        populateFilters();\n\n        el('cpi-loading')\n          .style.display =\n          'none';\n\n        el('cpi-area')\n          .style.display =\n          '';\n\n        redraw();\n      })\n\n      .catch(function(error){\n\n        el('cpi-loading')\n          .innerHTML =\n\n          '<div style=\"' +\n          'color:#dc2626;' +\n          'font-size:16px;' +\n          'font-weight:800;' +\n          'margin-bottom:8px' +\n          '\">' +\n\n          'Gagal memuatkan data' +\n\n          '<\/div>' +\n\n          '<p style=\"' +\n          'font-size:13px;' +\n          'color:#64748b' +\n          '\">' +\n\n          error.message +\n\n          '<\/p>';\n      });\n\n  }\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#cpiinf-wrap, #cpiinf-wrap * { box-sizing:border-box; }\n\n#cpiinf-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#cpiinf-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#cpiinf-wrap .hdr,\n#cpiinf-wrap .cc,\n#cpiinf-wrap .sc,\n#cpiinf-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#cpiinf-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#cpiinf-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#cpiinf-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#cpiinf-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#cpiinf-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#cpiinf-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#cpiinf-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#cpiinf-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#cpiinf-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#cpiinf-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#cpiinf-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#cpiinf-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#cpiinf-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#cpiinf-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n  line-height:1.35;\n}\n\n#cpiinf-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#cpiinf-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#cpiinf-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#cpiinf-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#cpiinf-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#cpiinf-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n  line-height:1.45;\n}\n\n#cpiinf-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#cpiinf-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#cpiinf-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#cpiinf-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#cpiinf-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:cpiinf-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes cpiinf-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n  #cpiinf-wrap .filter-bar,\n  #cpiinf-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #cpiinf-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n  #cpiinf-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #cpiinf-wrap .filter-bar,\n  #cpiinf-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #cpiinf-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #cpiinf-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #cpiinf-wrap .chart-box,\n  #cpiinf-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n<div id=\"cpiinf-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Inflasi Harga Pengguna Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Harga Pengguna Berdasarkan Inflasi CPI Bulanan\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi inflasi Indeks Harga Pengguna (CPI) bulanan bagi Melaka\n        mengikut kumpulan barangan dan perkhidmatan. Dashboard memaparkan\n        perubahan harga bulan ke bulan (MoM) dan perubahan tahun ke tahun\n        (YoY) berdasarkan kadar inflasi yang diterbitkan dalam dataset.\n      <\/p>\n\n    <\/div>\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"cpiinf-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Bulan<\/label>\n        <select id=\"cpiinf-month\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Divisyen CPI<\/label>\n        <select id=\"cpiinf-division\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Ukuran Inflasi<\/label>\n\n        <select id=\"cpiinf-metric\">\n          <option value=\"inflation_yoy\">\n            Inflasi Tahunan (YoY)\n          <\/option>\n\n          <option value=\"inflation_mom\">\n            Inflasi Bulanan (MoM)\n          <\/option>\n        <\/select>\n      <\/div>\n\n    <\/div>\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"cpiinf-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tempoh Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"cpiinf-stat-period\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Inflasi Bulanan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"cpiinf-stat-mom\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          bulan ke bulan (MoM)\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Inflasi Tahunan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"cpiinf-stat-yoy\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          tahun ke tahun (YoY)\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Inflasi YoY Tertinggi\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"cpiinf-stat-high\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"cpiinf-stat-high-name\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"cpiinf-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod inflasi CPI Melaka.\n      <\/div>\n\n    <\/div>\n\n    <!-- DASHBOARD -->\n    <div\n      id=\"cpiinf-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TABS -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"monthly\"\n        >\n          Bulanan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"division\"\n        >\n          Divisyen\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"compare\"\n        >\n          MoM vs YoY\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"seasonal\"\n        >\n          Corak Bulanan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n      <\/div>\n\n      <!-- =====================================\n           TREND\n      ====================================== -->\n      <div id=\"cpiinf-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Inflasi Mengikut Masa\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cpiinf-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"cpiinf-chart-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend MoM dan YoY\n            <\/div>\n\n            <div class=\"cs\">\n              Perbandingan perubahan harga bulan ke bulan dan tahun ke tahun\n              bagi divisyen CPI yang dipilih.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"cpiinf-chart-both-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =====================================\n           MONTHLY\n      ====================================== -->\n      <div\n        id=\"cpiinf-tab-monthly\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Inflasi Mengikut Bulan dalam Tahun Dipilih\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"cpiinf-monthly-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"cpiinf-chart-monthly\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            MoM dan YoY Mengikut Bulan\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan kadar inflasi bulanan dan tahunan bagi setiap bulan\n            dalam tahun dipilih.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"cpiinf-chart-monthly-both\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =====================================\n           DIVISION\n      ====================================== -->\n      <div\n        id=\"cpiinf-tab-division\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Inflasi Mengikut Divisyen\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"cpiinf-division-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"cpiinf-chart-division\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan MoM dan YoY Mengikut Divisyen\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan inflasi jangka pendek dan tahunan bagi 13 kumpulan\n            utama barangan dan perkhidmatan.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"cpiinf-chart-division-both\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =====================================\n           MOM VS YOY\n      ====================================== -->\n      <div\n        id=\"cpiinf-tab-compare\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Inflasi Bulanan (MoM)\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cpiinf-mom-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"cpiinf-chart-mom\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Inflasi Tahunan (YoY)\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cpiinf-yoy-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"cpiinf-chart-yoy\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =====================================\n           SEASONAL\n      ====================================== -->\n      <div\n        id=\"cpiinf-tab-seasonal\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Purata Inflasi Bulanan Mengikut Bulan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cpiinf-seasonal-mom-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"cpiinf-chart-seasonal-mom\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Purata Inflasi Tahunan Mengikut Bulan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cpiinf-seasonal-yoy-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"cpiinf-chart-seasonal-yoy\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =====================================\n           CHANGE\n      ====================================== -->\n      <div\n        id=\"cpiinf-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Kadar Inflasi Mengikut Divisyen\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cpiinf-change-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"cpiinf-chart-change\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Momentum Inflasi\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cpiinf-change-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"cpiinf-chart-change-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n      <!-- =====================================\n           STATE\n      ====================================== -->\n      <div\n        id=\"cpiinf-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Inflasi Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"cpiinf-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"cpiinf-chart-state\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan MoM dan YoY Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Melaka ditonjolkan untuk memudahkan perbandingan dengan\n            negeri-negeri lain bagi bulan dan divisyen CPI yang sama.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"cpiinf-chart-state-both\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=cpi_state_inflation&limit=50000';\n\n    var FIXED_STATE =\n      'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n    Chart.defaults.color =\n      '#64748b';\n\n    Chart.defaults.borderColor =\n      '#dbeafe';\n\n    Chart.defaults.font.family =\n      'Arial, sans-serif';\n\n\n    \/* =========================================================\n       DIVISION LABELS\n    ========================================================== *\/\n\n    var DIVISION_LABELS = {\n\n      overall:\n        'Keseluruhan',\n\n      '01':\n        'Makanan dan Minuman Bukan Alkohol',\n\n      '02':\n        'Minuman Alkohol dan Tembakau',\n\n      '03':\n        'Pakaian dan Kasut',\n\n      '04':\n        'Perumahan, Air, Elektrik, Gas dan Bahan Api Lain',\n\n      '05':\n        'Hiasan, Perkakasan dan Penyelenggaraan Isi Rumah',\n\n      '06':\n        'Kesihatan',\n\n      '07':\n        'Pengangkutan',\n\n      '08':\n        'Maklumat dan Komunikasi',\n\n      '09':\n        'Rekreasi, Sukan dan Kebudayaan',\n\n      '10':\n        'Perkhidmatan Pendidikan',\n\n      '11':\n        'Perkhidmatan Restoran dan Penginapan',\n\n      '12':\n        'Insurans dan Perkhidmatan Kewangan',\n\n      '13':\n        'Penjagaan Diri, Perlindungan Sosial dan Pelbagai Barangan dan Perkhidmatan'\n    };\n\n\n    \/* =========================================================\n       BASIC HELPERS\n    ========================================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === ''\n      ){\n        return null;\n      }\n\n      var n =\n        Number(value);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n    function valid(value){\n\n      return (\n        value !== null &&\n        value !== undefined &&\n        !isNaN(value)\n      );\n    }\n\n    function fmt(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n      return Number(value)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:d,\n            maximumFractionDigits:d\n          }\n        );\n    }\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,1) +\n        '%'\n      );\n    }\n\n    function signedPP(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,1) +\n        ' mata peratusan'\n      );\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    function parseDate(row){\n\n      var text =\n        String(\n          row.date || ''\n        );\n\n      if(\n        !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(\n          text\n        )\n      ){\n        return null;\n      }\n\n      var parts =\n        text.split('-');\n\n      return {\n        year:\n          parseInt(\n            parts[0],\n            10\n          ),\n\n        month:\n          parseInt(\n            parts[1],\n            10\n          ),\n\n        day:\n          parseInt(\n            parts[2],\n            10\n          )\n      };\n    }\n\n    function rowYear(row){\n\n      var p =\n        parseDate(row);\n\n      return p\n        ? p.year\n        : null;\n    }\n\n    function rowMonth(row){\n\n      var p =\n        parseDate(row);\n\n      return p\n        ? p.month\n        : null;\n    }\n\n    function periodKey(row){\n\n      return (\n        rowYear(row) * 100 +\n        rowMonth(row)\n      );\n    }\n\n    function monthName(month){\n\n      var names = [\n        '',\n        'Januari',\n        'Februari',\n        'Mac',\n        'April',\n        'Mei',\n        'Jun',\n        'Julai',\n        'Ogos',\n        'September',\n        'Oktober',\n        'November',\n        'Disember'\n      ];\n\n      return names[month] || '';\n    }\n\n    function shortMonth(month){\n\n      var names = [\n        '',\n        'Jan',\n        'Feb',\n        'Mac',\n        'Apr',\n        'Mei',\n        'Jun',\n        'Jul',\n        'Ogo',\n        'Sep',\n        'Okt',\n        'Nov',\n        'Dis'\n      ];\n\n      return names[month] || '';\n    }\n\n    function periodLabel(row){\n\n      return (\n        shortMonth(\n          rowMonth(row)\n        ) +\n        ' ' +\n        rowYear(row)\n      );\n    }\n\n    function fullPeriodLabel(\n      year,\n      month\n    ){\n\n      return (\n        monthName(month) +\n        ' ' +\n        year\n      );\n    }\n\n    function divisionLabel(code){\n\n      return (\n        DIVISION_LABELS[code] ||\n        (\n          code === 'overall'\n            ? 'Keseluruhan'\n            : 'Divisyen ' + code\n        )\n      );\n    }\n\n    function metricLabel(metric){\n\n      if(metric === 'inflation_mom'){\n        return 'Inflasi Bulanan (MoM)';\n      }\n\n      return 'Inflasi Tahunan (YoY)';\n    }\n\n\n    \/* =========================================================\n       DATA HELPERS\n    ========================================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(rowYear)\n            .filter(function(year){\n              return year !== null;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n    function monthsForYear(year){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n              return rowYear(row) === year;\n            })\n            .map(rowMonth)\n            .filter(function(month){\n              return month !== null;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n    function getDivisions(){\n\n      var divisions =\n        Array.from(\n          new Set(\n            melakaRows\n              .map(function(row){\n\n                return String(\n                  row.division || ''\n                );\n              })\n              .filter(Boolean)\n          )\n        );\n\n      return divisions.sort(\n        function(a,b){\n\n          if(a === 'overall'){\n            return -1;\n          }\n\n          if(b === 'overall'){\n            return 1;\n          }\n\n          return a.localeCompare(b);\n        }\n      );\n    }\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort();\n    }\n\n    function getSelected(){\n\n      return {\n\n        year:\n          parseInt(\n            el('cpiinf-year').value,\n            10\n          ),\n\n        month:\n          parseInt(\n            el('cpiinf-month').value,\n            10\n          ),\n\n        division:\n          el('cpiinf-division').value,\n\n        metric:\n          el('cpiinf-metric').value\n      };\n    }\n\n    function rowFor(\n      source,\n      state,\n      year,\n      month,\n      division\n    ){\n\n      return (\n        source.find(\n          function(row){\n\n            return (\n              String(\n                row.state || ''\n              ) === state &&\n\n              rowYear(row) === year &&\n\n              rowMonth(row) === month &&\n\n              String(\n                row.division\n              ) ===\n              String(\n                division\n              )\n            );\n          }\n        ) || null\n      );\n    }\n\n    function melakaRow(\n      year,\n      month,\n      division\n    ){\n\n      return rowFor(\n        melakaRows,\n        FIXED_STATE,\n        year,\n        month,\n        division\n      );\n    }\n\n    function getDivisionRowsSorted(\n      division\n    ){\n\n      return melakaRows\n        .filter(function(row){\n\n          return (\n            String(\n              row.division\n            ) ===\n            String(\n              division\n            )\n          );\n        })\n        .slice()\n        .sort(function(a,b){\n\n          return (\n            periodKey(a) -\n            periodKey(b)\n          );\n        });\n    }\n\n    function getPreviousRow(\n      year,\n      month,\n      division\n    ){\n\n      var rows =\n        getDivisionRowsSorted(\n          division\n        );\n\n      var index =\n        rows.findIndex(\n          function(row){\n\n            return (\n              rowYear(row) === year &&\n              rowMonth(row) === month\n            );\n          }\n        );\n\n      if(index <= 0){\n        return null;\n      }\n\n      return rows[index-1];\n    }\n\n\n    \/* =========================================================\n       FILTERS\n    ========================================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('cpiinf-year')\n        .innerHTML =\n\n        years.map(\n          function(year){\n\n            return (\n              '<option value=\"' +\n              year +\n              '\">' +\n              year +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n      if(years.length){\n\n        el('cpiinf-year')\n          .value =\n          years[0];\n      }\n\n      populateMonthFilter();\n\n      var divisions =\n        getDivisions();\n\n      el('cpiinf-division')\n        .innerHTML =\n\n        divisions.map(\n          function(code){\n\n            return (\n              '<option value=\"' +\n              code +\n              '\">' +\n\n              (\n                code === 'overall'\n                  ? 'Keseluruhan'\n                  : (\n                      code +\n                      ' - ' +\n                      divisionLabel(code)\n                    )\n              ) +\n\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n      if(\n        divisions.indexOf(\n          'overall'\n        ) !== -1\n      ){\n\n        el('cpiinf-division')\n          .value =\n          'overall';\n      }\n    }\n\n    function populateMonthFilter(){\n\n      var year =\n        parseInt(\n          el('cpiinf-year').value,\n          10\n        );\n\n      var months =\n        monthsForYear(year);\n\n      var current =\n        parseInt(\n          el('cpiinf-month').value,\n          10\n        );\n\n      el('cpiinf-month')\n        .innerHTML =\n\n        months.map(\n          function(month){\n\n            return (\n              '<option value=\"' +\n              month +\n              '\">' +\n              monthName(month) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n      if(\n        current &&\n        months.indexOf(current) !== -1\n      ){\n\n        el('cpiinf-month')\n          .value =\n          current;\n\n      }else if(months.length){\n\n        el('cpiinf-month')\n          .value =\n          months[\n            months.length - 1\n          ];\n      }\n    }\n\n\n    \/* =========================================================\n       SUMMARY CARDS\n    ========================================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n      var current =\n        melakaRow(\n          s.year,\n          s.month,\n          s.division\n        );\n\n      el('cpiinf-stat-period')\n        .textContent =\n        fullPeriodLabel(\n          s.year,\n          s.month\n        );\n\n      el('cpiinf-stat-mom')\n        .textContent =\n        current &&\n        valid(\n          num(\n            current.inflation_mom\n          )\n        )\n          ? signedPct(\n              num(\n                current.inflation_mom\n              )\n            )\n          : '\u2014';\n\n      el('cpiinf-stat-yoy')\n        .textContent =\n        current &&\n        valid(\n          num(\n            current.inflation_yoy\n          )\n        )\n          ? signedPct(\n              num(\n                current.inflation_yoy\n              )\n            )\n          : '\u2014';\n\n      var divisions =\n        getDivisions()\n        .filter(function(code){\n          return code !== 'overall';\n        });\n\n      var highest =\n        divisions\n        .map(function(code){\n\n          var row =\n            melakaRow(\n              s.year,\n              s.month,\n              code\n            );\n\n          return {\n\n            code:code,\n\n            value:\n              row\n                ? num(\n                    row.inflation_yoy\n                  )\n                : null\n          };\n        })\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      if(highest.length){\n\n        el('cpiinf-stat-high')\n          .textContent =\n          signedPct(\n            highest[0].value\n          );\n\n        el('cpiinf-stat-high-name')\n          .textContent =\n          divisionLabel(\n            highest[0].code\n          );\n\n      }else{\n\n        el('cpiinf-stat-high')\n          .textContent =\n          '\u2014';\n\n        el('cpiinf-stat-high-name')\n          .textContent =\n          'Tiada data';\n      }\n\n      el('cpiinf-stats')\n        .style.display =\n        '';\n    }\n\n\n    \/* =========================================================\n       TOOLTIPS\n    ========================================================== *\/\n\n    function tooltipPercent(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              signedPct(\n                c.parsed.y\n              )\n            );\n          }\n        }\n      };\n    }\n\n    function tooltipHorizontalPercent(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              signedPct(\n                c.parsed.x\n              )\n            );\n          }\n        }\n      };\n    }\n\n    function tooltipPP(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' Perubahan: ' +\n              signedPP(\n                c.parsed.y\n              )\n            );\n          }\n        }\n      };\n    }\n\n    function tooltipHorizontalPP(){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' Perubahan: ' +\n              signedPP(\n                c.parsed.x\n              )\n            );\n          }\n        }\n      };\n    }\n\n\n    \/* =========================================================\n       TREND\n    ========================================================== *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        getDivisionRowsSorted(\n          s.division\n        );\n\n      var labels =\n        rows.map(\n          periodLabel\n        );\n\n      el('cpiinf-trend-sub')\n        .textContent =\n        'Melaka \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 ' +\n        metricLabel(\n          s.metric\n        );\n\n      destroyChart('trend');\n\n      charts.trend =\n        new Chart(\n          el('cpiinf-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:labels,\n\n              datasets:[{\n\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  rows.map(\n                    function(row){\n\n                      return num(\n                        row[s.metric]\n                      );\n                    }\n                  ),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:14\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n\n                      return (\n                        fmt(value,1) +\n                        '%'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('bothTrend');\n\n      charts.bothTrend =\n        new Chart(\n          el('cpiinf-chart-both-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:labels,\n\n              datasets:[\n                {\n                  label:\n                    'Inflasi Bulanan (MoM)',\n\n                  data:\n                    rows.map(\n                      function(row){\n\n                        return num(\n                          row.inflation_mom\n                        );\n                      }\n                    ),\n\n                  borderColor:'#7c3aed',\n                  tension:.3,\n                  borderWidth:2.5,\n                  pointRadius:2\n                },\n\n                {\n                  label:\n                    'Inflasi Tahunan (YoY)',\n\n                  data:\n                    rows.map(\n                      function(row){\n\n                        return num(\n                          row.inflation_yoy\n                        );\n                      }\n                    ),\n\n                  borderColor:'#dc2626',\n                  tension:.3,\n                  borderWidth:2.5,\n                  pointRadius:2\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n                    autoSkip:true,\n                    maxTicksLimit:14\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =========================================================\n       MONTHLY\n    ========================================================== *\/\n\n    function buildMonthlyCharts(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        melakaRows\n        .filter(function(row){\n\n          return (\n            rowYear(row) ===\n              s.year &&\n\n            String(\n              row.division\n            ) ===\n            String(\n              s.division\n            )\n          );\n        })\n        .sort(function(a,b){\n\n          return (\n            rowMonth(a) -\n            rowMonth(b)\n          );\n        });\n\n      el('cpiinf-monthly-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 ' +\n        metricLabel(\n          s.metric\n        );\n\n      destroyChart('monthly');\n\n      charts.monthly =\n        new Chart(\n          el('cpiinf-chart-monthly')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                rows.map(\n                  function(row){\n\n                    return monthName(\n                      rowMonth(row)\n                    );\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  rows.map(\n                    function(row){\n\n                      return num(\n                        row[s.metric]\n                      );\n                    }\n                  ),\n\n                backgroundColor:\n                  rows.map(\n                    function(row){\n\n                      return (\n                        rowMonth(row) ===\n                        s.month\n                      )\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.75)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('monthlyBoth');\n\n      charts.monthlyBoth =\n        new Chart(\n          el('cpiinf-chart-monthly-both')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                rows.map(\n                  function(row){\n\n                    return monthName(\n                      rowMonth(row)\n                    );\n                  }\n                ),\n\n              datasets:[\n                {\n                  label:\n                    'Inflasi Bulanan (MoM)',\n\n                  data:\n                    rows.map(\n                      function(row){\n\n                        return num(\n                          row.inflation_mom\n                        );\n                      }\n                    ),\n\n                  backgroundColor:\n                    'rgba(124,58,237,.78)',\n\n                  borderRadius:5\n                },\n\n                {\n                  label:\n                    'Inflasi Tahunan (YoY)',\n\n                  data:\n                    rows.map(\n                      function(row){\n\n                        return num(\n                          row.inflation_yoy\n                        );\n                      }\n                    ),\n\n                  backgroundColor:\n                    'rgba(220,38,38,.75)',\n\n                  borderRadius:5\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =========================================================\n       DIVISION\n    ========================================================== *\/\n\n    function buildDivisionCharts(){\n\n      var s =\n        getSelected();\n\n      var divisions =\n        getDivisions()\n        .filter(function(code){\n          return code !== 'overall';\n        });\n\n      var data =\n        divisions.map(\n          function(code){\n\n            var row =\n              melakaRow(\n                s.year,\n                s.month,\n                code\n              );\n\n            return {\n\n              code:code,\n\n              label:\n                divisionLabel(code),\n\n              value:\n                row\n                  ? num(\n                      row[s.metric]\n                    )\n                  : null\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('cpiinf-division-sub')\n        .textContent =\n        fullPeriodLabel(\n          s.year,\n          s.month\n        ) +\n        ' \u00b7 Melaka \u00b7 ' +\n        metricLabel(\n          s.metric\n        );\n\n      destroyChart('division');\n\n      charts.division =\n        new Chart(\n          el('cpiinf-chart-division')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n\n                    return (\n                      item.code +\n                      ' - ' +\n                      item.label\n                    );\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.value >= 0\n                        ? 'rgba(220,38,38,.78)'\n                        : 'rgba(22,163,74,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var both =\n        divisions.map(\n          function(code){\n\n            var row =\n              melakaRow(\n                s.year,\n                s.month,\n                code\n              );\n\n            return {\n\n              code:code,\n\n              label:\n                divisionLabel(code),\n\n              mom:\n                row\n                  ? num(\n                      row.inflation_mom\n                    )\n                  : null,\n\n              yoy:\n                row\n                  ? num(\n                      row.inflation_yoy\n                    )\n                  : null\n            };\n          }\n        )\n        .filter(\n          function(item){\n\n            return (\n              valid(item.mom) ||\n              valid(item.yoy)\n            );\n          }\n        );\n\n      destroyChart('divisionBoth');\n\n      charts.divisionBoth =\n        new Chart(\n          el('cpiinf-chart-division-both')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                both.map(\n                  function(item){\n\n                    return (\n                      item.code +\n                      ' - ' +\n                      item.label\n                    );\n                  }\n                ),\n\n              datasets:[\n                {\n                  label:\n                    'Inflasi Bulanan (MoM)',\n\n                  data:\n                    both.map(\n                      function(item){\n                        return item.mom;\n                      }\n                    ),\n\n                  backgroundColor:\n                    'rgba(124,58,237,.75)',\n\n                  borderRadius:4\n                },\n\n                {\n                  label:\n                    'Inflasi Tahunan (YoY)',\n\n                  data:\n                    both.map(\n                      function(item){\n                        return item.yoy;\n                      }\n                    ),\n\n                  backgroundColor:\n                    'rgba(220,38,38,.72)',\n\n                  borderRadius:4\n                }\n              ]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipHorizontalPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =========================================================\n       MOM VS YOY\n    ========================================================== *\/\n\n    function buildCompareCharts(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        getDivisionRowsSorted(\n          s.division\n        );\n\n      var labels =\n        rows.map(\n          periodLabel\n        );\n\n      el('cpiinf-mom-sub')\n        .textContent =\n        'Melaka \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 perubahan harga berbanding bulan sebelumnya';\n\n      el('cpiinf-yoy-sub')\n        .textContent =\n        'Melaka \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 perubahan harga berbanding bulan sama tahun sebelumnya';\n\n      destroyChart('mom');\n\n      charts.mom =\n        new Chart(\n          el('cpiinf-chart-mom')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:labels,\n\n              datasets:[{\n\n                label:\n                  'Inflasi Bulanan (MoM)',\n\n                data:\n                  rows.map(\n                    function(row){\n\n                      return num(\n                        row.inflation_mom\n                      );\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    autoSkip:true,\n                    maxTicksLimit:12,\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('yoy');\n\n      charts.yoy =\n        new Chart(\n          el('cpiinf-chart-yoy')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:labels,\n\n              datasets:[{\n\n                label:\n                  'Inflasi Tahunan (YoY)',\n\n                data:\n                  rows.map(\n                    function(row){\n\n                      return num(\n                        row.inflation_yoy\n                      );\n                    }\n                  ),\n\n                borderColor:'#dc2626',\n                backgroundColor:'rgba(220,38,38,.12)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    autoSkip:true,\n                    maxTicksLimit:12,\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =========================================================\n       SEASONAL\n    ========================================================== *\/\n\n    function averageByMonth(\n      division,\n      metric\n    ){\n\n      var result = [];\n\n      for(\n        var month=1;\n        month<=12;\n        month++\n      ){\n\n        var values =\n          melakaRows\n          .filter(function(row){\n\n            return (\n              rowMonth(row) === month &#038;&#038;\n              String(\n                row.division\n              ) ===\n              String(\n                division\n              )\n            );\n          })\n          .map(function(row){\n\n            return num(\n              row[metric]\n            );\n          })\n          .filter(function(value){\n\n            return valid(value);\n          });\n\n        result.push(\n\n          values.length\n            ? (\n                values.reduce(\n                  function(a,b){\n                    return a+b;\n                  },\n                  0\n                ) \/\n                values.length\n              )\n            : null\n        );\n      }\n\n      return result;\n    }\n\n    function buildSeasonalCharts(){\n\n      var s =\n        getSelected();\n\n      var labels = [\n        'Januari',\n        'Februari',\n        'Mac',\n        'April',\n        'Mei',\n        'Jun',\n        'Julai',\n        'Ogos',\n        'September',\n        'Oktober',\n        'November',\n        'Disember'\n      ];\n\n      el('cpiinf-seasonal-mom-sub')\n        .textContent =\n        'Purata MoM \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 sepanjang tempoh data';\n\n      el('cpiinf-seasonal-yoy-sub')\n        .textContent =\n        'Purata YoY \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 sepanjang tempoh data';\n\n      destroyChart('seasonalMom');\n\n      charts.seasonalMom =\n        new Chart(\n          el('cpiinf-chart-seasonal-mom')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:labels,\n\n              datasets:[{\n\n                label:\n                  'Purata Inflasi Bulanan',\n\n                data:\n                  averageByMonth(\n                    s.division,\n                    'inflation_mom'\n                  ),\n\n                backgroundColor:\n                  'rgba(124,58,237,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('seasonalYoy');\n\n      charts.seasonalYoy =\n        new Chart(\n          el('cpiinf-chart-seasonal-yoy')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:labels,\n\n              datasets:[{\n\n                label:\n                  'Purata Inflasi Tahunan',\n\n                data:\n                  averageByMonth(\n                    s.division,\n                    'inflation_yoy'\n                  ),\n\n                backgroundColor:\n                  'rgba(220,38,38,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =========================================================\n       CHANGE \/ INFLATION MOMENTUM\n    ========================================================== *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n      var divisions =\n        getDivisions()\n        .filter(function(code){\n          return code !== 'overall';\n        });\n\n      \/*\n        Here \"change\" means:\n        current inflation rate minus\n        previous month's inflation rate.\n\n        Unit = percentage points.\n      *\/\n\n      var data =\n        divisions.map(\n          function(code){\n\n            var current =\n              melakaRow(\n                s.year,\n                s.month,\n                code\n              );\n\n            var previous =\n              getPreviousRow(\n                s.year,\n                s.month,\n                code\n              );\n\n            var now =\n              current\n                ? num(\n                    current[s.metric]\n                  )\n                : null;\n\n            var before =\n              previous\n                ? num(\n                    previous[s.metric]\n                  )\n                : null;\n\n            return {\n\n              code:code,\n\n              label:\n                divisionLabel(\n                  code\n                ),\n\n              change:\n                valid(now) &#038;&#038;\n                valid(before)\n                  ? now-before\n                  : null\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.change);\n        })\n        .sort(function(a,b){\n          return b.change-a.change;\n        });\n\n      var currentRow =\n        melakaRow(\n          s.year,\n          s.month,\n          s.division\n        );\n\n      var previousRow =\n        getPreviousRow(\n          s.year,\n          s.month,\n          s.division\n        );\n\n      el('cpiinf-change-sub')\n        .textContent =\n\n        previousRow\n          ? (\n              fullPeriodLabel(\n                s.year,\n                s.month\n              ) +\n              ' berbanding ' +\n              fullPeriodLabel(\n                rowYear(previousRow),\n                rowMonth(previousRow)\n              ) +\n              ' \u00b7 ' +\n              metricLabel(\n                s.metric\n              )\n            )\n          : (\n              fullPeriodLabel(\n                s.year,\n                s.month\n              ) +\n              ' \u00b7 tiada bulan sebelumnya'\n            );\n\n      destroyChart('change');\n\n      charts.change =\n        new Chart(\n          el('cpiinf-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n\n                    return (\n                      item.code +\n                      ' - ' +\n                      item.label\n                    );\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Kadar Inflasi',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.change;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.change >= 0\n                        ? 'rgba(220,38,38,.78)'\n                        : 'rgba(22,163,74,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalPP()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n\n                      return (\n                        fmt(value,1) +\n                        ' pp'\n                      );\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var rows =\n        getDivisionRowsSorted(\n          s.division\n        );\n\n      var trend = [];\n\n      rows.forEach(\n        function(row,index){\n\n          if(index === 0){\n            return;\n          }\n\n          var now =\n            num(\n              row[s.metric]\n            );\n\n          var before =\n            num(\n              rows[index-1][\n                s.metric\n              ]\n            );\n\n          trend.push({\n\n            label:\n              periodLabel(row),\n\n            change:\n              valid(now) &&\n              valid(before)\n                ? now-before\n                : null\n          });\n        }\n      );\n\n      el('cpiinf-change-trend-sub')\n        .textContent =\n        'Momentum ' +\n        metricLabel(\n          s.metric\n        ) +\n        ' \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 perubahan berbanding bulan sebelumnya';\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend =\n        new Chart(\n          el('cpiinf-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:\n                trend.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Kadar Inflasi',\n\n                data:\n                  trend.map(\n                    function(item){\n                      return item.change;\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.3,\n                borderWidth:2.5,\n                pointRadius:2\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipPP()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    autoSkip:true,\n                    maxTicksLimit:12,\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n\n                      return (\n                        fmt(value,1) +\n                        ' pp'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =========================================================\n       STATE\n    ========================================================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n      var states =\n        getStates();\n\n      var data =\n        states.map(\n          function(state){\n\n            var row =\n              rowFor(\n                allRows,\n                state,\n                s.year,\n                s.month,\n                s.division\n              );\n\n            return {\n\n              state:state,\n\n              value:\n                row\n                  ? num(\n                      row[s.metric]\n                    )\n                  : null\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('cpiinf-state-sub')\n        .textContent =\n        fullPeriodLabel(\n          s.year,\n          s.month\n        ) +\n        ' \u00b7 semua negeri \u00b7 ' +\n        divisionLabel(\n          s.division\n        ) +\n        ' \u00b7 ' +\n        metricLabel(\n          s.metric\n        );\n\n      destroyChart('state');\n\n      charts.state =\n        new Chart(\n          el('cpiinf-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 'rgba(249,115,22,.95)'\n                        : (\n                            item.value >= 0\n                              ? 'rgba(37,99,235,.70)'\n                              : 'rgba(22,163,74,.68)'\n                          );\n                    }\n                  ),\n\n                borderColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label ===\n                          'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n      var both =\n        states.map(\n          function(state){\n\n            var row =\n              rowFor(\n                allRows,\n                state,\n                s.year,\n                s.month,\n                s.division\n              );\n\n            return {\n\n              state:state,\n\n              mom:\n                row\n                  ? num(\n                      row.inflation_mom\n                    )\n                  : null,\n\n              yoy:\n                row\n                  ? num(\n                      row.inflation_yoy\n                    )\n                  : null\n            };\n          }\n        )\n        .filter(\n          function(item){\n\n            return (\n              valid(item.mom) ||\n              valid(item.yoy)\n            );\n          }\n        );\n\n      destroyChart('stateBoth');\n\n      charts.stateBoth =\n        new Chart(\n          el('cpiinf-chart-state-both')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                both.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[\n                {\n                  label:\n                    'Inflasi Bulanan (MoM)',\n\n                  data:\n                    both.map(\n                      function(item){\n                        return item.mom;\n                      }\n                    ),\n\n                  backgroundColor:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? 'rgba(249,115,22,.95)'\n                          : 'rgba(124,58,237,.68)';\n                      }\n                    ),\n\n                  borderColor:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? '#f97316'\n                          : 'transparent';\n                      }\n                    ),\n\n                  borderWidth:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? 2\n                          : 0;\n                      }\n                    ),\n\n                  borderRadius:5\n                },\n\n                {\n                  label:\n                    'Inflasi Tahunan (YoY)',\n\n                  data:\n                    both.map(\n                      function(item){\n                        return item.yoy;\n                      }\n                    ),\n\n                  backgroundColor:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? 'rgba(249,115,22,.65)'\n                          : 'rgba(37,99,235,.68)';\n                      }\n                    ),\n\n                  borderColor:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? '#f97316'\n                          : 'transparent';\n                      }\n                    ),\n\n                  borderWidth:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? 2\n                          : 0;\n                      }\n                    ),\n\n                  borderRadius:5\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipPercent()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =========================================================\n       ACTIVE TAB \/ REDRAW\n    ========================================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#cpiinf-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      var tab =\n        activeTab();\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'monthly'){\n        buildMonthlyCharts();\n      }\n\n      if(tab === 'division'){\n        buildDivisionCharts();\n      }\n\n      if(tab === 'compare'){\n        buildCompareCharts();\n      }\n\n      if(tab === 'seasonal'){\n        buildSeasonalCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n\n    \/* =========================================================\n       TAB EVENTS\n    ========================================================== *\/\n\n    document\n      .querySelectorAll(\n        '#cpiinf-wrap .tbtn'\n      )\n      .forEach(\n        function(btn){\n\n          btn.addEventListener(\n            'click',\n            function(){\n\n              document\n                .querySelectorAll(\n                  '#cpiinf-wrap .tbtn'\n                )\n                .forEach(\n                  function(button){\n\n                    button.classList\n                      .remove(\n                        'active'\n                      );\n                  }\n                );\n\n              btn.classList\n                .add(\n                  'active'\n                );\n\n              [\n                'trend',\n                'monthly',\n                'division',\n                'compare',\n                'seasonal',\n                'change',\n                'state'\n              ]\n              .forEach(\n                function(tab){\n\n                  el(\n                    'cpiinf-tab-' +\n                    tab\n                  ).style.display =\n                    'none';\n                }\n              );\n\n              el(\n                'cpiinf-tab-' +\n                btn.getAttribute(\n                  'data-tab'\n                )\n              ).style.display =\n                '';\n\n              redraw();\n            }\n          );\n        }\n      );\n\n\n    \/* =========================================================\n       FILTER EVENTS\n    ========================================================== *\/\n\n    el('cpiinf-year')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateMonthFilter();\n          redraw();\n        }\n      );\n\n    [\n      'cpiinf-month',\n      'cpiinf-division',\n      'cpiinf-metric'\n    ]\n    .forEach(\n      function(id){\n\n        el(id)\n          .addEventListener(\n            'change',\n            redraw\n          );\n      }\n    );\n\n\n    \/* =========================================================\n       FETCH API\n    ========================================================== *\/\n\n    fetch(API_URL)\n\n      .then(\n        function(response){\n\n          if(!response.ok){\n\n            throw new Error(\n              'HTTP ' +\n              response.status\n            );\n          }\n\n          return response.json();\n        }\n      )\n\n      .then(\n        function(data){\n\n          allRows =\n            Array.isArray(data)\n              ? data\n              : [];\n\n          melakaRows =\n            allRows.filter(\n              function(row){\n\n                return (\n                  String(\n                    row.state || ''\n                  )\n                  .trim()\n                  .toLowerCase()\n                  ===\n                  'melaka'\n                );\n              }\n            );\n\n          if(!melakaRows.length){\n\n            throw new Error(\n              'Tiada rekod inflasi CPI Melaka ditemui.'\n            );\n          }\n\n          populateFilters();\n\n          el('cpiinf-loading')\n            .style.display =\n            'none';\n\n          el('cpiinf-area')\n            .style.display =\n            '';\n\n          redraw();\n        }\n      )\n\n      .catch(\n        function(error){\n\n          el('cpiinf-loading')\n            .innerHTML =\n\n            '<div style=\"' +\n            'color:#dc2626;' +\n            'font-size:16px;' +\n            'font-weight:800;' +\n            'margin-bottom:8px' +\n            '\">' +\n\n            'Gagal memuatkan data' +\n\n            '<\/div>' +\n\n            '<p style=\"' +\n            'font-size:13px;' +\n            'color:#64748b' +\n            '\">' +\n\n            error.message +\n\n            '<\/p>';\n        }\n      );\n\n  }\n\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<div style=\"text-align: center; margin: 40px 0 30px 0;\">\n  <h2 style=\"\n    font-size: 32px;\n    font-weight: 700;\n    color: #1f3c88;\n    margin-bottom: 10px;\n    letter-spacing: 0.5px;\n  \">\n    Dashboard Kategori Ekonomi\n  <\/h2>\n\n  <div style=\"\n    width: 80px;\n    height: 4px;\n    background-color: #1f3c88;\n    margin: 0 auto;\n    border-radius: 4px;\n  \"><\/div>\n<\/div>\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#crop-wrap, #crop-wrap * { box-sizing:border-box; }\n\n#crop-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#crop-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#crop-wrap .hdr,\n#crop-wrap .cc,\n#crop-wrap .sc,\n#crop-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#crop-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#crop-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#crop-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#crop-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#crop-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(3,1fr);\n  gap:14px;\n  background:#fff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#crop-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#crop-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#crop-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#fff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#crop-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#crop-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#crop-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#crop-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#crop-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#crop-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n  line-height:1.35;\n}\n\n#crop-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#crop-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#crop-wrap .tbtn.active{\n  background:#fff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#crop-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#crop-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#crop-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n  line-height:1.45;\n}\n\n#crop-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#crop-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#crop-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#crop-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#crop-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:crop-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes crop-spin{\n  to{transform:rotate(360deg);}\n}\n\n@media(max-width:1024px){\n\n  #crop-wrap .filter-bar,\n  #crop-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #crop-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #crop-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #crop-wrap .filter-bar,\n  #crop-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #crop-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #crop-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #crop-wrap .chart-box,\n  #crop-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n<div id=\"crop-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Tanaman Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Pengeluaran dan Keluasan Tanaman Mengikut Negeri\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi pengeluaran dan keluasan tanaman di Melaka mengikut tahun\n        dan jenis tanaman. Dashboard turut menunjukkan hasil pengeluaran bagi\n        setiap hektar, perubahan dari tahun ke tahun dan perbandingan Melaka\n        dengan negeri-negeri lain.\n      <\/p>\n\n    <\/div>\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"crop-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Jenis Tanaman<\/label>\n        <select id=\"crop-type\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Ukuran<\/label>\n\n        <select id=\"crop-metric\">\n          <option value=\"production\">\n            Pengeluaran\n          <\/option>\n\n          <option value=\"planted_area\">\n            Keluasan Tanaman\n          <\/option>\n\n          <option value=\"yield\">\n            Hasil per Hektar\n          <\/option>\n        <\/select>\n      <\/div>\n\n    <\/div>\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"crop-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tahun Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"crop-stat-year\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Jumlah Pengeluaran\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"crop-stat-production\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          tan metrik\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Keluasan Tanaman\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"crop-stat-area\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          hektar\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Hasil per Hektar\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"crop-stat-yield\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          tan metrik \/ hektar\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"crop-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod tanaman Melaka.\n      <\/div>\n\n    <\/div>\n\n    <!-- DASHBOARD -->\n    <div\n      id=\"crop-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TABS -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"type\"\n        >\n          Jenis Tanaman\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"production\"\n        >\n          Pengeluaran &#038; Keluasan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"yield\"\n        >\n          Hasil per Hektar\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"topbottom\"\n        >\n          Tertinggi &#038; Terendah\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n      <\/div>\n\n\n      <!-- ==========================\n           TREND\n      =========================== -->\n      <div id=\"crop-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Ukuran Tanaman Mengikut Masa\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"crop-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"crop-chart-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Pengeluaran dan Keluasan\n            <\/div>\n\n            <div class=\"cs\">\n              Perbandingan perubahan pengeluaran dan keluasan tanaman\n              mengikut tahun.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"crop-chart-prod-area-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================\n           CROP TYPE\n      =========================== -->\n      <div\n        id=\"crop-tab-type\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Mengikut Jenis Tanaman\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"crop-type-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"crop-chart-type\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Sumbangan Pengeluaran Mengikut Jenis Tanaman\n          <\/div>\n\n          <div class=\"cs\">\n            Peratus sumbangan setiap jenis tanaman kepada jumlah\n            pengeluaran tanaman Melaka.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"crop-chart-share\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================\n           PRODUCTION + AREA\n      =========================== -->\n      <div\n        id=\"crop-tab-production\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pengeluaran Mengikut Jenis Tanaman\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"crop-production-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"crop-chart-production\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Keluasan Mengikut Jenis Tanaman\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"crop-area-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"crop-chart-area\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================\n           YIELD\n      =========================== -->\n      <div\n        id=\"crop-tab-yield\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Hasil Pengeluaran per Hektar Mengikut Jenis Tanaman\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"crop-yield-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"crop-chart-yield\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Trend Hasil Pengeluaran per Hektar\n          <\/div>\n\n          <div class=\"cs\">\n            Nisbah pengeluaran kepada keluasan tanaman bagi setiap tahun.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"crop-chart-yield-trend\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================\n           TOP BOTTOM\n      =========================== -->\n      <div\n        id=\"crop-tab-topbottom\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Jenis Tanaman Tertinggi\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"crop-top-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"crop-chart-top\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Jenis Tanaman Terendah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"crop-bottom-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"crop-chart-bottom\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================\n           CHANGE\n      =========================== -->\n      <div\n        id=\"crop-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Mengikut Jenis Tanaman\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"crop-change-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"crop-chart-change\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Perubahan Tahunan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"crop-change-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"crop-chart-change-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================\n           STATE\n      =========================== -->\n      <div\n        id=\"crop-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"crop-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"crop-chart-state\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Pengeluaran dan Keluasan Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan pengeluaran dan keluasan tanaman bagi negeri-negeri\n            yang mempunyai rekod. Melaka ditonjolkan dalam warna oren.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"crop-chart-state-both\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=crops_state&limit=1000';\n\n    var FIXED_STATE =\n      'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n    Chart.defaults.color =\n      '#64748b';\n\n    Chart.defaults.borderColor =\n      '#dbeafe';\n\n    Chart.defaults.font.family =\n      'Arial, sans-serif';\n\n\n    \/* =====================================================\n       LABEL TANAMAN\n    ====================================================== *\/\n\n    var CROP_LABELS = {\n\n      cash_crops:\n        'Tanaman Kontan',\n\n      industrial_crops:\n        'Tanaman Industri',\n\n      coconut:\n        'Kelapa',\n\n      paddy:\n        'Padi',\n\n      flower:\n        'Bunga',\n\n      flowers:\n        'Bunga',\n\n      herb:\n        'Herba',\n\n      herbs:\n        'Herba',\n\n      spice:\n        'Rempah-ratus',\n\n      spices:\n        'Rempah-ratus',\n\n      fruit:\n        'Buah-buahan',\n\n      fruits:\n        'Buah-buahan',\n\n      vegetable:\n        'Sayur-sayuran',\n\n      vegetables:\n        'Sayur-sayuran'\n    };\n\n\n    \/* =====================================================\n       BASIC HELPERS\n    ====================================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(row){\n\n      return parseInt(\n        String(\n          row.date || ''\n        ).slice(0,4),\n        10\n      );\n    }\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === ''\n      ){\n        return null;\n      }\n\n      var n =\n        Number(value);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n    function valid(value){\n\n      return (\n        value !== null &&\n        value !== undefined &&\n        !isNaN(value)\n      );\n    }\n\n    function fmt(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n      return Number(value)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:d,\n            maximumFractionDigits:d\n          }\n        );\n    }\n\n    function signed(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(\n          value,\n          digits === undefined\n            ? 1\n            : digits\n        )\n      );\n    }\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,1) +\n        '%'\n      );\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n    function cropLabel(type){\n\n      if(type === 'all'){\n        return 'Semua Jenis Tanaman';\n      }\n\n      if(\n        CROP_LABELS[type]\n      ){\n        return CROP_LABELS[type];\n      }\n\n      return String(type || '')\n        .replace(\/_\/g,' ')\n        .replace(\n          \/\\b\\w\/g,\n          function(c){\n            return c.toUpperCase();\n          }\n        );\n    }\n\n    function metricLabel(metric){\n\n      if(metric === 'production'){\n        return 'Pengeluaran';\n      }\n\n      if(metric === 'planted_area'){\n        return 'Keluasan Tanaman';\n      }\n\n      if(metric === 'yield'){\n        return 'Hasil per Hektar';\n      }\n\n      return metric;\n    }\n\n    function metricUnit(metric){\n\n      if(metric === 'production'){\n        return 'tan metrik';\n      }\n\n      if(metric === 'planted_area'){\n        return 'hektar';\n      }\n\n      if(metric === 'yield'){\n        return 'tan metrik\/hektar';\n      }\n\n      return '';\n    }\n\n    function percentChange(\n      current,\n      previous\n    ){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n      return (\n        (\n          current -\n          previous\n        ) \/\n        previous\n      ) * 100;\n    }\n\n\n    \/* =====================================================\n       DATA HELPERS\n    ====================================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(yr)\n            .filter(function(year){\n              return !isNaN(year);\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n    function getPreviousYear(current){\n\n      var years =\n        availableYears()\n        .filter(function(year){\n          return year < current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n    function getCropTypes(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(function(row){\n              return row.crop_type;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort(\n        function(a,b){\n\n          return cropLabel(a)\n            .localeCompare(\n              cropLabel(b),\n              'ms'\n            );\n        }\n      );\n    }\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(function(state){\n\n              return (\n                state &#038;&#038;\n                state !== 'Malaysia'\n              );\n            })\n        )\n      )\n      .sort();\n    }\n\n    function getSelected(){\n\n      return {\n\n        year:\n          parseInt(\n            el('crop-year').value,\n            10\n          ),\n\n        crop:\n          el('crop-type').value,\n\n        metric:\n          el('crop-metric').value\n      };\n    }\n\n    function rowsFor(\n      source,\n      state,\n      year,\n      crop\n    ){\n\n      return source.filter(\n        function(row){\n\n          return (\n            row.state === state &#038;&#038;\n            yr(row) === year &#038;&#038;\n            (\n              crop === 'all' ||\n              row.crop_type === crop\n            )\n          );\n        }\n      );\n    }\n\n    function totalProduction(rows){\n\n      var values =\n        rows\n        .map(function(row){\n          return num(row.production);\n        })\n        .filter(valid);\n\n      if(!values.length){\n        return null;\n      }\n\n      return values.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      );\n    }\n\n    function totalArea(rows){\n\n      var values =\n        rows\n        .map(function(row){\n          return num(row.planted_area);\n        })\n        .filter(valid);\n\n      if(!values.length){\n        return null;\n      }\n\n      return values.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      );\n    }\n\n    \/*\n      Hasil per hektar untuk \"Semua Jenis Tanaman\"\n      dikira sebagai:\n\n      jumlah pengeluaran \/ jumlah keluasan\n\n      bukan purata mudah semua nisbah tanaman.\n    *\/\n\n    function yieldValue(rows){\n\n      var production =\n        totalProduction(rows);\n\n      var area =\n        totalArea(rows);\n\n      if(\n        !valid(production) ||\n        !valid(area) ||\n        area === 0\n      ){\n        return null;\n      }\n\n      return production \/ area;\n    }\n\n    function metricValue(\n      rows,\n      metric\n    ){\n\n      if(metric === 'production'){\n        return totalProduction(rows);\n      }\n\n      if(metric === 'planted_area'){\n        return totalArea(rows);\n      }\n\n      if(metric === 'yield'){\n        return yieldValue(rows);\n      }\n\n      return null;\n    }\n\n    function melakaMetric(\n      year,\n      crop,\n      metric\n    ){\n\n      return metricValue(\n        rowsFor(\n          melakaRows,\n          FIXED_STATE,\n          year,\n          crop\n        ),\n        metric\n      );\n    }\n\n    function stateMetric(\n      state,\n      year,\n      crop,\n      metric\n    ){\n\n      return metricValue(\n        rowsFor(\n          allRows,\n          state,\n          year,\n          crop\n        ),\n        metric\n      );\n    }\n\n\n    \/* =====================================================\n       FILTER\n    ====================================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('crop-year')\n        .innerHTML =\n\n        years.map(\n          function(year){\n\n            return (\n              '<option value=\"' +\n              year +\n              '\">' +\n              year +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n      if(years.length){\n\n        el('crop-year')\n          .value =\n          years[0];\n      }\n\n      var crops =\n        getCropTypes();\n\n      el('crop-type')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Jenis Tanaman' +\n        '<\/option>' +\n\n        crops.map(\n          function(type){\n\n            return (\n              '<option value=\"' +\n              type +\n              '\">' +\n              cropLabel(type) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n    }\n\n\n    \/* =====================================================\n       SUMMARY\n    ====================================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n      var rows =\n        rowsFor(\n          melakaRows,\n          FIXED_STATE,\n          s.year,\n          s.crop\n        );\n\n      var production =\n        totalProduction(rows);\n\n      var area =\n        totalArea(rows);\n\n      var yieldResult =\n        yieldValue(rows);\n\n      el('crop-stat-year')\n        .textContent =\n        s.year || '\u2014';\n\n      el('crop-stat-production')\n        .textContent =\n        valid(production)\n          ? fmt(production,1)\n          : '\u2014';\n\n      el('crop-stat-area')\n        .textContent =\n        valid(area)\n          ? fmt(area,1)\n          : '\u2014';\n\n      el('crop-stat-yield')\n        .textContent =\n        valid(yieldResult)\n          ? fmt(yieldResult,2)\n          : '\u2014';\n\n      el('crop-stats')\n        .style.display =\n        '';\n    }\n\n\n    \/* =====================================================\n       TOOLTIP\n    ====================================================== *\/\n\n    function tooltipMetric(metric){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.y,\n                metric === 'yield'\n                  ? 2\n                  : 1\n              ) +\n              ' ' +\n              metricUnit(metric)\n            );\n          }\n        }\n      };\n    }\n\n    function tooltipHorizontalMetric(\n      metric\n    ){\n\n      return {\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.x,\n                metric === 'yield'\n                  ? 2\n                  : 1\n              ) +\n              ' ' +\n              metricUnit(metric)\n            );\n          }\n        }\n      };\n    }\n\n\n    \/* =====================================================\n       TREND\n    ====================================================== *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var years =\n        availableYears();\n\n      el('crop-trend-sub')\n        .textContent =\n        'Melaka \u00b7 ' +\n        cropLabel(s.crop) +\n        ' \u00b7 ' +\n        metricLabel(s.metric);\n\n      destroyChart('trend');\n\n      charts.trend =\n        new Chart(\n          el('crop-chart-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  years.map(\n                    function(year){\n\n                      return melakaMetric(\n                        year,\n                        s.crop,\n                        s.metric\n                      );\n                    }\n                  ),\n\n                borderColor:'#2563eb',\n                backgroundColor:'rgba(37,99,235,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipMetric(\n                    s.metric\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n\n                      return fmt(\n                        value,\n                        s.metric === 'yield'\n                          ? 2\n                          : 1\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/*\n        Mixed axis chart:\n        production = metric tonnes\n        area = hectares\n      *\/\n\n      destroyChart('prodAreaTrend');\n\n      charts.prodAreaTrend =\n        new Chart(\n          el('crop-chart-prod-area-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[\n                {\n                  label:'Pengeluaran',\n\n                  data:\n                    years.map(\n                      function(year){\n\n                        return melakaMetric(\n                          year,\n                          s.crop,\n                          'production'\n                        );\n                      }\n                    ),\n\n                  borderColor:'#16a34a',\n                  backgroundColor:'rgba(22,163,74,.10)',\n                  yAxisID:'y',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                },\n\n                {\n                  label:'Keluasan Tanaman',\n\n                  data:\n                    years.map(\n                      function(year){\n\n                        return melakaMetric(\n                          year,\n                          s.crop,\n                          'planted_area'\n                        );\n                      }\n                    ),\n\n                  borderColor:'#f97316',\n                  backgroundColor:'rgba(249,115,22,.10)',\n                  yAxisID:'y1',\n                  tension:.35,\n                  borderWidth:2.5,\n                  pointRadius:3\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      if(\n                        c.dataset.yAxisID === 'y'\n                      ){\n\n                        return (\n                          ' Pengeluaran: ' +\n                          fmt(c.parsed.y,1) +\n                          ' tan metrik'\n                        );\n                      }\n\n                      return (\n                        ' Keluasan: ' +\n                        fmt(c.parsed.y,1) +\n                        ' hektar'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  type:'linear',\n                  position:'left',\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  title:{\n                    display:true,\n                    text:'Pengeluaran (tan metrik)'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y1:{\n                  type:'linear',\n                  position:'right',\n                  beginAtZero:true,\n\n                  grid:{\n                    drawOnChartArea:false\n                  },\n\n                  title:{\n                    display:true,\n                    text:'Keluasan (hektar)'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       CROP TYPE\n    ====================================================== *\/\n\n    function buildTypeCharts(){\n\n      var s =\n        getSelected();\n\n      var cropTypes =\n        getCropTypes();\n\n      var data =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              type:type,\n\n              label:\n                cropLabel(type),\n\n              value:\n                melakaMetric(\n                  s.year,\n                  type,\n                  s.metric\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('crop-type-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 Melaka \u00b7 ' +\n        metricLabel(\n          s.metric\n        );\n\n      destroyChart('type');\n\n      charts.type =\n        new Chart(\n          el('crop-chart-type')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(37,99,235,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalMetric(\n                    s.metric\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/* Production share *\/\n\n      var productionData =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              label:\n                cropLabel(type),\n\n              value:\n                melakaMetric(\n                  s.year,\n                  type,\n                  'production'\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      destroyChart('share');\n\n      charts.share =\n        new Chart(\n          el('crop-chart-share')\n            .getContext('2d'),\n          {\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                productionData.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran',\n\n                data:\n                  productionData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#16a34a',\n                  '#f97316',\n                  '#7c3aed',\n                  '#0891b2',\n                  '#dc2626',\n                  '#64748b',\n                  '#0d9488',\n                  '#9333ea'\n                ],\n\n                borderColor:'#ffffff',\n                borderWidth:3\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' tan metrik (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       PRODUCTION + AREA\n    ====================================================== *\/\n\n    function buildProductionCharts(){\n\n      var s =\n        getSelected();\n\n      var cropTypes =\n        getCropTypes();\n\n      var production =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              label:\n                cropLabel(type),\n\n              value:\n                melakaMetric(\n                  s.year,\n                  type,\n                  'production'\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      var area =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              label:\n                cropLabel(type),\n\n              value:\n                melakaMetric(\n                  s.year,\n                  type,\n                  'planted_area'\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('crop-production-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 Melaka \u00b7 jumlah pengeluaran';\n\n      el('crop-area-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 Melaka \u00b7 keluasan tanaman';\n\n      destroyChart('production');\n\n      charts.production =\n        new Chart(\n          el('crop-chart-production')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                production.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:'Pengeluaran',\n\n                data:\n                  production.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(22,163,74,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalMetric(\n                    'production'\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{display:false}\n                }\n              }\n            }\n          }\n        );\n\n      destroyChart('area');\n\n      charts.area =\n        new Chart(\n          el('crop-chart-area')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                area.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:'Keluasan Tanaman',\n\n                data:\n                  area.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(249,115,22,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalMetric(\n                    'planted_area'\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{display:false}\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       YIELD\n    ====================================================== *\/\n\n    function buildYieldCharts(){\n\n      var s =\n        getSelected();\n\n      var cropTypes =\n        getCropTypes();\n\n      var data =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              label:\n                cropLabel(type),\n\n              value:\n                melakaMetric(\n                  s.year,\n                  type,\n                  'yield'\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('crop-yield-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 Melaka \u00b7 pengeluaran \u00f7 keluasan tanaman';\n\n      destroyChart('yield');\n\n      charts.yield =\n        new Chart(\n          el('crop-chart-yield')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Hasil per Hektar',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(124,58,237,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalMetric(\n                    'yield'\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,2);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{display:false}\n                }\n              }\n            }\n          }\n        );\n\n\n      var years =\n        availableYears();\n\n      destroyChart('yieldTrend');\n\n      charts.yieldTrend =\n        new Chart(\n          el('crop-chart-yield-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Hasil per Hektar',\n\n                data:\n                  years.map(\n                    function(year){\n\n                      return melakaMetric(\n                        year,\n                        s.crop,\n                        'yield'\n                      );\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipMetric(\n                    'yield'\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  grid:{color:'#dbeafe'}\n                },\n\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,2);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TOP & BOTTOM\n    ====================================================== *\/\n\n    function buildTopBottomCharts(){\n\n      var s =\n        getSelected();\n\n      var cropTypes =\n        getCropTypes();\n\n      var data =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              label:\n                cropLabel(type),\n\n              value:\n                melakaMetric(\n                  s.year,\n                  type,\n                  s.metric\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      var limit =\n        Math.min(\n          5,\n          data.length\n        );\n\n      var top =\n        data.slice(\n          0,\n          limit\n        );\n\n      var bottom =\n        data.slice(\n          -limit\n        )\n        .reverse();\n\n      el('crop-top-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 ' +\n        metricLabel(s.metric) +\n        ' tertinggi';\n\n      el('crop-bottom-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 ' +\n        metricLabel(s.metric) +\n        ' terendah';\n\n      function makeChart(\n        id,\n        name,\n        dataset,\n        color\n      ){\n\n        destroyChart(name);\n\n        charts[name] =\n          new Chart(\n            el(id)\n              .getContext('2d'),\n            {\n              type:'bar',\n\n              data:{\n\n                labels:\n                  dataset.map(\n                    function(item){\n                      return item.label;\n                    }\n                  ),\n\n                datasets:[{\n\n                  label:\n                    metricLabel(\n                      s.metric\n                    ),\n\n                  data:\n                    dataset.map(\n                      function(item){\n                        return item.value;\n                      }\n                    ),\n\n                  backgroundColor:\n                    color,\n\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n                responsive:true,\n                maintainAspectRatio:false,\n\n                plugins:{\n\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipMetric(\n                      s.metric\n                    )\n                },\n\n                scales:{\n\n                  x:{\n                    grid:{display:false},\n                    ticks:{maxRotation:45}\n                  },\n\n                  y:{\n                    beginAtZero:true,\n                    grid:{color:'#dbeafe'},\n\n                    ticks:{\n                      callback:function(value){\n\n                        return fmt(\n                          value,\n                          s.metric === 'yield'\n                            ? 2\n                            : 0\n                        );\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n      }\n\n      makeChart(\n        'crop-chart-top',\n        'top',\n        top,\n        'rgba(22,163,74,.78)'\n      );\n\n      makeChart(\n        'crop-chart-bottom',\n        'bottom',\n        bottom,\n        'rgba(220,38,38,.78)'\n      );\n    }\n\n\n    \/* =====================================================\n       CHANGE\n    ====================================================== *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n      if(\n        previousYear === null\n      ){\n\n        el('crop-change-sub')\n          .textContent =\n          s.year +\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('crop-change-trend-sub')\n          .textContent =\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n      var cropTypes =\n        getCropTypes();\n\n      var data =\n        cropTypes.map(\n          function(type){\n\n            var current =\n              melakaMetric(\n                s.year,\n                type,\n                s.metric\n              );\n\n            var previous =\n              melakaMetric(\n                previousYear,\n                type,\n                s.metric\n              );\n\n            return {\n\n              label:\n                cropLabel(type),\n\n              value:\n                valid(current) &&\n                valid(previous)\n                  ? percentChange(\n                      current,\n                      previous\n                    )\n                  : null\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('crop-change-sub')\n        .textContent =\n        s.year +\n        ' berbanding ' +\n        previousYear +\n        ' \u00b7 ' +\n        metricLabel(\n          s.metric\n        );\n\n      destroyChart('change');\n\n      charts.change =\n        new Chart(\n          el('crop-chart-change')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.value >= 0\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              indexAxis:'y',\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.x\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{display:false}\n                }\n              }\n            }\n          }\n        );\n\n\n      var years =\n        availableYears();\n\n      var trend = [];\n\n      years.forEach(\n        function(year,index){\n\n          if(index === 0){\n            return;\n          }\n\n          var previous =\n            years[index-1];\n\n          var currentValue =\n            melakaMetric(\n              year,\n              s.crop,\n              s.metric\n            );\n\n          var previousValue =\n            melakaMetric(\n              previous,\n              s.crop,\n              s.metric\n            );\n\n          if(\n            valid(currentValue) &&\n            valid(previousValue)\n          ){\n\n            trend.push({\n\n              label:\n                year +\n                ' berbanding ' +\n                previous,\n\n              value:\n                percentChange(\n                  currentValue,\n                  previousValue\n                )\n            });\n          }\n        }\n      );\n\n      el('crop-change-trend-sub')\n        .textContent =\n        'Perubahan tahunan \u00b7 ' +\n        cropLabel(s.crop) +\n        ' \u00b7 ' +\n        metricLabel(s.metric);\n\n      destroyChart('changeTrend');\n\n      charts.changeTrend =\n        new Chart(\n          el('crop-chart-change-trend')\n            .getContext('2d'),\n          {\n            type:'line',\n\n            data:{\n\n              labels:\n                trend.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Tahunan',\n\n                data:\n                  trend.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n                backgroundColor:'rgba(124,58,237,.15)',\n                fill:true,\n                tension:.35,\n                borderWidth:2.5,\n                pointRadius:4\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{color:'#dbeafe'}\n                },\n\n                y:{\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       STATE\n    ====================================================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n      var states =\n        getStates();\n\n      var data =\n        states.map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                stateMetric(\n                  state,\n                  s.year,\n                  s.crop,\n                  s.metric\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n      el('crop-state-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 semua negeri \u00b7 ' +\n        cropLabel(\n          s.crop\n        ) +\n        ' \u00b7 ' +\n        metricLabel(\n          s.metric\n        );\n\n      destroyChart('state');\n\n      charts.state =\n        new Chart(\n          el('crop-chart-state')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  metricLabel(\n                    s.metric\n                  ),\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipMetric(\n                    s.metric\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  grid:{display:false},\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label ===\n                          'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n                  grid:{color:'#dbeafe'},\n\n                  ticks:{\n                    callback:function(value){\n\n                      return fmt(\n                        value,\n                        s.metric === 'yield'\n                          ? 2\n                          : 0\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/*\n        Production + planted area have different units,\n        therefore use two Y axes.\n      *\/\n\n      var both =\n        states.map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              production:\n                stateMetric(\n                  state,\n                  s.year,\n                  s.crop,\n                  'production'\n                ),\n\n              area:\n                stateMetric(\n                  state,\n                  s.year,\n                  s.crop,\n                  'planted_area'\n                )\n            };\n          }\n        )\n        .filter(\n          function(item){\n\n            return (\n              valid(item.production) ||\n              valid(item.area)\n            );\n          }\n        );\n\n      destroyChart('stateBoth');\n\n      charts.stateBoth =\n        new Chart(\n          el('crop-chart-state-both')\n            .getContext('2d'),\n          {\n            type:'bar',\n\n            data:{\n\n              labels:\n                both.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[\n                {\n                  label:'Pengeluaran',\n\n                  data:\n                    both.map(\n                      function(item){\n                        return item.production;\n                      }\n                    ),\n\n                  yAxisID:'y',\n\n                  backgroundColor:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? 'rgba(249,115,22,.95)'\n                          : 'rgba(22,163,74,.70)';\n                      }\n                    ),\n\n                  borderColor:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? '#f97316'\n                          : 'transparent';\n                      }\n                    ),\n\n                  borderWidth:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? 2\n                          : 0;\n                      }\n                    ),\n\n                  borderRadius:5\n                },\n\n                {\n                  label:'Keluasan Tanaman',\n\n                  data:\n                    both.map(\n                      function(item){\n                        return item.area;\n                      }\n                    ),\n\n                  yAxisID:'y1',\n\n                  backgroundColor:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? 'rgba(249,115,22,.55)'\n                          : 'rgba(37,99,235,.68)';\n                      }\n                    ),\n\n                  borderColor:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? '#f97316'\n                          : 'transparent';\n                      }\n                    ),\n\n                  borderWidth:\n                    both.map(\n                      function(item){\n\n                        return item.state ===\n                          'Melaka'\n                          ? 2\n                          : 0;\n                      }\n                    ),\n\n                  borderRadius:5\n                }\n              ]\n            },\n\n            options:{\n              responsive:true,\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      if(\n                        c.dataset.yAxisID === 'y'\n                      ){\n\n                        return (\n                          ' Pengeluaran: ' +\n                          fmt(c.parsed.y,1) +\n                          ' tan metrik'\n                        );\n                      }\n\n                      return (\n                        ' Keluasan: ' +\n                        fmt(c.parsed.y,1) +\n                        ' hektar'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{display:false},\n\n                  ticks:{\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  type:'linear',\n                  position:'left',\n                  beginAtZero:true,\n\n                  grid:{color:'#dbeafe'},\n\n                  title:{\n                    display:true,\n                    text:'Pengeluaran (tan metrik)'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y1:{\n                  type:'linear',\n                  position:'right',\n                  beginAtZero:true,\n\n                  grid:{\n                    drawOnChartArea:false\n                  },\n\n                  title:{\n                    display:true,\n                    text:'Keluasan (hektar)'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TAB CONTROL\n    ====================================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#crop-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n    function redraw(){\n\n      updateCards();\n\n      var tab =\n        activeTab();\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'type'){\n        buildTypeCharts();\n      }\n\n      if(tab === 'production'){\n        buildProductionCharts();\n      }\n\n      if(tab === 'yield'){\n        buildYieldCharts();\n      }\n\n      if(tab === 'topbottom'){\n        buildTopBottomCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n\n    \/* =====================================================\n       TAB EVENTS\n    ====================================================== *\/\n\n    document\n      .querySelectorAll(\n        '#crop-wrap .tbtn'\n      )\n      .forEach(\n        function(btn){\n\n          btn.addEventListener(\n            'click',\n            function(){\n\n              document\n                .querySelectorAll(\n                  '#crop-wrap .tbtn'\n                )\n                .forEach(\n                  function(button){\n\n                    button.classList\n                      .remove('active');\n                  }\n                );\n\n              btn.classList\n                .add('active');\n\n              [\n                'trend',\n                'type',\n                'production',\n                'yield',\n                'topbottom',\n                'change',\n                'state'\n              ]\n              .forEach(\n                function(tab){\n\n                  el(\n                    'crop-tab-' +\n                    tab\n                  ).style.display =\n                    'none';\n                }\n              );\n\n              el(\n                'crop-tab-' +\n                btn.getAttribute(\n                  'data-tab'\n                )\n              ).style.display =\n                '';\n\n              redraw();\n            }\n          );\n        }\n      );\n\n\n    \/* =====================================================\n       FILTER EVENTS\n    ====================================================== *\/\n\n    [\n      'crop-year',\n      'crop-type',\n      'crop-metric'\n    ]\n    .forEach(\n      function(id){\n\n        el(id)\n          .addEventListener(\n            'change',\n            redraw\n          );\n      }\n    );\n\n\n    \/* =====================================================\n       FETCH API\n    ====================================================== *\/\n\n    fetch(API_URL)\n\n      .then(\n        function(response){\n\n          if(!response.ok){\n\n            throw new Error(\n              'HTTP ' +\n              response.status\n            );\n          }\n\n          return response.json();\n        }\n      )\n\n      .then(\n        function(data){\n\n          allRows =\n            Array.isArray(data)\n              ? data\n              : [];\n\n          melakaRows =\n            allRows.filter(\n              function(row){\n\n                return (\n                  String(\n                    row.state || ''\n                  )\n                  .trim()\n                  .toLowerCase()\n                  ===\n                  'melaka'\n                );\n              }\n            );\n\n          if(!melakaRows.length){\n\n            throw new Error(\n              'Tiada rekod tanaman Melaka ditemui.'\n            );\n          }\n\n          populateFilters();\n\n          el('crop-loading')\n            .style.display =\n            'none';\n\n          el('crop-area')\n            .style.display =\n            '';\n\n          redraw();\n        }\n      )\n\n      .catch(\n        function(error){\n\n          el('crop-loading')\n            .innerHTML =\n\n            '<div style=\"' +\n            'color:#dc2626;' +\n            'font-size:16px;' +\n            'font-weight:800;' +\n            'margin-bottom:8px' +\n            '\">' +\n\n            'Gagal memuatkan data' +\n\n            '<\/div>' +\n\n            '<p style=\"' +\n            'font-size:13px;' +\n            'color:#64748b' +\n            '\">' +\n\n            error.message +\n\n            '<\/p>';\n        }\n      );\n\n  }\n\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#croparea-wrap, #croparea-wrap *{\n  box-sizing:border-box;\n}\n\n#croparea-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(\n    180deg,\n    #ffffff 0%,\n    var(--bg) 100%\n  );\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#croparea-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#croparea-wrap .hdr,\n#croparea-wrap .cc,\n#croparea-wrap .sc,\n#croparea-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#croparea-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#croparea-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#croparea-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#croparea-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#croparea-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  background:#ffffff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#croparea-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#croparea-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#croparea-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#ffffff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#croparea-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#croparea-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#croparea-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#croparea-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#croparea-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#croparea-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n  line-height:1.35;\n}\n\n#croparea-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#croparea-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#croparea-wrap .tbtn.active{\n  background:#ffffff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#croparea-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#croparea-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#croparea-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n  line-height:1.45;\n}\n\n#croparea-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#croparea-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#croparea-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#croparea-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#croparea-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:croparea-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes croparea-spin{\n  to{\n    transform:rotate(360deg);\n  }\n}\n\n@media(max-width:1024px){\n\n  #croparea-wrap .filter-bar,\n  #croparea-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #croparea-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #croparea-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #croparea-wrap .filter-bar,\n  #croparea-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #croparea-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #croparea-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #croparea-wrap .chart-box,\n  #croparea-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n\n<div id=\"croparea-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Keluasan Tanaman Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Keluasan Tanaman Mengikut Daerah\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi keluasan tanaman di Melaka mengikut tahun,\n        daerah pentadbiran, jenis tanaman dan spesies tanaman.\n        Dashboard turut menunjukkan komposisi tanaman, perubahan tahunan,\n        tanaman dengan keluasan tertinggi dan perbandingan Melaka\n        dengan negeri-negeri lain.\n      <\/p>\n\n    <\/div>\n\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"croparea-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Daerah<\/label>\n        <select id=\"croparea-district\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Jenis Tanaman<\/label>\n        <select id=\"croparea-type\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Spesies Tanaman<\/label>\n        <select id=\"croparea-species\"><\/select>\n      <\/div>\n\n    <\/div>\n\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"croparea-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tahun Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"croparea-stat-year\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Jumlah Keluasan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"croparea-stat-total\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          hektar\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Daerah Tertinggi\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"croparea-stat-high\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"croparea-stat-high-name\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Perubahan Tahunan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"croparea-stat-change\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"croparea-stat-change-sub\"\n        >\n          berbanding tahun sebelumnya\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"croparea-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod keluasan tanaman Melaka.\n      <\/div>\n\n    <\/div>\n\n\n    <!-- DASHBOARD -->\n    <div\n      id=\"croparea-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TABS -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"district\"\n        >\n          Daerah\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"type\"\n        >\n          Jenis Tanaman\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"species\"\n        >\n          Spesies\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"composition\"\n        >\n          Komposisi\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n      <\/div>\n\n\n      <!-- ====================================\n           TREND\n      ===================================== -->\n      <div id=\"croparea-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Keluasan Tanaman Mengikut Masa\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"croparea-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"croparea-chart-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Mengikut Daerah\n            <\/div>\n\n            <div class=\"cs\">\n              Perbandingan keluasan tanaman antara daerah-daerah\n              di Melaka sepanjang tempoh data.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"croparea-chart-district-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ====================================\n           DISTRICT\n      ===================================== -->\n      <div\n        id=\"croparea-tab-district\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Keluasan Tanaman Mengikut Daerah\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"croparea-district-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"croparea-chart-district\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Komposisi Jenis Tanaman Mengikut Daerah\n          <\/div>\n\n          <div class=\"cs\">\n            Pecahan keluasan jenis tanaman bagi setiap daerah\n            di Melaka.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"croparea-chart-district-type\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ====================================\n           TYPE\n      ===================================== -->\n      <div\n        id=\"croparea-tab-type\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Keluasan Mengikut Jenis Tanaman\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"croparea-type-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"croparea-chart-type\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Sumbangan Jenis Tanaman\n            <\/div>\n\n            <div class=\"cs\">\n              Peratus sumbangan setiap jenis tanaman kepada jumlah\n              keluasan tanaman bagi pilihan semasa.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"croparea-chart-type-share\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ====================================\n           SPECIES\n      ===================================== -->\n      <div\n        id=\"croparea-tab-species\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Keluasan Mengikut Spesies Tanaman\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"croparea-species-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"croparea-chart-species\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Spesies dengan Keluasan Tertinggi\n          <\/div>\n\n          <div class=\"cs\">\n            Spesies tanaman utama berdasarkan keluasan tanaman\n            pada tahun dipilih.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"croparea-chart-species-top\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ====================================\n           COMPOSITION\n      ===================================== -->\n      <div\n        id=\"croparea-tab-composition\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komposisi Keluasan Mengikut Daerah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"croparea-composition-district-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"croparea-chart-district-share\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komposisi Keluasan Mengikut Jenis Tanaman\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"croparea-composition-type-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"croparea-chart-composition-type\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ====================================\n           CHANGE\n      ===================================== -->\n      <div\n        id=\"croparea-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Keluasan Mengikut Daerah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"croparea-change-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"croparea-chart-change\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Perubahan Tahunan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"croparea-change-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas\n                id=\"croparea-chart-change-trend\"\n              ><\/canvas>\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ====================================\n           STATE\n      ===================================== -->\n      <div\n        id=\"croparea-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Keluasan Tanaman Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"croparea-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"croparea-chart-state\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Jenis Tanaman Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan keluasan mengikut jenis tanaman bagi negeri-negeri\n            yang mempunyai rekod. Melaka ditonjolkan dalam warna oren.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas\n              id=\"croparea-chart-state-type\"\n            ><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=crops_district_area&limit=15000';\n\n    var FIXED_STATE =\n      'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n\n    Chart.defaults.color =\n      '#64748b';\n\n    Chart.defaults.borderColor =\n      '#dbeafe';\n\n    Chart.defaults.font.family =\n      'Arial, sans-serif';\n\n\n    \/* =====================================================\n       LABEL JENIS TANAMAN\n    ====================================================== *\/\n\n    var CROP_TYPE_LABELS = {\n\n      cash_crops:\n        'Tanaman Kontan',\n\n      industrial_crops:\n        'Tanaman Industri',\n\n      coconut:\n        'Kelapa',\n\n      paddy:\n        'Padi',\n\n      flower:\n        'Bunga',\n\n      flowers:\n        'Bunga',\n\n      herb:\n        'Herba',\n\n      herbs:\n        'Herba',\n\n      spice:\n        'Rempah-ratus',\n\n      spices:\n        'Rempah-ratus',\n\n      fruit:\n        'Buah-buahan',\n\n      fruits:\n        'Buah-buahan',\n\n      vegetable:\n        'Sayur-sayuran',\n\n      vegetables:\n        'Sayur-sayuran'\n    };\n\n\n    \/* =====================================================\n       LABEL SPESIES\n    ====================================================== *\/\n\n    var SPECIES_LABELS = {\n\n      cassava:\n        'Ubi Kayu',\n\n      groundnuts:\n        'Kacang Tanah',\n\n      sweet_corn:\n        'Jagung Manis',\n\n      sweet_potato:\n        'Ubi Keledek',\n\n      yam:\n        'Keladi',\n\n      yellow_sugar_cane:\n        'Tebu Kuning',\n\n      banana:\n        'Pisang',\n\n      cempedak:\n        'Cempedak',\n\n      ciku:\n        'Ciku',\n\n      dokong:\n        'Dokong',\n\n      dragon_fruit:\n        'Buah Naga',\n\n      durian:\n        'Durian',\n\n      guava:\n        'Jambu Batu',\n\n      jackfruit:\n        'Nangka',\n\n      langsat:\n        'Langsat',\n\n      mango:\n        'Mangga',\n\n      mangosteen:\n        'Manggis',\n\n      papaya:\n        'Betik',\n\n      pineapple:\n        'Nanas',\n\n      rambutan:\n        'Rambutan',\n\n      starfruit:\n        'Belimbing',\n\n      watermelon:\n        'Tembikai',\n\n      coconut:\n        'Kelapa',\n\n      paddy:\n        'Padi'\n    };\n\n\n    \/* =====================================================\n       BASIC HELPERS\n    ====================================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n    function yr(row){\n\n      return parseInt(\n        String(\n          row.date || ''\n        ).slice(0,4),\n        10\n      );\n    }\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === ''\n      ){\n        return null;\n      }\n\n      var n =\n        Number(value);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n    function valid(value){\n\n      return (\n        value !== null &&\n        value !== undefined &&\n        !isNaN(value)\n      );\n    }\n\n    function fmt(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n      return Number(value)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:d,\n            maximumFractionDigits:d\n          }\n        );\n    }\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,1) +\n        '%'\n      );\n    }\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n\n    function titleFromSnakeCase(value){\n\n      return String(value || '')\n        .replace(\/_\/g,' ')\n        .replace(\n          \/\\b\\w\/g,\n          function(c){\n            return c.toUpperCase();\n          }\n        );\n    }\n\n\n    function cropTypeLabel(type){\n\n      if(type === 'all'){\n        return 'Semua Jenis Tanaman';\n      }\n\n      if(CROP_TYPE_LABELS[type]){\n        return CROP_TYPE_LABELS[type];\n      }\n\n      return titleFromSnakeCase(type);\n    }\n\n\n    function speciesLabel(species){\n\n      if(species === 'all'){\n        return 'Semua Spesies';\n      }\n\n      if(SPECIES_LABELS[species]){\n        return SPECIES_LABELS[species];\n      }\n\n      return titleFromSnakeCase(\n        species\n      );\n    }\n\n\n    function percentChange(\n      current,\n      previous\n    ){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n      return (\n        (\n          current -\n          previous\n        ) \/\n        previous\n      ) * 100;\n    }\n\n\n    \/* =====================================================\n       AVAILABLE VALUES\n    ====================================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(yr)\n            .filter(function(year){\n              return !isNaN(year);\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n\n    function getPreviousYear(current){\n\n      var years =\n        availableYears()\n        .filter(function(year){\n          return year < current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n\n    function getDistricts(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(function(row){\n              return row.district;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort();\n    }\n\n\n    function getCropTypes(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(function(row){\n              return row.crop_type;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort(\n        function(a,b){\n\n          return cropTypeLabel(a)\n            .localeCompare(\n              cropTypeLabel(b),\n              'ms'\n            );\n        }\n      );\n    }\n\n\n    function getSpecies(\n      cropType\n    ){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n\n              return (\n                cropType === 'all' ||\n                row.crop_type === cropType\n              );\n            })\n            .map(function(row){\n              return row.crop_species;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort(\n        function(a,b){\n\n          return speciesLabel(a)\n            .localeCompare(\n              speciesLabel(b),\n              'ms'\n            );\n        }\n      );\n    }\n\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(function(state){\n\n              return (\n                state &#038;&#038;\n                state !== 'Malaysia'\n              );\n            })\n        )\n      )\n      .sort();\n    }\n\n\n    function getSelected(){\n\n      return {\n\n        year:\n          parseInt(\n            el('croparea-year').value,\n            10\n          ),\n\n        district:\n          el('croparea-district').value,\n\n        cropType:\n          el('croparea-type').value,\n\n        species:\n          el('croparea-species').value\n      };\n    }\n\n\n    \/* =====================================================\n       FILTER \/ AGGREGATION LOGIC\n    ====================================================== *\/\n\n    function rowsFor(\n      source,\n      state,\n      year,\n      district,\n      cropType,\n      species\n    ){\n\n      return source.filter(\n        function(row){\n\n          return (\n\n            row.state === state &#038;&#038;\n\n            yr(row) === year &#038;&#038;\n\n            (\n              district === 'all' ||\n              row.district === district\n            ) &#038;&#038;\n\n            (\n              cropType === 'all' ||\n              row.crop_type === cropType\n            ) &#038;&#038;\n\n            (\n              species === 'all' ||\n              row.crop_species === species\n            )\n          );\n        }\n      );\n    }\n\n\n    function totalArea(rows){\n\n      var values =\n        rows\n        .map(function(row){\n          return num(\n            row.planted_area\n          );\n        })\n        .filter(valid);\n\n      if(!values.length){\n        return null;\n      }\n\n      return values.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      );\n    }\n\n\n    function melakaArea(\n      year,\n      district,\n      cropType,\n      species\n    ){\n\n      return totalArea(\n        rowsFor(\n          melakaRows,\n          FIXED_STATE,\n          year,\n          district,\n          cropType,\n          species\n        )\n      );\n    }\n\n\n    function stateArea(\n      state,\n      year,\n      cropType,\n      species\n    ){\n\n      return totalArea(\n        rowsFor(\n          allRows,\n          state,\n          year,\n          'all',\n          cropType,\n          species\n        )\n      );\n    }\n\n\n    \/* =====================================================\n       FILTER POPULATION\n    ====================================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      el('croparea-year')\n        .innerHTML =\n\n        years.map(\n          function(year){\n\n            return (\n              '<option value=\"' +\n              year +\n              '\">' +\n              year +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n      if(years.length){\n\n        el('croparea-year')\n          .value =\n          years[0];\n      }\n\n\n      var districts =\n        getDistricts();\n\n      el('croparea-district')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Daerah' +\n        '<\/option>' +\n\n        districts.map(\n          function(district){\n\n            return (\n              '<option value=\"' +\n              district +\n              '\">' +\n              district +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      var cropTypes =\n        getCropTypes();\n\n      el('croparea-type')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Jenis Tanaman' +\n        '<\/option>' +\n\n        cropTypes.map(\n          function(type){\n\n            return (\n              '<option value=\"' +\n              type +\n              '\">' +\n              cropTypeLabel(type) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      populateSpeciesFilter();\n    }\n\n\n    function populateSpeciesFilter(){\n\n      var type =\n        el('croparea-type').value ||\n        'all';\n\n      var species =\n        getSpecies(type);\n\n      var current =\n        el('croparea-species').value;\n\n      el('croparea-species')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Spesies' +\n        '<\/option>' +\n\n        species.map(\n          function(item){\n\n            return (\n              '<option value=\"' +\n              item +\n              '\">' +\n              speciesLabel(item) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(\n        current &&\n        species.indexOf(current) !== -1\n      ){\n\n        el('croparea-species')\n          .value =\n          current;\n\n      }else{\n\n        el('croparea-species')\n          .value =\n          'all';\n      }\n    }\n\n\n    \/* =====================================================\n       SUMMARY CARDS\n    ====================================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n      var total =\n        melakaArea(\n          s.year,\n          s.district,\n          s.cropType,\n          s.species\n        );\n\n\n      var districts =\n        getDistricts();\n\n      var districtData =\n        districts.map(\n          function(district){\n\n            return {\n\n              district:district,\n\n              value:\n                melakaArea(\n                  s.year,\n                  district,\n                  s.cropType,\n                  s.species\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var highest =\n        districtData.length\n          ? districtData[0]\n          : null;\n\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n\n      var previousTotal =\n        previousYear !== null\n          ? melakaArea(\n              previousYear,\n              s.district,\n              s.cropType,\n              s.species\n            )\n          : null;\n\n\n      var change =\n        previousYear !== null\n          ? percentChange(\n              total,\n              previousTotal\n            )\n          : null;\n\n\n      el('croparea-stat-year')\n        .textContent =\n        s.year || '\u2014';\n\n\n      el('croparea-stat-total')\n        .textContent =\n        valid(total)\n          ? fmt(total,1)\n          : '\u2014';\n\n\n      el('croparea-stat-high')\n        .textContent =\n        highest\n          ? fmt(\n              highest.value,\n              1\n            )\n          : '\u2014';\n\n\n      el('croparea-stat-high-name')\n        .textContent =\n        highest\n          ? highest.district\n          : '\u2014';\n\n\n      el('croparea-stat-change')\n        .textContent =\n        valid(change)\n          ? signedPct(change)\n          : '\u2014';\n\n\n      el('croparea-stat-change-sub')\n        .textContent =\n        previousYear !== null\n          ? (\n              'berbanding ' +\n              previousYear\n            )\n          : 'tiada tahun sebelumnya';\n\n\n      el('croparea-stats')\n        .style.display =\n        '';\n    }\n\n\n    \/* =====================================================\n       TOOLTIP\n    ====================================================== *\/\n\n    function tooltipArea(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.y,\n                1\n              ) +\n              ' hektar'\n            );\n          }\n        }\n      };\n    }\n\n\n    function tooltipHorizontalArea(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.x,\n                1\n              ) +\n              ' hektar'\n            );\n          }\n        }\n      };\n    }\n\n\n    \/* =====================================================\n       TREND\n    ====================================================== *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var years =\n        availableYears();\n\n\n      el('croparea-trend-sub')\n        .textContent =\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart('trend');\n\n\n      charts.trend =\n        new Chart(\n          el('croparea-chart-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Keluasan Tanaman',\n\n                data:\n                  years.map(\n                    function(year){\n\n                      return melakaArea(\n                        year,\n                        s.district,\n                        s.cropType,\n                        s.species\n                      );\n                    }\n                  ),\n\n                borderColor:'#2563eb',\n\n                backgroundColor:\n                  'rgba(37,99,235,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/* Trend by district *\/\n\n      var districts =\n        getDistricts();\n\n      var colours = [\n        '#2563eb',\n        '#f97316',\n        '#16a34a',\n        '#7c3aed',\n        '#dc2626',\n        '#0891b2'\n      ];\n\n\n      destroyChart(\n        'districtTrend'\n      );\n\n\n      charts.districtTrend =\n        new Chart(\n          el('croparea-chart-district-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:\n                districts.map(\n                  function(district,index){\n\n                    return {\n\n                      label:district,\n\n                      data:\n                        years.map(\n                          function(year){\n\n                            return melakaArea(\n                              year,\n                              district,\n                              s.cropType,\n                              s.species\n                            );\n                          }\n                        ),\n\n                      borderColor:\n                        colours[\n                          index %\n                          colours.length\n                        ],\n\n                      tension:.35,\n\n                      borderWidth:2.3,\n\n                      pointRadius:3\n                    };\n                  }\n                )\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       DISTRICT\n    ====================================================== *\/\n\n    function buildDistrictCharts(){\n\n      var s =\n        getSelected();\n\n      var districts =\n        getDistricts();\n\n\n      var data =\n        districts.map(\n          function(district){\n\n            return {\n\n              district:district,\n\n              value:\n                melakaArea(\n                  s.year,\n                  district,\n                  s.cropType,\n                  s.species\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('croparea-district-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n        cropTypeLabel(\n          s.cropType\n        ) +\n        ' \u00b7 ' +\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart('district');\n\n\n      charts.district =\n        new Chart(\n          el('croparea-chart-district')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.district;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(37,99,235,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/* Stacked crop type by district *\/\n\n      var cropTypes =\n        getCropTypes();\n\n      var typeColours = [\n        'rgba(37,99,235,.78)',\n        'rgba(22,163,74,.78)',\n        'rgba(249,115,22,.78)',\n        'rgba(124,58,237,.78)',\n        'rgba(8,145,178,.78)',\n        'rgba(220,38,38,.78)',\n        'rgba(100,116,139,.78)',\n        'rgba(13,148,136,.78)',\n        'rgba(147,51,234,.78)'\n      ];\n\n\n      destroyChart(\n        'districtType'\n      );\n\n\n      charts.districtType =\n        new Chart(\n          el('croparea-chart-district-type')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:districts,\n\n              datasets:\n                cropTypes.map(\n                  function(type,index){\n\n                    return {\n\n                      label:\n                        cropTypeLabel(\n                          type\n                        ),\n\n                      data:\n                        districts.map(\n                          function(district){\n\n                            return melakaArea(\n                              s.year,\n                              district,\n                              type,\n                              'all'\n                            );\n                          }\n                        ),\n\n                      backgroundColor:\n                        typeColours[\n                          index %\n                          typeColours.length\n                        ],\n\n                      borderRadius:4\n                    };\n                  }\n                )\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n                  stacked:true,\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  stacked:true,\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TYPE\n    ====================================================== *\/\n\n    function buildTypeCharts(){\n\n      var s =\n        getSelected();\n\n      var cropTypes =\n        getCropTypes();\n\n\n      var data =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              type:type,\n\n              label:\n                cropTypeLabel(type),\n\n              value:\n                melakaArea(\n                  s.year,\n                  s.district,\n                  type,\n                  'all'\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('croparea-type-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        );\n\n\n      destroyChart('type');\n\n\n      charts.type =\n        new Chart(\n          el('croparea-chart-type')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(37,99,235,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              indexAxis:'y',\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalArea()\n              },\n\n              scales:{\n\n                x:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      destroyChart(\n        'typeShare'\n      );\n\n\n      charts.typeShare =\n        new Chart(\n          el('croparea-chart-type-share')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#16a34a',\n                  '#f97316',\n                  '#7c3aed',\n                  '#0891b2',\n                  '#dc2626',\n                  '#64748b',\n                  '#0d9488',\n                  '#9333ea'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' hektar (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       SPECIES\n    ====================================================== *\/\n\n    function buildSpeciesCharts(){\n\n      var s =\n        getSelected();\n\n      var species =\n        getSpecies(\n          s.cropType\n        );\n\n\n      var data =\n        species.map(\n          function(item){\n\n            return {\n\n              species:item,\n\n              label:\n                speciesLabel(item),\n\n              value:\n                melakaArea(\n                  s.year,\n                  s.district,\n                  s.cropType,\n                  item\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('croparea-species-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        );\n\n\n      destroyChart('species');\n\n\n      charts.species =\n        new Chart(\n          el('croparea-chart-species')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(124,58,237,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              indexAxis:'y',\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalArea()\n              },\n\n              scales:{\n\n                x:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var topSpecies =\n        data.slice(\n          0,\n          Math.min(\n            10,\n            data.length\n          )\n        );\n\n\n      destroyChart(\n        'speciesTop'\n      );\n\n\n      charts.speciesTop =\n        new Chart(\n          el('croparea-chart-species-top')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                topSpecies.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan Tanaman',\n\n                data:\n                  topSpecies.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(22,163,74,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       COMPOSITION\n    ====================================================== *\/\n\n    function buildCompositionCharts(){\n\n      var s =\n        getSelected();\n\n      var districts =\n        getDistricts();\n\n\n      var districtData =\n        districts.map(\n          function(district){\n\n            return {\n\n              label:district,\n\n              value:\n                melakaArea(\n                  s.year,\n                  district,\n                  s.cropType,\n                  s.species\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        });\n\n\n      el('croparea-composition-district-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart(\n        'districtShare'\n      );\n\n\n      charts.districtShare =\n        new Chart(\n          el('croparea-chart-district-share')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                districtData.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan',\n\n                data:\n                  districtData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#f97316',\n                  '#16a34a',\n                  '#7c3aed',\n                  '#0891b2'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' hektar (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var types =\n        getCropTypes();\n\n\n      var typeData =\n        types.map(\n          function(type){\n\n            return {\n\n              label:\n                cropTypeLabel(type),\n\n              value:\n                melakaArea(\n                  s.year,\n                  s.district,\n                  type,\n                  'all'\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        });\n\n\n      el('croparea-composition-type-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        );\n\n\n      destroyChart(\n        'compositionType'\n      );\n\n\n      charts.compositionType =\n        new Chart(\n          el('croparea-chart-composition-type')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                typeData.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan',\n\n                data:\n                  typeData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#16a34a',\n                  '#f97316',\n                  '#7c3aed',\n                  '#0891b2',\n                  '#dc2626',\n                  '#64748b',\n                  '#0d9488',\n                  '#9333ea'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' hektar (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       CHANGE\n    ====================================================== *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n\n      if(\n        previousYear === null\n      ){\n\n        el('croparea-change-sub')\n          .textContent =\n          s.year +\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n        el('croparea-change-trend-sub')\n          .textContent =\n          'Tiada tahun sebelumnya tersedia.';\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n\n      var districts =\n        getDistricts();\n\n\n      var data =\n        districts.map(\n          function(district){\n\n            var current =\n              melakaArea(\n                s.year,\n                district,\n                s.cropType,\n                s.species\n              );\n\n            var previous =\n              melakaArea(\n                previousYear,\n                district,\n                s.cropType,\n                s.species\n              );\n\n            return {\n\n              district:district,\n\n              value:\n                valid(current) &&\n                valid(previous)\n                  ? percentChange(\n                      current,\n                      previous\n                    )\n                  : null\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('croparea-change-sub')\n        .textContent =\n\n        s.year +\n        ' berbanding ' +\n        previousYear +\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart('change');\n\n\n      charts.change =\n        new Chart(\n          el('croparea-chart-change')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.district;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.value >= 0\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var years =\n        availableYears();\n\n      var trend = [];\n\n\n      years.forEach(\n        function(year,index){\n\n          if(index === 0){\n            return;\n          }\n\n          var previous =\n            years[index-1];\n\n\n          var currentValue =\n            melakaArea(\n              year,\n              s.district,\n              s.cropType,\n              s.species\n            );\n\n\n          var previousValue =\n            melakaArea(\n              previous,\n              s.district,\n              s.cropType,\n              s.species\n            );\n\n\n          if(\n            valid(currentValue) &&\n            valid(previousValue)\n          ){\n\n            trend.push({\n\n              label:\n                year +\n                ' berbanding ' +\n                previous,\n\n              value:\n                percentChange(\n                  currentValue,\n                  previousValue\n                )\n            });\n          }\n        }\n      );\n\n\n      el('croparea-change-trend-sub')\n        .textContent =\n\n        'Perubahan tahunan \u00b7 ' +\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart(\n        'changeTrend'\n      );\n\n\n      charts.changeTrend =\n        new Chart(\n          el('croparea-chart-change-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                trend.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Tahunan',\n\n                data:\n                  trend.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n\n                backgroundColor:\n                  'rgba(124,58,237,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       STATE COMPARISON\n    ====================================================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n      var states =\n        getStates();\n\n\n      var data =\n        states.map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                stateArea(\n                  state,\n                  s.year,\n                  s.cropType,\n                  s.species\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('croparea-state-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 semua negeri \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart('state');\n\n\n      charts.state =\n        new Chart(\n          el('croparea-chart-state')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label ===\n                          'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/*\n        Compare crop types by state.\n        Species filter is intentionally ignored here\n        so the chart can show crop-type composition.\n      *\/\n\n      var cropTypes =\n        getCropTypes();\n\n      var stateOrder =\n        data.map(\n          function(item){\n            return item.state;\n          }\n        );\n\n\n      var typeColours = [\n        'rgba(37,99,235,.75)',\n        'rgba(22,163,74,.75)',\n        'rgba(249,115,22,.75)',\n        'rgba(124,58,237,.75)',\n        'rgba(8,145,178,.75)',\n        'rgba(220,38,38,.75)',\n        'rgba(100,116,139,.75)',\n        'rgba(13,148,136,.75)',\n        'rgba(147,51,234,.75)'\n      ];\n\n\n      destroyChart(\n        'stateType'\n      );\n\n\n      charts.stateType =\n        new Chart(\n          el('croparea-chart-state-type')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                stateOrder,\n\n              datasets:\n                cropTypes.map(\n                  function(type,index){\n\n                    return {\n\n                      label:\n                        cropTypeLabel(\n                          type\n                        ),\n\n                      data:\n                        stateOrder.map(\n                          function(state){\n\n                            return stateArea(\n                              state,\n                              s.year,\n                              type,\n                              'all'\n                            );\n                          }\n                        ),\n\n                      backgroundColor:\n                        stateOrder.map(\n                          function(state){\n\n                            if(\n                              state ===\n                              'Melaka'\n                            ){\n\n                              return (\n                                index % 2 === 0\n                                  ? 'rgba(249,115,22,.95)'\n                                  : 'rgba(249,115,22,.60)'\n                              );\n                            }\n\n                            return typeColours[\n                              index %\n                              typeColours.length\n                            ];\n                          }\n                        ),\n\n                      borderColor:\n                        stateOrder.map(\n                          function(state){\n\n                            return (\n                              state ===\n                              'Melaka'\n                            )\n                              ? '#f97316'\n                              : 'transparent';\n                          }\n                        ),\n\n                      borderWidth:\n                        stateOrder.map(\n                          function(state){\n\n                            return (\n                              state ===\n                              'Melaka'\n                            )\n                              ? 1.5\n                              : 0;\n                          }\n                        )\n                    };\n                  }\n                )\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  stacked:true,\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       ACTIVE TAB\n    ====================================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#croparea-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n\n    function redraw(){\n\n      updateCards();\n\n      var tab =\n        activeTab();\n\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'district'){\n        buildDistrictCharts();\n      }\n\n      if(tab === 'type'){\n        buildTypeCharts();\n      }\n\n      if(tab === 'species'){\n        buildSpeciesCharts();\n      }\n\n      if(tab === 'composition'){\n        buildCompositionCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n\n    \/* =====================================================\n       TAB EVENTS\n    ====================================================== *\/\n\n    document\n      .querySelectorAll(\n        '#croparea-wrap .tbtn'\n      )\n      .forEach(\n        function(btn){\n\n          btn.addEventListener(\n            'click',\n            function(){\n\n              document\n                .querySelectorAll(\n                  '#croparea-wrap .tbtn'\n                )\n                .forEach(\n                  function(button){\n\n                    button.classList\n                      .remove(\n                        'active'\n                      );\n                  }\n                );\n\n\n              btn.classList\n                .add(\n                  'active'\n                );\n\n\n              [\n                'trend',\n                'district',\n                'type',\n                'species',\n                'composition',\n                'change',\n                'state'\n              ]\n              .forEach(\n                function(tab){\n\n                  el(\n                    'croparea-tab-' +\n                    tab\n                  ).style.display =\n                    'none';\n                }\n              );\n\n\n              el(\n                'croparea-tab-' +\n                btn.getAttribute(\n                  'data-tab'\n                )\n              ).style.display =\n                '';\n\n\n              redraw();\n            }\n          );\n        }\n      );\n\n\n    \/* =====================================================\n       FILTER EVENTS\n    ====================================================== *\/\n\n    el('croparea-type')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateSpeciesFilter();\n\n          redraw();\n        }\n      );\n\n\n    [\n      'croparea-year',\n      'croparea-district',\n      'croparea-species'\n    ]\n    .forEach(\n      function(id){\n\n        el(id)\n          .addEventListener(\n            'change',\n            redraw\n          );\n      }\n    );\n\n\n    \/* =====================================================\n       FETCH\n    ====================================================== *\/\n\n    fetch(API_URL)\n\n      .then(\n        function(response){\n\n          if(!response.ok){\n\n            throw new Error(\n              'HTTP ' +\n              response.status\n            );\n          }\n\n          return response.json();\n        }\n      )\n\n      .then(\n        function(data){\n\n          allRows =\n            Array.isArray(data)\n              ? data\n              : [];\n\n\n          melakaRows =\n            allRows.filter(\n              function(row){\n\n                return (\n                  String(\n                    row.state || ''\n                  )\n                  .trim()\n                  .toLowerCase()\n                  ===\n                  'melaka'\n                );\n              }\n            );\n\n\n          if(!melakaRows.length){\n\n            throw new Error(\n              'Tiada rekod keluasan tanaman Melaka ditemui.'\n            );\n          }\n\n\n          populateFilters();\n\n\n          el('croparea-loading')\n            .style.display =\n            'none';\n\n\n          el('croparea-area')\n            .style.display =\n            '';\n\n\n          redraw();\n        }\n      )\n\n      .catch(\n        function(error){\n\n          el('croparea-loading')\n            .innerHTML =\n\n            '<div style=\"' +\n            'color:#dc2626;' +\n            'font-size:16px;' +\n            'font-weight:800;' +\n            'margin-bottom:8px' +\n            '\">' +\n\n            'Gagal memuatkan data' +\n\n            '<\/div>' +\n\n            '<p style=\"' +\n            'font-size:13px;' +\n            'color:#64748b' +\n            '\">' +\n\n            error.message +\n\n            '<\/p>';\n        }\n      );\n\n  }\n\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#cropprod-wrap, #cropprod-wrap *{\n  box-sizing:border-box;\n}\n\n#cropprod-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(\n    180deg,\n    #ffffff 0%,\n    var(--bg) 100%\n  );\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#cropprod-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#cropprod-wrap .hdr,\n#cropprod-wrap .cc,\n#cropprod-wrap .sc,\n#cropprod-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#cropprod-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#cropprod-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#cropprod-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#cropprod-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#cropprod-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  background:#ffffff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#cropprod-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#cropprod-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#cropprod-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#ffffff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#cropprod-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#cropprod-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#cropprod-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#cropprod-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#cropprod-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#cropprod-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n  line-height:1.35;\n}\n\n#cropprod-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#cropprod-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#cropprod-wrap .tbtn.active{\n  background:#ffffff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#cropprod-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#cropprod-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#cropprod-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n  line-height:1.45;\n}\n\n#cropprod-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#cropprod-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#cropprod-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#cropprod-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#cropprod-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:cropprod-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes cropprod-spin{\n  to{\n    transform:rotate(360deg);\n  }\n}\n\n@media(max-width:1024px){\n\n  #cropprod-wrap .filter-bar,\n  #cropprod-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #cropprod-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #cropprod-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #cropprod-wrap .filter-bar,\n  #cropprod-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #cropprod-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #cropprod-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #cropprod-wrap .chart-box,\n  #cropprod-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n\n<div id=\"cropprod-wrap\">\n  <div class=\"dash\">\n\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Pengeluaran Tanaman Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Pengeluaran Tanaman Mengikut Daerah\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi pengeluaran tanaman di Melaka mengikut tahun,\n        daerah pentadbiran, jenis tanaman dan spesies tanaman.\n        Dashboard turut menunjukkan komposisi pengeluaran,\n        tanaman utama, perubahan tahunan dan perbandingan Melaka\n        dengan negeri-negeri lain.\n      <\/p>\n\n    <\/div>\n\n\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"cropprod-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Daerah<\/label>\n        <select id=\"cropprod-district\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Jenis Tanaman<\/label>\n        <select id=\"cropprod-type\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Spesies Tanaman<\/label>\n        <select id=\"cropprod-species\"><\/select>\n      <\/div>\n\n    <\/div>\n\n\n    <div\n      class=\"stats\"\n      id=\"cropprod-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tahun Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"cropprod-stat-year\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Jumlah Pengeluaran\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"cropprod-stat-total\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          tan metrik\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Daerah Tertinggi\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"cropprod-stat-high\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"cropprod-stat-high-name\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Perubahan Tahunan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"cropprod-stat-change\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"cropprod-stat-change-sub\"\n        >\n          berbanding tahun sebelumnya\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <div\n      class=\"sbox\"\n      id=\"cropprod-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod pengeluaran tanaman Melaka.\n      <\/div>\n\n    <\/div>\n\n\n    <div\n      id=\"cropprod-area\"\n      style=\"display:none\"\n    >\n\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"district\"\n        >\n          Daerah\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"type\"\n        >\n          Jenis Tanaman\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"species\"\n        >\n          Spesies\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"composition\"\n        >\n          Komposisi\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n      <\/div>\n\n\n      <!-- TREND -->\n      <div id=\"cropprod-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Pengeluaran Tanaman Mengikut Masa\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cropprod-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"cropprod-chart-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Pengeluaran Mengikut Daerah\n            <\/div>\n\n            <div class=\"cs\">\n              Perbandingan pengeluaran tanaman antara daerah-daerah\n              di Melaka sepanjang tempoh data.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"cropprod-chart-district-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- DISTRICT -->\n      <div\n        id=\"cropprod-tab-district\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Pengeluaran Tanaman Mengikut Daerah\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"cropprod-district-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"cropprod-chart-district\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Komposisi Jenis Tanaman Mengikut Daerah\n          <\/div>\n\n          <div class=\"cs\">\n            Pecahan pengeluaran mengikut jenis tanaman bagi setiap daerah\n            di Melaka.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"cropprod-chart-district-type\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- TYPE -->\n      <div\n        id=\"cropprod-tab-type\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pengeluaran Mengikut Jenis Tanaman\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cropprod-type-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"cropprod-chart-type\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Sumbangan Jenis Tanaman\n            <\/div>\n\n            <div class=\"cs\">\n              Peratus sumbangan setiap jenis tanaman kepada jumlah\n              pengeluaran tanaman bagi pilihan semasa.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"cropprod-chart-type-share\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- SPECIES -->\n      <div\n        id=\"cropprod-tab-species\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Pengeluaran Mengikut Spesies Tanaman\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"cropprod-species-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"cropprod-chart-species\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Spesies dengan Pengeluaran Tertinggi\n          <\/div>\n\n          <div class=\"cs\">\n            Sepuluh spesies tanaman utama berdasarkan jumlah pengeluaran\n            pada tahun dipilih.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"cropprod-chart-species-top\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- COMPOSITION -->\n      <div\n        id=\"cropprod-tab-composition\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komposisi Pengeluaran Mengikut Daerah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cropprod-composition-district-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"cropprod-chart-district-share\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komposisi Pengeluaran Mengikut Jenis Tanaman\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cropprod-composition-type-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"cropprod-chart-composition-type\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- CHANGE -->\n      <div\n        id=\"cropprod-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Pengeluaran Mengikut Daerah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cropprod-change-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"cropprod-chart-change\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Perubahan Tahunan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"cropprod-change-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"cropprod-chart-change-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- STATE -->\n      <div\n        id=\"cropprod-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Pengeluaran Tanaman Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"cropprod-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"cropprod-chart-state\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Jenis Tanaman Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan pengeluaran mengikut jenis tanaman bagi\n            negeri-negeri yang mempunyai rekod.\n            Melaka ditonjolkan dalam warna oren.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"cropprod-chart-state-type\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=crops_district_production&limit=20000';\n\n    var FIXED_STATE =\n      'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n\n    Chart.defaults.color =\n      '#64748b';\n\n    Chart.defaults.borderColor =\n      '#dbeafe';\n\n    Chart.defaults.font.family =\n      'Arial, sans-serif';\n\n\n    \/* =====================================================\n       CROP TYPE LABELS\n    ====================================================== *\/\n\n    var CROP_TYPE_LABELS = {\n\n      cash_crops:\n        'Tanaman Kontan',\n\n      industrial_crops:\n        'Tanaman Industri',\n\n      coconut:\n        'Kelapa',\n\n      paddy:\n        'Padi',\n\n      flower:\n        'Bunga',\n\n      flowers:\n        'Bunga',\n\n      herb:\n        'Herba',\n\n      herbs:\n        'Herba',\n\n      spice:\n        'Rempah-ratus',\n\n      spices:\n        'Rempah-ratus',\n\n      fruit:\n        'Buah-buahan',\n\n      fruits:\n        'Buah-buahan',\n\n      vegetable:\n        'Sayur-sayuran',\n\n      vegetables:\n        'Sayur-sayuran'\n    };\n\n\n    \/* =====================================================\n       SPECIES LABELS\n    ====================================================== *\/\n\n    var SPECIES_LABELS = {\n\n      cassava:\n        'Ubi Kayu',\n\n      groundnuts:\n        'Kacang Tanah',\n\n      sweet_corn:\n        'Jagung Manis',\n\n      sweet_potato:\n        'Ubi Keledek',\n\n      yam:\n        'Keladi',\n\n      yellow_sugar_cane:\n        'Tebu Kuning',\n\n      banana:\n        'Pisang',\n\n      cempedak:\n        'Cempedak',\n\n      ciku:\n        'Ciku',\n\n      dokong:\n        'Dokong',\n\n      dragon_fruit:\n        'Buah Naga',\n\n      durian:\n        'Durian',\n\n      guava:\n        'Jambu Batu',\n\n      jackfruit:\n        'Nangka',\n\n      langsat:\n        'Langsat',\n\n      mango:\n        'Mangga',\n\n      mangosteen:\n        'Manggis',\n\n      papaya:\n        'Betik',\n\n      pineapple:\n        'Nanas',\n\n      pulasan:\n        'Pulasan',\n\n      rambutan:\n        'Rambutan',\n\n      salak:\n        'Salak',\n\n      starfruit:\n        'Belimbing',\n\n      watermelon:\n        'Tembikai',\n\n      coconut:\n        'Kelapa',\n\n      paddy:\n        'Padi'\n    };\n\n\n    \/* =====================================================\n       BASIC HELPERS\n    ====================================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n\n    function yr(row){\n\n      return parseInt(\n        String(\n          row.date || ''\n        ).slice(0,4),\n        10\n      );\n    }\n\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === ''\n      ){\n        return null;\n      }\n\n      var n =\n        Number(value);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n\n    function valid(value){\n\n      return (\n        value !== null &&\n        value !== undefined &&\n        !isNaN(value)\n      );\n    }\n\n\n    function fmt(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n      return Number(value)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:d,\n            maximumFractionDigits:d\n          }\n        );\n    }\n\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,1) +\n        '%'\n      );\n    }\n\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n\n    function titleFromSnakeCase(value){\n\n      return String(value || '')\n        .replace(\/_\/g,' ')\n        .replace(\n          \/\\b\\w\/g,\n          function(c){\n            return c.toUpperCase();\n          }\n        );\n    }\n\n\n    function cropTypeLabel(type){\n\n      if(type === 'all'){\n        return 'Semua Jenis Tanaman';\n      }\n\n      if(CROP_TYPE_LABELS[type]){\n        return CROP_TYPE_LABELS[type];\n      }\n\n      return titleFromSnakeCase(type);\n    }\n\n\n    function speciesLabel(species){\n\n      if(species === 'all'){\n        return 'Semua Spesies';\n      }\n\n      if(SPECIES_LABELS[species]){\n        return SPECIES_LABELS[species];\n      }\n\n      return titleFromSnakeCase(\n        species\n      );\n    }\n\n\n    function percentChange(\n      current,\n      previous\n    ){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n      return (\n        (\n          current -\n          previous\n        ) \/\n        previous\n      ) * 100;\n    }\n\n\n    \/* =====================================================\n       AVAILABLE VALUES\n    ====================================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(yr)\n            .filter(function(year){\n              return !isNaN(year);\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n\n    function getPreviousYear(current){\n\n      var years =\n        availableYears()\n        .filter(function(year){\n          return year < current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n\n    function getDistricts(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(function(row){\n              return row.district;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort();\n    }\n\n\n    function getCropTypes(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(function(row){\n              return row.crop_type;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort(\n        function(a,b){\n\n          return cropTypeLabel(a)\n            .localeCompare(\n              cropTypeLabel(b),\n              'ms'\n            );\n        }\n      );\n    }\n\n\n    function getSpecies(\n      cropType\n    ){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n\n              return (\n                cropType === 'all' ||\n                row.crop_type === cropType\n              );\n            })\n            .map(function(row){\n              return row.crop_species;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort(\n        function(a,b){\n\n          return speciesLabel(a)\n            .localeCompare(\n              speciesLabel(b),\n              'ms'\n            );\n        }\n      );\n    }\n\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(function(state){\n\n              return (\n                state &#038;&#038;\n                state !== 'Malaysia'\n              );\n            })\n        )\n      )\n      .sort();\n    }\n\n\n    function getSelected(){\n\n      return {\n\n        year:\n          parseInt(\n            el('cropprod-year').value,\n            10\n          ),\n\n        district:\n          el('cropprod-district').value,\n\n        cropType:\n          el('cropprod-type').value,\n\n        species:\n          el('cropprod-species').value\n      };\n    }\n\n\n    \/* =====================================================\n       FILTER + AGGREGATION\n    ====================================================== *\/\n\n    function rowsFor(\n      source,\n      state,\n      year,\n      district,\n      cropType,\n      species\n    ){\n\n      return source.filter(\n        function(row){\n\n          return (\n\n            row.state === state &#038;&#038;\n\n            yr(row) === year &#038;&#038;\n\n            (\n              district === 'all' ||\n              row.district === district\n            ) &#038;&#038;\n\n            (\n              cropType === 'all' ||\n              row.crop_type === cropType\n            ) &#038;&#038;\n\n            (\n              species === 'all' ||\n              row.crop_species === species\n            )\n\n          );\n        }\n      );\n    }\n\n\n    function totalProduction(rows){\n\n      var values =\n        rows\n        .map(function(row){\n          return num(\n            row.production\n          );\n        })\n        .filter(valid);\n\n      if(!values.length){\n        return null;\n      }\n\n      return values.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      );\n    }\n\n\n    function melakaProduction(\n      year,\n      district,\n      cropType,\n      species\n    ){\n\n      return totalProduction(\n        rowsFor(\n          melakaRows,\n          FIXED_STATE,\n          year,\n          district,\n          cropType,\n          species\n        )\n      );\n    }\n\n\n    function stateProduction(\n      state,\n      year,\n      cropType,\n      species\n    ){\n\n      return totalProduction(\n        rowsFor(\n          allRows,\n          state,\n          year,\n          'all',\n          cropType,\n          species\n        )\n      );\n    }\n\n\n    \/* =====================================================\n       FILTER POPULATION\n    ====================================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n\n      el('cropprod-year')\n        .innerHTML =\n\n        years.map(\n          function(year){\n\n            return (\n              '<option value=\"' +\n              year +\n              '\">' +\n              year +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(years.length){\n\n        el('cropprod-year')\n          .value =\n          years[0];\n      }\n\n\n      var districts =\n        getDistricts();\n\n\n      el('cropprod-district')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Daerah' +\n        '<\/option>' +\n\n        districts.map(\n          function(district){\n\n            return (\n              '<option value=\"' +\n              district +\n              '\">' +\n              district +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      var cropTypes =\n        getCropTypes();\n\n\n      el('cropprod-type')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Jenis Tanaman' +\n        '<\/option>' +\n\n        cropTypes.map(\n          function(type){\n\n            return (\n              '<option value=\"' +\n              type +\n              '\">' +\n              cropTypeLabel(type) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      populateSpeciesFilter();\n    }\n\n\n    function populateSpeciesFilter(){\n\n      var type =\n        el('cropprod-type').value ||\n        'all';\n\n      var species =\n        getSpecies(type);\n\n      var current =\n        el('cropprod-species').value;\n\n\n      el('cropprod-species')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Spesies' +\n        '<\/option>' +\n\n        species.map(\n          function(item){\n\n            return (\n              '<option value=\"' +\n              item +\n              '\">' +\n              speciesLabel(item) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(\n        current &&\n        species.indexOf(current) !== -1\n      ){\n\n        el('cropprod-species')\n          .value =\n          current;\n\n      }else{\n\n        el('cropprod-species')\n          .value =\n          'all';\n      }\n    }\n\n\n    \/* =====================================================\n       SUMMARY CARDS\n    ====================================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n\n      var total =\n        melakaProduction(\n          s.year,\n          s.district,\n          s.cropType,\n          s.species\n        );\n\n\n      var districts =\n        getDistricts();\n\n\n      var districtData =\n        districts.map(\n          function(district){\n\n            return {\n\n              district:district,\n\n              value:\n                melakaProduction(\n                  s.year,\n                  district,\n                  s.cropType,\n                  s.species\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var highest =\n        districtData.length\n          ? districtData[0]\n          : null;\n\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n\n      var previousTotal =\n        previousYear !== null\n          ? melakaProduction(\n              previousYear,\n              s.district,\n              s.cropType,\n              s.species\n            )\n          : null;\n\n\n      var change =\n        previousYear !== null\n          ? percentChange(\n              total,\n              previousTotal\n            )\n          : null;\n\n\n      el('cropprod-stat-year')\n        .textContent =\n        s.year || '\u2014';\n\n\n      el('cropprod-stat-total')\n        .textContent =\n        valid(total)\n          ? fmt(total,1)\n          : '\u2014';\n\n\n      el('cropprod-stat-high')\n        .textContent =\n        highest\n          ? fmt(\n              highest.value,\n              1\n            )\n          : '\u2014';\n\n\n      el('cropprod-stat-high-name')\n        .textContent =\n        highest\n          ? (\n              highest.district +\n              ' \u00b7 tan metrik'\n            )\n          : '\u2014';\n\n\n      el('cropprod-stat-change')\n        .textContent =\n        valid(change)\n          ? signedPct(change)\n          : '\u2014';\n\n\n      el('cropprod-stat-change-sub')\n        .textContent =\n        previousYear !== null\n          ? (\n              'berbanding ' +\n              previousYear\n            )\n          : 'tiada tahun sebelumnya';\n\n\n      el('cropprod-stats')\n        .style.display =\n        '';\n    }\n\n\n    \/* =====================================================\n       TOOLTIPS\n    ====================================================== *\/\n\n    function tooltipProduction(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.y,\n                1\n              ) +\n              ' tan metrik'\n            );\n          }\n        }\n      };\n    }\n\n\n    function tooltipHorizontalProduction(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.x,\n                1\n              ) +\n              ' tan metrik'\n            );\n          }\n        }\n      };\n    }\n\n\n    \/* =====================================================\n       TREND\n    ====================================================== *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var years =\n        availableYears();\n\n\n      el('cropprod-trend-sub')\n        .textContent =\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart('trend');\n\n\n      charts.trend =\n        new Chart(\n          el('cropprod-chart-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Tanaman',\n\n                data:\n                  years.map(\n                    function(year){\n\n                      return melakaProduction(\n                        year,\n                        s.district,\n                        s.cropType,\n                        s.species\n                      );\n                    }\n                  ),\n\n                borderColor:'#2563eb',\n\n                backgroundColor:\n                  'rgba(37,99,235,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var districts =\n        getDistricts();\n\n      var colours = [\n        '#2563eb',\n        '#f97316',\n        '#16a34a',\n        '#7c3aed',\n        '#dc2626',\n        '#0891b2'\n      ];\n\n\n      destroyChart(\n        'districtTrend'\n      );\n\n\n      charts.districtTrend =\n        new Chart(\n          el('cropprod-chart-district-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:\n                districts.map(\n                  function(district,index){\n\n                    return {\n\n                      label:district,\n\n                      data:\n                        years.map(\n                          function(year){\n\n                            return melakaProduction(\n                              year,\n                              district,\n                              s.cropType,\n                              s.species\n                            );\n                          }\n                        ),\n\n                      borderColor:\n                        colours[\n                          index %\n                          colours.length\n                        ],\n\n                      tension:.35,\n\n                      borderWidth:2.3,\n\n                      pointRadius:3\n                    };\n                  }\n                )\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipProduction()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       DISTRICT\n    ====================================================== *\/\n\n    function buildDistrictCharts(){\n\n      var s =\n        getSelected();\n\n      var districts =\n        getDistricts();\n\n\n      var data =\n        districts.map(\n          function(district){\n\n            return {\n\n              district:district,\n\n              value:\n                melakaProduction(\n                  s.year,\n                  district,\n                  s.cropType,\n                  s.species\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('cropprod-district-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart('district');\n\n\n      charts.district =\n        new Chart(\n          el('cropprod-chart-district')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.district;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(37,99,235,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var cropTypes =\n        getCropTypes();\n\n      var typeColours = [\n        'rgba(37,99,235,.78)',\n        'rgba(22,163,74,.78)',\n        'rgba(249,115,22,.78)',\n        'rgba(124,58,237,.78)',\n        'rgba(8,145,178,.78)',\n        'rgba(220,38,38,.78)',\n        'rgba(100,116,139,.78)',\n        'rgba(13,148,136,.78)',\n        'rgba(147,51,234,.78)'\n      ];\n\n\n      destroyChart(\n        'districtType'\n      );\n\n\n      charts.districtType =\n        new Chart(\n          el('cropprod-chart-district-type')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:districts,\n\n              datasets:\n                cropTypes.map(\n                  function(type,index){\n\n                    return {\n\n                      label:\n                        cropTypeLabel(\n                          type\n                        ),\n\n                      data:\n                        districts.map(\n                          function(district){\n\n                            return melakaProduction(\n                              s.year,\n                              district,\n                              type,\n                              'all'\n                            );\n                          }\n                        ),\n\n                      backgroundColor:\n                        typeColours[\n                          index %\n                          typeColours.length\n                        ],\n\n                      borderRadius:4\n                    };\n                  }\n                )\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipProduction()\n              },\n\n              scales:{\n\n                x:{\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  stacked:true,\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TYPE\n    ====================================================== *\/\n\n    function buildTypeCharts(){\n\n      var s =\n        getSelected();\n\n      var cropTypes =\n        getCropTypes();\n\n\n      var data =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              type:type,\n\n              label:\n                cropTypeLabel(type),\n\n              value:\n                melakaProduction(\n                  s.year,\n                  s.district,\n                  type,\n                  'all'\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('cropprod-type-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        );\n\n\n      destroyChart('type');\n\n\n      charts.type =\n        new Chart(\n          el('cropprod-chart-type')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(37,99,235,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              indexAxis:'y',\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalProduction()\n              },\n\n              scales:{\n\n                x:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      destroyChart(\n        'typeShare'\n      );\n\n\n      charts.typeShare =\n        new Chart(\n          el('cropprod-chart-type-share')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#16a34a',\n                  '#f97316',\n                  '#7c3aed',\n                  '#0891b2',\n                  '#dc2626',\n                  '#64748b',\n                  '#0d9488',\n                  '#9333ea'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' tan metrik (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       SPECIES\n    ====================================================== *\/\n\n    function buildSpeciesCharts(){\n\n      var s =\n        getSelected();\n\n      var species =\n        getSpecies(\n          s.cropType\n        );\n\n\n      var data =\n        species.map(\n          function(item){\n\n            return {\n\n              species:item,\n\n              label:\n                speciesLabel(item),\n\n              value:\n                melakaProduction(\n                  s.year,\n                  s.district,\n                  s.cropType,\n                  item\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('cropprod-species-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        );\n\n\n      destroyChart('species');\n\n\n      charts.species =\n        new Chart(\n          el('cropprod-chart-species')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(124,58,237,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              indexAxis:'y',\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalProduction()\n              },\n\n              scales:{\n\n                x:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var topSpecies =\n        data.slice(\n          0,\n          Math.min(\n            10,\n            data.length\n          )\n        );\n\n\n      destroyChart(\n        'speciesTop'\n      );\n\n\n      charts.speciesTop =\n        new Chart(\n          el('cropprod-chart-species-top')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                topSpecies.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Tanaman',\n\n                data:\n                  topSpecies.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(22,163,74,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       COMPOSITION\n    ====================================================== *\/\n\n    function buildCompositionCharts(){\n\n      var s =\n        getSelected();\n\n      var districts =\n        getDistricts();\n\n\n      var districtData =\n        districts.map(\n          function(district){\n\n            return {\n\n              label:district,\n\n              value:\n                melakaProduction(\n                  s.year,\n                  district,\n                  s.cropType,\n                  s.species\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        });\n\n\n      el('cropprod-composition-district-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart(\n        'districtShare'\n      );\n\n\n      charts.districtShare =\n        new Chart(\n          el('cropprod-chart-district-share')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                districtData.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran',\n\n                data:\n                  districtData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#f97316',\n                  '#16a34a',\n                  '#7c3aed',\n                  '#0891b2'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' tan metrik (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var cropTypes =\n        getCropTypes();\n\n\n      var typeData =\n        cropTypes.map(\n          function(type){\n\n            return {\n\n              label:\n                cropTypeLabel(type),\n\n              value:\n                melakaProduction(\n                  s.year,\n                  s.district,\n                  type,\n                  'all'\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return (\n            valid(item.value) &&\n            item.value > 0\n          );\n        });\n\n\n      el('cropprod-composition-type-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        );\n\n\n      destroyChart(\n        'compositionType'\n      );\n\n\n      charts.compositionType =\n        new Chart(\n          el('cropprod-chart-composition-type')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                typeData.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran',\n\n                data:\n                  typeData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#16a34a',\n                  '#f97316',\n                  '#7c3aed',\n                  '#0891b2',\n                  '#dc2626',\n                  '#64748b',\n                  '#0d9488',\n                  '#9333ea'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' tan metrik (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       CHANGE\n    ====================================================== *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n\n      if(previousYear === null){\n\n        el('cropprod-change-sub')\n          .textContent =\n          s.year +\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n\n        el('cropprod-change-trend-sub')\n          .textContent =\n          'Tiada tahun sebelumnya tersedia.';\n\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n\n      var districts =\n        getDistricts();\n\n\n      var data =\n        districts.map(\n          function(district){\n\n            var current =\n              melakaProduction(\n                s.year,\n                district,\n                s.cropType,\n                s.species\n              );\n\n\n            var previous =\n              melakaProduction(\n                previousYear,\n                district,\n                s.cropType,\n                s.species\n              );\n\n\n            return {\n\n              district:district,\n\n              value:\n                valid(current) &&\n                valid(previous)\n                  ? percentChange(\n                      current,\n                      previous\n                    )\n                  : null\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('cropprod-change-sub')\n        .textContent =\n\n        s.year +\n        ' berbanding ' +\n        previousYear +\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart('change');\n\n\n      charts.change =\n        new Chart(\n          el('cropprod-chart-change')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.district;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.value >= 0\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var years =\n        availableYears();\n\n      var trend = [];\n\n\n      years.forEach(\n        function(year,index){\n\n          if(index === 0){\n            return;\n          }\n\n\n          var previous =\n            years[index-1];\n\n\n          var currentValue =\n            melakaProduction(\n              year,\n              s.district,\n              s.cropType,\n              s.species\n            );\n\n\n          var previousValue =\n            melakaProduction(\n              previous,\n              s.district,\n              s.cropType,\n              s.species\n            );\n\n\n          if(\n            valid(currentValue) &&\n            valid(previousValue)\n          ){\n\n            trend.push({\n\n              label:\n                year +\n                ' berbanding ' +\n                previous,\n\n              value:\n                percentChange(\n                  currentValue,\n                  previousValue\n                )\n            });\n          }\n        }\n      );\n\n\n      el('cropprod-change-trend-sub')\n        .textContent =\n\n        'Perubahan tahunan \u00b7 ' +\n\n        (\n          s.district === 'all'\n            ? 'Semua Daerah'\n            : s.district\n        ) +\n\n        ' \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart(\n        'changeTrend'\n      );\n\n\n      charts.changeTrend =\n        new Chart(\n          el('cropprod-chart-change-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                trend.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Tahunan',\n\n                data:\n                  trend.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n\n                backgroundColor:\n                  'rgba(124,58,237,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       STATE\n    ====================================================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n      var states =\n        getStates();\n\n\n      var data =\n        states.map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                stateProduction(\n                  state,\n                  s.year,\n                  s.cropType,\n                  s.species\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('cropprod-state-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 semua negeri \u00b7 ' +\n\n        cropTypeLabel(\n          s.cropType\n        ) +\n\n        ' \u00b7 ' +\n\n        speciesLabel(\n          s.species\n        );\n\n\n      destroyChart('state');\n\n\n      charts.state =\n        new Chart(\n          el('cropprod-chart-state')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Tanaman',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label ===\n                          'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var cropTypes =\n        getCropTypes();\n\n      var stateOrder =\n        data.map(\n          function(item){\n            return item.state;\n          }\n        );\n\n\n      var typeColours = [\n        'rgba(37,99,235,.75)',\n        'rgba(22,163,74,.75)',\n        'rgba(249,115,22,.75)',\n        'rgba(124,58,237,.75)',\n        'rgba(8,145,178,.75)',\n        'rgba(220,38,38,.75)',\n        'rgba(100,116,139,.75)',\n        'rgba(13,148,136,.75)',\n        'rgba(147,51,234,.75)'\n      ];\n\n\n      destroyChart(\n        'stateType'\n      );\n\n\n      charts.stateType =\n        new Chart(\n          el('cropprod-chart-state-type')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                stateOrder,\n\n              datasets:\n                cropTypes.map(\n                  function(type,index){\n\n                    return {\n\n                      label:\n                        cropTypeLabel(\n                          type\n                        ),\n\n                      data:\n                        stateOrder.map(\n                          function(state){\n\n                            return stateProduction(\n                              state,\n                              s.year,\n                              type,\n                              'all'\n                            );\n                          }\n                        ),\n\n                      backgroundColor:\n                        stateOrder.map(\n                          function(state){\n\n                            if(\n                              state ===\n                              'Melaka'\n                            ){\n\n                              return (\n                                index % 2 === 0\n                                  ? 'rgba(249,115,22,.95)'\n                                  : 'rgba(249,115,22,.60)'\n                              );\n                            }\n\n                            return typeColours[\n                              index %\n                              typeColours.length\n                            ];\n                          }\n                        ),\n\n                      borderColor:\n                        stateOrder.map(\n                          function(state){\n\n                            return (\n                              state ===\n                              'Melaka'\n                            )\n                              ? '#f97316'\n                              : 'transparent';\n                          }\n                        ),\n\n                      borderWidth:\n                        stateOrder.map(\n                          function(state){\n\n                            return (\n                              state ===\n                              'Melaka'\n                            )\n                              ? 1.5\n                              : 0;\n                          }\n                        )\n                    };\n                  }\n                )\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipProduction()\n              },\n\n              scales:{\n\n                x:{\n\n                  stacked:true,\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  stacked:true,\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TAB CONTROL\n    ====================================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#cropprod-wrap .tbtn.active'\n        );\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n\n    function redraw(){\n\n      updateCards();\n\n      var tab =\n        activeTab();\n\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n      if(tab === 'district'){\n        buildDistrictCharts();\n      }\n\n      if(tab === 'type'){\n        buildTypeCharts();\n      }\n\n      if(tab === 'species'){\n        buildSpeciesCharts();\n      }\n\n      if(tab === 'composition'){\n        buildCompositionCharts();\n      }\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n\n    \/* =====================================================\n       TAB EVENTS\n    ====================================================== *\/\n\n    document\n      .querySelectorAll(\n        '#cropprod-wrap .tbtn'\n      )\n      .forEach(\n        function(btn){\n\n          btn.addEventListener(\n            'click',\n            function(){\n\n              document\n                .querySelectorAll(\n                  '#cropprod-wrap .tbtn'\n                )\n                .forEach(\n                  function(button){\n\n                    button.classList\n                      .remove(\n                        'active'\n                      );\n                  }\n                );\n\n\n              btn.classList\n                .add(\n                  'active'\n                );\n\n\n              [\n                'trend',\n                'district',\n                'type',\n                'species',\n                'composition',\n                'change',\n                'state'\n              ]\n              .forEach(\n                function(tab){\n\n                  el(\n                    'cropprod-tab-' +\n                    tab\n                  ).style.display =\n                    'none';\n                }\n              );\n\n\n              el(\n                'cropprod-tab-' +\n                btn.getAttribute(\n                  'data-tab'\n                )\n              ).style.display =\n                '';\n\n\n              redraw();\n            }\n          );\n        }\n      );\n\n\n    \/* =====================================================\n       FILTER EVENTS\n    ====================================================== *\/\n\n    el('cropprod-type')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateSpeciesFilter();\n\n          redraw();\n        }\n      );\n\n\n    [\n      'cropprod-year',\n      'cropprod-district',\n      'cropprod-species'\n    ]\n    .forEach(\n      function(id){\n\n        el(id)\n          .addEventListener(\n            'change',\n            redraw\n          );\n      }\n    );\n\n\n    \/* =====================================================\n       FETCH\n    ====================================================== *\/\n\n    fetch(API_URL)\n\n      .then(\n        function(response){\n\n          if(!response.ok){\n\n            throw new Error(\n              'HTTP ' +\n              response.status\n            );\n          }\n\n          return response.json();\n        }\n      )\n\n      .then(\n        function(data){\n\n          allRows =\n            Array.isArray(data)\n              ? data\n              : [];\n\n\n          melakaRows =\n            allRows.filter(\n              function(row){\n\n                return (\n                  String(\n                    row.state || ''\n                  )\n                  .trim()\n                  .toLowerCase()\n                  ===\n                  'melaka'\n                );\n              }\n            );\n\n\n          if(!melakaRows.length){\n\n            throw new Error(\n              'Tiada rekod pengeluaran tanaman Melaka ditemui.'\n            );\n          }\n\n\n          populateFilters();\n\n\n          el('cropprod-loading')\n            .style.display =\n            'none';\n\n\n          el('cropprod-area')\n            .style.display =\n            '';\n\n\n          redraw();\n        }\n      )\n\n      .catch(\n        function(error){\n\n          el('cropprod-loading')\n            .innerHTML =\n\n            '<div style=\"' +\n            'color:#dc2626;' +\n            'font-size:16px;' +\n            'font-weight:800;' +\n            'margin-bottom:8px' +\n            '\">' +\n\n            'Gagal memuatkan data' +\n\n            '<\/div>' +\n\n            '<p style=\"' +\n            'font-size:13px;' +\n            'color:#64748b' +\n            '\">' +\n\n            error.message +\n\n            '<\/p>';\n        }\n      );\n\n  }\n\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#fishland-wrap, #fishland-wrap *{\n  box-sizing:border-box;\n}\n\n#fishland-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(\n    180deg,\n    #ffffff 0%,\n    var(--bg) 100%\n  );\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#fishland-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#fishland-wrap .hdr,\n#fishland-wrap .cc,\n#fishland-wrap .sc,\n#fishland-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#fishland-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#fishland-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#fishland-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#fishland-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#fishland-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(3,1fr);\n  gap:14px;\n  background:#ffffff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#fishland-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#fishland-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#fishland-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#ffffff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#fishland-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#fishland-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#fishland-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#fishland-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#fishland-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#fishland-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n  line-height:1.35;\n}\n\n#fishland-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#fishland-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#fishland-wrap .tbtn.active{\n  background:#ffffff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#fishland-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#fishland-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#fishland-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n  line-height:1.45;\n}\n\n#fishland-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#fishland-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#fishland-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#fishland-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#fishland-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:fishland-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes fishland-spin{\n  to{\n    transform:rotate(360deg);\n  }\n}\n\n@media(max-width:1024px){\n\n  #fishland-wrap .filter-bar,\n  #fishland-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #fishland-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #fishland-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #fishland-wrap .filter-bar,\n  #fishland-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #fishland-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #fishland-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #fishland-wrap .chart-box,\n  #fishland-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n\n<div id=\"fishland-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Pendaratan Ikan Laut Melaka\n      <\/div>\n\n      <h2>\n  Papan Pemuka Pendaratan Ikan Laut Bulanan\n<\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi jumlah pendaratan ikan laut di Melaka secara bulanan\n        mengikut pantai. Dashboard ini menunjukkan trend pendaratan,\n        jumlah tahunan, corak bulanan, perubahan bulan ke bulan,\n        tempoh pendaratan tertinggi dan perbandingan Melaka dengan\n        negeri-negeri lain.\n      <\/p>\n\n    <\/div>\n\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"fishland-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Bulan<\/label>\n        <select id=\"fishland-month\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Pantai<\/label>\n        <select id=\"fishland-coast\"><\/select>\n      <\/div>\n\n    <\/div>\n\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"fishland-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tempoh Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"fishland-stat-period\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Pendaratan Bulanan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"fishland-stat-month\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          tan metrik\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Jumlah Tahun Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"fishland-stat-year-total\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          tan metrik\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Perubahan Bulanan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"fishland-stat-change\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"fishland-stat-change-sub\"\n        >\n          berbanding bulan sebelumnya\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"fishland-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod pendaratan ikan laut Melaka.\n      <\/div>\n\n    <\/div>\n\n\n    <!-- DASHBOARD -->\n    <div\n      id=\"fishland-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TAB -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"monthly\"\n        >\n          Bulanan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"coast\"\n        >\n          Pantai\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"seasonal\"\n        >\n          Corak Bulanan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"highlow\"\n        >\n          Tertinggi &#038; Terendah\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           TREND\n      ====================================================== -->\n      <div id=\"fishland-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Pendaratan Ikan Laut\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"fishland-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Jumlah Pendaratan Tahunan\n            <\/div>\n\n            <div class=\"cs\">\n              Jumlah keseluruhan pendaratan ikan laut bagi setiap tahun.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-annual-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           MONTHLY\n      ====================================================== -->\n      <div\n        id=\"fishland-tab-monthly\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Pendaratan Mengikut Bulan\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"fishland-monthly-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"fishland-chart-monthly\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perubahan Bulan ke Bulan\n          <\/div>\n\n          <div class=\"cs\">\n            Perubahan peratus jumlah pendaratan ikan laut\n            berbanding bulan sebelumnya.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"fishland-chart-monthly-change\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           COAST\n      ====================================================== -->\n      <div\n        id=\"fishland-tab-coast\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pendaratan Mengikut Pantai\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"fishland-coast-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-coast\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Sumbangan Pendaratan Mengikut Pantai\n            <\/div>\n\n            <div class=\"cs\">\n              Peratus sumbangan setiap kumpulan pantai kepada\n              jumlah pendaratan dalam rekod yang tersedia.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-coast-share\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           SEASONAL\n      ====================================================== -->\n      <div\n        id=\"fishland-tab-seasonal\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Purata Pendaratan Mengikut Bulan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"fishland-seasonal-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-seasonal\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Corak Pendaratan Bulanan Mengikut Tahun\n            <\/div>\n\n            <div class=\"cs\">\n              Perbandingan corak Januari hingga Disember\n              bagi setiap tahun yang tersedia.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-seasonal-years\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           HIGH LOW\n      ====================================================== -->\n      <div\n        id=\"fishland-tab-highlow\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Bulan dengan Pendaratan Tertinggi\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"fishland-high-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-high\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Bulan dengan Pendaratan Terendah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"fishland-low-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-low\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           CHANGE\n      ====================================================== -->\n      <div\n        id=\"fishland-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Bulanan dalam Tahun Dipilih\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"fishland-change-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-change\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Perubahan Tahun ke Tahun\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"fishland-yoy-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"fishland-chart-yoy\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           STATE\n      ====================================================== -->\n      <div\n        id=\"fishland-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Pendaratan Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"fishland-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"fishland-chart-state\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Jumlah Tahunan Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Jumlah pendaratan bagi tahun dipilih.\n            Melaka ditonjolkan dalam warna oren.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"fishland-chart-state-annual\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=fish_landings&limit=2000';\n\n    var FIXED_STATE =\n      'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n\n    Chart.defaults.color =\n      '#64748b';\n\n    Chart.defaults.borderColor =\n      '#dbeafe';\n\n    Chart.defaults.font.family =\n      'Arial, sans-serif';\n\n\n    \/* =====================================================\n       BASIC HELPERS\n    ====================================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === ''\n      ){\n        return null;\n      }\n\n      var n =\n        Number(value);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n\n    function valid(value){\n\n      return (\n        value !== null &&\n        value !== undefined &&\n        !isNaN(value)\n      );\n    }\n\n\n    function fmt(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n      return Number(value)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:d,\n            maximumFractionDigits:d\n          }\n        );\n    }\n\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,1) +\n        '%'\n      );\n    }\n\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n\n    function parseDate(row){\n\n      var value =\n        String(row.date || '');\n\n      if(\n        !\/^\\d{4}-\\d{2}-\\d{2}$\/.test(\n          value\n        )\n      ){\n        return null;\n      }\n\n      var parts =\n        value.split('-');\n\n      return {\n\n        year:\n          parseInt(\n            parts[0],\n            10\n          ),\n\n        month:\n          parseInt(\n            parts[1],\n            10\n          )\n      };\n    }\n\n\n    function rowYear(row){\n\n      var p =\n        parseDate(row);\n\n      return p\n        ? p.year\n        : null;\n    }\n\n\n    function rowMonth(row){\n\n      var p =\n        parseDate(row);\n\n      return p\n        ? p.month\n        : null;\n    }\n\n\n    function periodKey(row){\n\n      return (\n        rowYear(row) * 100 +\n        rowMonth(row)\n      );\n    }\n\n\n    function monthName(month){\n\n      var names = [\n        '',\n        'Januari',\n        'Februari',\n        'Mac',\n        'April',\n        'Mei',\n        'Jun',\n        'Julai',\n        'Ogos',\n        'September',\n        'Oktober',\n        'November',\n        'Disember'\n      ];\n\n      return names[month] || '';\n    }\n\n\n    function shortMonth(month){\n\n      var names = [\n        '',\n        'Jan',\n        'Feb',\n        'Mac',\n        'Apr',\n        'Mei',\n        'Jun',\n        'Jul',\n        'Ogo',\n        'Sep',\n        'Okt',\n        'Nov',\n        'Dis'\n      ];\n\n      return names[month] || '';\n    }\n\n\n    function fullPeriod(\n      year,\n      month\n    ){\n\n      return (\n        monthName(month) +\n        ' ' +\n        year\n      );\n    }\n\n\n    function periodLabel(row){\n\n      return (\n        shortMonth(\n          rowMonth(row)\n        ) +\n        ' ' +\n        rowYear(row)\n      );\n    }\n\n\n    function coastLabel(coast){\n\n      var labels = {\n\n        all:\n          'Semua Pantai',\n\n        east:\n          'Pantai Timur Semenanjung',\n\n        west:\n          'Pantai Barat Semenanjung',\n\n        borneo:\n          'Borneo'\n      };\n\n      return (\n        labels[coast] ||\n        coast\n      );\n    }\n\n\n    function percentChange(\n      current,\n      previous\n    ){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n      return (\n        (\n          current -\n          previous\n        ) \/\n        previous\n      ) * 100;\n    }\n\n\n    \/* =====================================================\n       DATA VALUES\n    ====================================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(rowYear)\n            .filter(function(year){\n              return year !== null;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n\n    function monthsForYear(year){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n              return rowYear(row) === year;\n            })\n            .map(rowMonth)\n            .filter(function(month){\n              return month !== null;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n\n    function getCoasts(){\n\n      var values =\n        Array.from(\n          new Set(\n            melakaRows\n              .map(function(row){\n                return row.coast;\n              })\n              .filter(Boolean)\n          )\n        );\n\n\n      return values.sort(\n        function(a,b){\n\n          if(a === 'all'){\n            return -1;\n          }\n\n          if(b === 'all'){\n            return 1;\n          }\n\n          return coastLabel(a)\n            .localeCompare(\n              coastLabel(b),\n              'ms'\n            );\n        }\n      );\n    }\n\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(function(state){\n\n              return (\n                state &&\n                state !== 'Malaysia'\n              );\n            })\n        )\n      )\n      .sort();\n    }\n\n\n    function getSelected(){\n\n      return {\n\n        year:\n          parseInt(\n            el('fishland-year').value,\n            10\n          ),\n\n        month:\n          parseInt(\n            el('fishland-month').value,\n            10\n          ),\n\n        coast:\n          el('fishland-coast').value\n      };\n    }\n\n\n    \/* =====================================================\n       LANDING AGGREGATION\n    ====================================================== *\/\n\n    function rowsForPeriod(\n      source,\n      state,\n      year,\n      month\n    ){\n\n      return source.filter(\n        function(row){\n\n          return (\n            row.state === state &&\n            rowYear(row) === year &&\n            rowMonth(row) === month\n          );\n        }\n      );\n    }\n\n\n    \/*\n      Important:\n      If coast = all and an explicit \"all\" row exists,\n      use that row only.\n\n      This prevents:\n      all + east + west + borneo\n      from being summed together.\n    *\/\n\n    function landingFor(\n      source,\n      state,\n      year,\n      month,\n      coast\n    ){\n\n      var periodRows =\n        rowsForPeriod(\n          source,\n          state,\n          year,\n          month\n        );\n\n\n      if(coast === 'all'){\n\n        var aggregateRows =\n          periodRows.filter(\n            function(row){\n\n              return row.coast === 'all';\n            }\n          );\n\n\n        if(aggregateRows.length){\n\n          var aggregateValues =\n            aggregateRows\n            .map(function(row){\n              return num(row.landings);\n            })\n            .filter(valid);\n\n\n          if(!aggregateValues.length){\n            return null;\n          }\n\n\n          return aggregateValues.reduce(\n            function(a,b){\n              return a+b;\n            },\n            0\n          );\n        }\n\n\n        var detailed =\n          periodRows\n          .filter(function(row){\n            return row.coast !== 'all';\n          })\n          .map(function(row){\n            return num(row.landings);\n          })\n          .filter(valid);\n\n\n        if(!detailed.length){\n          return null;\n        }\n\n\n        return detailed.reduce(\n          function(a,b){\n            return a+b;\n          },\n          0\n        );\n      }\n\n\n      var selected =\n        periodRows\n        .filter(function(row){\n\n          return row.coast === coast;\n        })\n        .map(function(row){\n          return num(row.landings);\n        })\n        .filter(valid);\n\n\n      if(!selected.length){\n        return null;\n      }\n\n\n      return selected.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      );\n    }\n\n\n    function melakaLanding(\n      year,\n      month,\n      coast\n    ){\n\n      return landingFor(\n        melakaRows,\n        FIXED_STATE,\n        year,\n        month,\n        coast\n      );\n    }\n\n\n    function stateLanding(\n      state,\n      year,\n      month,\n      coast\n    ){\n\n      return landingFor(\n        allRows,\n        state,\n        year,\n        month,\n        coast\n      );\n    }\n\n\n    function annualLanding(\n      source,\n      state,\n      year,\n      coast\n    ){\n\n      var months = [];\n\n      for(\n        var month=1;\n        month<=12;\n        month++\n      ){\n\n        var value =\n          landingFor(\n            source,\n            state,\n            year,\n            month,\n            coast\n          );\n\n\n        if(valid(value)){\n          months.push(value);\n        }\n      }\n\n\n      if(!months.length){\n        return null;\n      }\n\n\n      return months.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      );\n    }\n\n\n    function melakaAnnual(\n      year,\n      coast\n    ){\n\n      return annualLanding(\n        melakaRows,\n        FIXED_STATE,\n        year,\n        coast\n      );\n    }\n\n\n    \/* =====================================================\n       PREVIOUS PERIOD\n    ====================================================== *\/\n\n    function availablePeriods(\n      coast\n    ){\n\n      var values = [];\n\n\n      availableYears()\n        .forEach(\n          function(year){\n\n            for(\n              var month=1;\n              month<=12;\n              month++\n            ){\n\n              var value =\n                melakaLanding(\n                  year,\n                  month,\n                  coast\n                );\n\n\n              if(valid(value)){\n\n                values.push({\n\n                  year:year,\n\n                  month:month,\n\n                  value:value,\n\n                  key:\n                    year * 100 +\n                    month\n                });\n              }\n            }\n          }\n        );\n\n\n      return values.sort(\n        function(a,b){\n          return a.key-b.key;\n        }\n      );\n    }\n\n\n    function previousPeriod(\n      year,\n      month,\n      coast\n    ){\n\n      var key =\n        year * 100 +\n        month;\n\n\n      var periods =\n        availablePeriods(coast)\n        .filter(function(item){\n          return item.key < key;\n        });\n\n\n      return periods.length\n        ? periods[\n            periods.length-1\n          ]\n        : null;\n    }\n\n\n    function previousYearSameMonth(\n      year,\n      month,\n      coast\n    ){\n\n      var years =\n        availableYears()\n        .filter(function(y){\n\n          return (\n            y < year &#038;&#038;\n            valid(\n              melakaLanding(\n                y,\n                month,\n                coast\n              )\n            )\n          );\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n\n      if(!years.length){\n        return null;\n      }\n\n\n      return {\n\n        year:\n          years[0],\n\n        month:\n          month,\n\n        value:\n          melakaLanding(\n            years[0],\n            month,\n            coast\n          )\n      };\n    }\n\n\n    \/* =====================================================\n       FILTER POPULATION\n    ====================================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n\n      el('fishland-year')\n        .innerHTML =\n\n        years.map(\n          function(year){\n\n            return (\n              '<option value=\"' +\n              year +\n              '\">' +\n              year +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(years.length){\n\n        el('fishland-year')\n          .value =\n          years[0];\n      }\n\n\n      populateMonthFilter();\n\n\n      var coasts =\n        getCoasts();\n\n\n      el('fishland-coast')\n        .innerHTML =\n\n        coasts.map(\n          function(coast){\n\n            return (\n              '<option value=\"' +\n              coast +\n              '\">' +\n              coastLabel(coast) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(\n        coasts.indexOf('all') !== -1\n      ){\n\n        el('fishland-coast')\n          .value =\n          'all';\n      }\n    }\n\n\n    function populateMonthFilter(){\n\n      var year =\n        parseInt(\n          el('fishland-year').value,\n          10\n        );\n\n\n      var months =\n        monthsForYear(year);\n\n\n      var current =\n        parseInt(\n          el('fishland-month').value,\n          10\n        );\n\n\n      el('fishland-month')\n        .innerHTML =\n\n        months.map(\n          function(month){\n\n            return (\n              '<option value=\"' +\n              month +\n              '\">' +\n              monthName(month) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(\n        current &&\n        months.indexOf(current) !== -1\n      ){\n\n        el('fishland-month')\n          .value =\n          current;\n\n      }else if(months.length){\n\n        el('fishland-month')\n          .value =\n          months[\n            months.length-1\n          ];\n      }\n    }\n\n\n    \/* =====================================================\n       SUMMARY CARDS\n    ====================================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n\n      var current =\n        melakaLanding(\n          s.year,\n          s.month,\n          s.coast\n        );\n\n\n      var annual =\n        melakaAnnual(\n          s.year,\n          s.coast\n        );\n\n\n      var previous =\n        previousPeriod(\n          s.year,\n          s.month,\n          s.coast\n        );\n\n\n      var change =\n        previous\n          ? percentChange(\n              current,\n              previous.value\n            )\n          : null;\n\n\n      el('fishland-stat-period')\n        .textContent =\n        fullPeriod(\n          s.year,\n          s.month\n        );\n\n\n      el('fishland-stat-month')\n        .textContent =\n        valid(current)\n          ? fmt(current,1)\n          : '\u2014';\n\n\n      el('fishland-stat-year-total')\n        .textContent =\n        valid(annual)\n          ? fmt(annual,1)\n          : '\u2014';\n\n\n      el('fishland-stat-change')\n        .textContent =\n        valid(change)\n          ? signedPct(change)\n          : '\u2014';\n\n\n      el('fishland-stat-change-sub')\n        .textContent =\n        previous\n          ? (\n              'berbanding ' +\n              fullPeriod(\n                previous.year,\n                previous.month\n              )\n            )\n          : 'tiada bulan sebelumnya';\n\n\n      el('fishland-stats')\n        .style.display =\n        '';\n    }\n\n\n    \/* =====================================================\n       TOOLTIP\n    ====================================================== *\/\n\n    function tooltipLanding(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.y,\n                1\n              ) +\n              ' tan metrik'\n            );\n          }\n        }\n      };\n    }\n\n\n    function tooltipHorizontalLanding(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.x,\n                1\n              ) +\n              ' tan metrik'\n            );\n          }\n        }\n      };\n    }\n\n\n    \/* =====================================================\n       TREND\n    ====================================================== *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n\n      var periods =\n        availablePeriods(\n          s.coast\n        );\n\n\n      el('fishland-trend-sub')\n        .textContent =\n\n        'Melaka \u00b7 ' +\n        coastLabel(\n          s.coast\n        );\n\n\n      destroyChart('trend');\n\n\n      charts.trend =\n        new Chart(\n          el('fishland-chart-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                periods.map(\n                  function(item){\n\n                    return (\n                      shortMonth(\n                        item.month\n                      ) +\n                      ' ' +\n                      item.year\n                    );\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pendaratan Ikan Laut',\n\n                data:\n                  periods.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#2563eb',\n\n                backgroundColor:\n                  'rgba(37,99,235,.15)',\n\n                fill:true,\n\n                tension:.3,\n\n                borderWidth:2.5,\n\n                pointRadius:2\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    autoSkip:true,\n                    maxTicksLimit:14,\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var years =\n        availableYears();\n\n\n      destroyChart('annualTrend');\n\n\n      charts.annualTrend =\n        new Chart(\n          el('fishland-chart-annual-trend')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Jumlah Tahunan',\n\n                data:\n                  years.map(\n                    function(year){\n\n                      return melakaAnnual(\n                        year,\n                        s.coast\n                      );\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(22,163,74,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       MONTHLY\n    ====================================================== *\/\n\n    function buildMonthlyCharts(){\n\n      var s =\n        getSelected();\n\n\n      var months =\n        monthsForYear(\n          s.year\n        );\n\n\n      var values =\n        months.map(\n          function(month){\n\n            return melakaLanding(\n              s.year,\n              month,\n              s.coast\n            );\n          }\n        );\n\n\n      el('fishland-monthly-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 Melaka \u00b7 ' +\n        coastLabel(\n          s.coast\n        );\n\n\n      destroyChart('monthly');\n\n\n      charts.monthly =\n        new Chart(\n          el('fishland-chart-monthly')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                months.map(\n                  monthName\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pendaratan Ikan Laut',\n\n                data:values,\n\n                backgroundColor:\n                  months.map(\n                    function(month){\n\n                      return month === s.month\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.75)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var changes =\n        months.map(\n          function(month){\n\n            var current =\n              melakaLanding(\n                s.year,\n                month,\n                s.coast\n              );\n\n\n            var previous =\n              previousPeriod(\n                s.year,\n                month,\n                s.coast\n              );\n\n\n            return previous\n              ? percentChange(\n                  current,\n                  previous.value\n                )\n              : null;\n          }\n        );\n\n\n      destroyChart(\n        'monthlyChange'\n      );\n\n\n      charts.monthlyChange =\n        new Chart(\n          el('fishland-chart-monthly-change')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                months.map(\n                  monthName\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Bulanan',\n\n                data:changes,\n\n                backgroundColor:\n                  changes.map(\n                    function(value){\n\n                      return (\n                        valid(value) &&\n                        value >= 0\n                      )\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       COAST\n    ====================================================== *\/\n\n    function buildCoastCharts(){\n\n      var s =\n        getSelected();\n\n\n      \/*\n        Exclude \"all\" from composition because\n        it is the aggregate of the detailed coasts.\n      *\/\n\n      var coasts =\n        Array.from(\n          new Set(\n            allRows\n              .map(function(row){\n                return row.coast;\n              })\n              .filter(function(coast){\n\n                return (\n                  coast &&\n                  coast !== 'all'\n                );\n              })\n          )\n        );\n\n\n      var data =\n        coasts.map(\n          function(coast){\n\n            var value =\n              landingFor(\n                allRows,\n                'Malaysia',\n                s.year,\n                s.month,\n                coast\n              );\n\n\n            return {\n\n              coast:coast,\n\n              label:\n                coastLabel(coast),\n\n              value:value\n            };\n          }\n        )\n        .filter(function(item){\n\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('fishland-coast-sub')\n        .textContent =\n\n        fullPeriod(\n          s.year,\n          s.month\n        ) +\n        ' \u00b7 Malaysia';\n\n\n      destroyChart('coast');\n\n\n      charts.coast =\n        new Chart(\n          el('fishland-chart-coast')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pendaratan Ikan Laut',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  'rgba(37,99,235,.78)',\n                  'rgba(22,163,74,.78)',\n                  'rgba(124,58,237,.78)'\n                ],\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      destroyChart(\n        'coastShare'\n      );\n\n\n      charts.coastShare =\n        new Chart(\n          el('fishland-chart-coast-share')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pendaratan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#16a34a',\n                  '#7c3aed'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' tan metrik (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       SEASONAL\n    ====================================================== *\/\n\n    function averageMonth(\n      month,\n      coast\n    ){\n\n      var values = [];\n\n\n      availableYears()\n        .forEach(\n          function(year){\n\n            var value =\n              melakaLanding(\n                year,\n                month,\n                coast\n              );\n\n\n            if(valid(value)){\n              values.push(value);\n            }\n          }\n        );\n\n\n      if(!values.length){\n        return null;\n      }\n\n\n      return values.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      ) \/ values.length;\n    }\n\n\n    function buildSeasonalCharts(){\n\n      var s =\n        getSelected();\n\n\n      var months =\n        [\n          1,2,3,4,5,6,\n          7,8,9,10,11,12\n        ];\n\n\n      el('fishland-seasonal-sub')\n        .textContent =\n\n        'Melaka \u00b7 ' +\n        coastLabel(\n          s.coast\n        ) +\n        ' \u00b7 purata sepanjang tempoh data';\n\n\n      destroyChart('seasonal');\n\n\n      charts.seasonal =\n        new Chart(\n          el('fishland-chart-seasonal')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                months.map(\n                  monthName\n                ),\n\n              datasets:[{\n\n                label:\n                  'Purata Pendaratan',\n\n                data:\n                  months.map(\n                    function(month){\n\n                      return averageMonth(\n                        month,\n                        s.coast\n                      );\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(37,99,235,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var years =\n        availableYears();\n\n\n      var colours = [\n        '#2563eb',\n        '#16a34a',\n        '#f97316',\n        '#7c3aed',\n        '#dc2626',\n        '#0891b2'\n      ];\n\n\n      destroyChart(\n        'seasonalYears'\n      );\n\n\n      charts.seasonalYears =\n        new Chart(\n          el('fishland-chart-seasonal-years')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                months.map(\n                  monthName\n                ),\n\n              datasets:\n                years.map(\n                  function(year,index){\n\n                    return {\n\n                      label:\n                        String(year),\n\n                      data:\n                        months.map(\n                          function(month){\n\n                            return melakaLanding(\n                              year,\n                              month,\n                              s.coast\n                            );\n                          }\n                        ),\n\n                      borderColor:\n                        colours[\n                          index %\n                          colours.length\n                        ],\n\n                      tension:.3,\n\n                      borderWidth:2,\n\n                      pointRadius:2\n                    };\n                  }\n                )\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       HIGH \/ LOW\n    ====================================================== *\/\n\n    function buildHighLowCharts(){\n\n      var s =\n        getSelected();\n\n\n      var data =\n        availablePeriods(\n          s.coast\n        )\n        .filter(function(item){\n\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var limit =\n        Math.min(\n          8,\n          data.length\n        );\n\n\n      var highest =\n        data.slice(\n          0,\n          limit\n        );\n\n\n      var lowest =\n        data.slice(\n          -limit\n        )\n        .sort(function(a,b){\n          return a.value-b.value;\n        });\n\n\n      el('fishland-high-sub')\n        .textContent =\n\n        'Melaka \u00b7 ' +\n        coastLabel(\n          s.coast\n        ) +\n        ' \u00b7 sepanjang tempoh data';\n\n\n      el('fishland-low-sub')\n        .textContent =\n        el('fishland-high-sub')\n          .textContent;\n\n\n      function label(item){\n\n        return (\n          shortMonth(\n            item.month\n          ) +\n          ' ' +\n          item.year\n        );\n      }\n\n\n      destroyChart('high');\n\n\n      charts.high =\n        new Chart(\n          el('fishland-chart-high')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                highest.map(label),\n\n              datasets:[{\n\n                label:\n                  'Pendaratan',\n\n                data:\n                  highest.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(22,163,74,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      destroyChart('low');\n\n\n      charts.low =\n        new Chart(\n          el('fishland-chart-low')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                lowest.map(label),\n\n              datasets:[{\n\n                label:\n                  'Pendaratan',\n\n                data:\n                  lowest.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(220,38,38,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       CHANGE\n    ====================================================== *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n\n      var months =\n        monthsForYear(\n          s.year\n        );\n\n\n      var changes =\n        months.map(\n          function(month){\n\n            var current =\n              melakaLanding(\n                s.year,\n                month,\n                s.coast\n              );\n\n\n            var previous =\n              previousPeriod(\n                s.year,\n                month,\n                s.coast\n              );\n\n\n            return {\n\n              month:month,\n\n              value:\n                previous\n                  ? percentChange(\n                      current,\n                      previous.value\n                    )\n                  : null\n            };\n          }\n        );\n\n\n      el('fishland-change-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 Melaka \u00b7 ' +\n        coastLabel(\n          s.coast\n        );\n\n\n      destroyChart('change');\n\n\n      charts.change =\n        new Chart(\n          el('fishland-chart-change')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                changes.map(\n                  function(item){\n                    return monthName(\n                      item.month\n                    );\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Bulanan',\n\n                data:\n                  changes.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n\n                backgroundColor:\n                  'rgba(124,58,237,.15)',\n\n                fill:true,\n\n                tension:.3,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var periods =\n        availablePeriods(\n          s.coast\n        );\n\n\n      var yoy = [];\n\n\n      periods.forEach(\n        function(item){\n\n          var previous =\n            previousYearSameMonth(\n              item.year,\n              item.month,\n              s.coast\n            );\n\n\n          if(previous){\n\n            yoy.push({\n\n              label:\n                shortMonth(\n                  item.month\n                ) +\n                ' ' +\n                item.year,\n\n              value:\n                percentChange(\n                  item.value,\n                  previous.value\n                )\n            });\n          }\n        }\n      );\n\n\n      el('fishland-yoy-sub')\n        .textContent =\n\n        'Melaka \u00b7 ' +\n        coastLabel(\n          s.coast\n        ) +\n        ' \u00b7 bulan sama berbanding tahun terdahulu yang tersedia';\n\n\n      destroyChart('yoy');\n\n\n      charts.yoy =\n        new Chart(\n          el('fishland-chart-yoy')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                yoy.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Tahun ke Tahun',\n\n                data:\n                  yoy.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#dc2626',\n\n                backgroundColor:\n                  'rgba(220,38,38,.12)',\n\n                fill:true,\n\n                tension:.3,\n\n                borderWidth:2.5,\n\n                pointRadius:2\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    autoSkip:true,\n                    maxTicksLimit:12,\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       STATE\n    ====================================================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n\n      var states =\n        getStates();\n\n\n      var data =\n        states.map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                stateLanding(\n                  state,\n                  s.year,\n                  s.month,\n                  s.coast\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('fishland-state-sub')\n        .textContent =\n\n        fullPeriod(\n          s.year,\n          s.month\n        ) +\n        ' \u00b7 ' +\n        coastLabel(\n          s.coast\n        ) +\n        ' \u00b7 semua negeri yang mempunyai rekod';\n\n\n      destroyChart('state');\n\n\n      charts.state =\n        new Chart(\n          el('fishland-chart-state')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pendaratan Ikan Laut',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label ===\n                          'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var annualData =\n        states.map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                annualLanding(\n                  allRows,\n                  state,\n                  s.year,\n                  s.coast\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      destroyChart(\n        'stateAnnual'\n      );\n\n\n      charts.stateAnnual =\n        new Chart(\n          el('fishland-chart-state-annual')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                annualData.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Jumlah Tahunan',\n\n                data:\n                  annualData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  annualData.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(22,163,74,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  annualData.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  annualData.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipLanding()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TAB CONTROL\n    ====================================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#fishland-wrap .tbtn.active'\n        );\n\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n\n    function redraw(){\n\n      updateCards();\n\n\n      var tab =\n        activeTab();\n\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n\n      if(tab === 'monthly'){\n        buildMonthlyCharts();\n      }\n\n\n      if(tab === 'coast'){\n        buildCoastCharts();\n      }\n\n\n      if(tab === 'seasonal'){\n        buildSeasonalCharts();\n      }\n\n\n      if(tab === 'highlow'){\n        buildHighLowCharts();\n      }\n\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n\n    \/* =====================================================\n       TAB EVENTS\n    ====================================================== *\/\n\n    document\n      .querySelectorAll(\n        '#fishland-wrap .tbtn'\n      )\n      .forEach(\n        function(btn){\n\n          btn.addEventListener(\n            'click',\n            function(){\n\n              document\n                .querySelectorAll(\n                  '#fishland-wrap .tbtn'\n                )\n                .forEach(\n                  function(button){\n\n                    button.classList\n                      .remove(\n                        'active'\n                      );\n                  }\n                );\n\n\n              btn.classList\n                .add(\n                  'active'\n                );\n\n\n              [\n                'trend',\n                'monthly',\n                'coast',\n                'seasonal',\n                'highlow',\n                'change',\n                'state'\n              ]\n              .forEach(\n                function(tab){\n\n                  el(\n                    'fishland-tab-' +\n                    tab\n                  ).style.display =\n                    'none';\n                }\n              );\n\n\n              el(\n                'fishland-tab-' +\n                btn.getAttribute(\n                  'data-tab'\n                )\n              ).style.display =\n                '';\n\n\n              redraw();\n            }\n          );\n        }\n      );\n\n\n    \/* =====================================================\n       FILTER EVENTS\n    ====================================================== *\/\n\n    el('fishland-year')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateMonthFilter();\n\n          redraw();\n        }\n      );\n\n\n    [\n      'fishland-month',\n      'fishland-coast'\n    ]\n    .forEach(\n      function(id){\n\n        el(id)\n          .addEventListener(\n            'change',\n            redraw\n          );\n      }\n    );\n\n\n    \/* =====================================================\n       API FETCH\n    ====================================================== *\/\n\n    fetch(API_URL)\n\n      .then(\n        function(response){\n\n          if(!response.ok){\n\n            throw new Error(\n              'HTTP ' +\n              response.status\n            );\n          }\n\n\n          return response.json();\n        }\n      )\n\n      .then(\n        function(data){\n\n          allRows =\n            Array.isArray(data)\n              ? data\n              : [];\n\n\n          melakaRows =\n            allRows.filter(\n              function(row){\n\n                return (\n                  String(\n                    row.state || ''\n                  )\n                  .trim()\n                  .toLowerCase()\n                  ===\n                  'melaka'\n                );\n              }\n            );\n\n\n          if(!melakaRows.length){\n\n            throw new Error(\n              'Tiada rekod pendaratan ikan laut Melaka ditemui.'\n            );\n          }\n\n\n          populateFilters();\n\n\n          el('fishland-loading')\n            .style.display =\n            'none';\n\n\n          el('fishland-area')\n            .style.display =\n            '';\n\n\n          redraw();\n        }\n      )\n\n      .catch(\n        function(error){\n\n          el('fishland-loading')\n            .innerHTML =\n\n            '<div style=\"' +\n            'color:#dc2626;' +\n            'font-size:16px;' +\n            'font-weight:800;' +\n            'margin-bottom:8px' +\n            '\">' +\n\n            'Gagal memuatkan data' +\n\n            '<\/div>' +\n\n            '<p style=\"' +\n            'font-size:13px;' +\n            'color:#64748b' +\n            '\">' +\n\n            error.message +\n\n            '<\/p>';\n        }\n      );\n\n  }\n\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#mineral-wrap, #mineral-wrap *{\n  box-sizing:border-box;\n}\n\n#mineral-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(\n    180deg,\n    #ffffff 0%,\n    var(--bg) 100%\n  );\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#mineral-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#mineral-wrap .hdr,\n#mineral-wrap .cc,\n#mineral-wrap .sc,\n#mineral-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#mineral-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#mineral-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#mineral-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#mineral-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#mineral-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(3,1fr);\n  gap:14px;\n  background:#ffffff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#mineral-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#mineral-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#mineral-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#ffffff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#mineral-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#mineral-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#mineral-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#mineral-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#mineral-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#mineral-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n  line-height:1.35;\n}\n\n#mineral-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#mineral-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#mineral-wrap .tbtn.active{\n  background:#ffffff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#mineral-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#mineral-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#mineral-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n  line-height:1.45;\n}\n\n#mineral-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#mineral-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#mineral-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#mineral-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#mineral-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:mineral-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes mineral-spin{\n  to{\n    transform:rotate(360deg);\n  }\n}\n\n@media(max-width:1024px){\n\n  #mineral-wrap .filter-bar,\n  #mineral-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #mineral-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #mineral-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #mineral-wrap .filter-bar,\n  #mineral-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #mineral-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #mineral-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #mineral-wrap .chart-box,\n  #mineral-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n\n<div id=\"mineral-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Pengekstrakan Mineral Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Pengekstrakan Mineral Mengikut Negeri dan Komoditi\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi pengekstrakan mineral di Melaka mengikut tahun,\n        jenis komoditi dan komoditi mineral. Dashboard ini memaparkan\n        trend pengekstrakan, komposisi komoditi, perubahan tahunan serta\n        perbandingan Melaka dengan negeri-negeri lain.\n      <\/p>\n\n    <\/div>\n\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label>Tahun<\/label>\n        <select id=\"mineral-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Jenis Komoditi<\/label>\n        <select id=\"mineral-type\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label>Komoditi<\/label>\n        <select id=\"mineral-commodity\"><\/select>\n      <\/div>\n\n    <\/div>\n\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"mineral-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tahun Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"mineral-stat-year\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Pengekstrakan Tan Metrik\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"mineral-stat-tonnes\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          tan metrik\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Emas &#038; Perak\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"mineral-stat-grams\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          gram\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Perubahan Tahunan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"mineral-stat-change\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"mineral-stat-change-sub\"\n        >\n          berbanding tahun sebelumnya\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"mineral-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod pengekstrakan mineral Melaka.\n      <\/div>\n\n    <\/div>\n\n\n    <!-- DASHBOARD -->\n    <div\n      id=\"mineral-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TAB BAR -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"commodity\"\n        >\n          Komoditi\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"type\"\n        >\n          Jenis Mineral\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"highlow\"\n        >\n          Tertinggi &#038; Terendah\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"precious\"\n        >\n          Emas &#038; Perak\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           TREND\n      ====================================================== -->\n      <div id=\"mineral-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Pengekstrakan Mineral\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"mineral-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Jumlah Pengekstrakan Tan Metrik\n            <\/div>\n\n            <div class=\"cs\">\n              Jumlah tahunan komoditi yang menggunakan unit tan metrik.\n              Emas dan perak tidak dicampurkan ke dalam jumlah ini.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-total-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           COMMODITY\n      ====================================================== -->\n      <div\n        id=\"mineral-tab-commodity\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Pengekstrakan Mengikut Komoditi\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"mineral-commodity-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"mineral-chart-commodity\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Sumbangan Komoditi Berasaskan Tan Metrik\n          <\/div>\n\n          <div class=\"cs\">\n            Peratus sumbangan setiap komoditi kepada jumlah pengekstrakan\n            berasaskan tan metrik di Melaka.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"mineral-chart-share\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           TYPE\n      ====================================================== -->\n      <div\n        id=\"mineral-tab-type\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pengekstrakan Mengikut Jenis Mineral\n            <\/div>\n\n            <div class=\"cs\">\n              Jumlah tan metrik bagi mineral tenaga, logam dan bukan logam.\n              Emas dan perak tidak dimasukkan kerana menggunakan unit gram.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-type\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komposisi Jenis Mineral\n            <\/div>\n\n            <div class=\"cs\">\n              Bahagian setiap jenis mineral berdasarkan jumlah\n              pengekstrakan tan metrik.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-type-share\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           HIGH LOW\n      ====================================================== -->\n      <div\n        id=\"mineral-tab-highlow\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komoditi dengan Pengekstrakan Tertinggi\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"mineral-high-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-high\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Komoditi dengan Pengekstrakan Terendah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"mineral-low-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-low\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           CHANGE\n      ====================================================== -->\n      <div\n        id=\"mineral-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Pengekstrakan Mengikut Komoditi\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"mineral-change-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-change\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Perubahan Tahunan\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"mineral-change-trend-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-change-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           PRECIOUS METALS\n      ====================================================== -->\n      <div\n        id=\"mineral-tab-precious\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pengekstrakan Emas\n            <\/div>\n\n            <div class=\"cs\">\n              Trend pengeluaran emas di Melaka dalam unit gram.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-gold\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Pengekstrakan Perak\n            <\/div>\n\n            <div class=\"cs\">\n              Trend pengeluaran perak di Melaka dalam unit gram.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"mineral-chart-silver\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- =====================================================\n           STATE\n      ====================================================== -->\n      <div\n        id=\"mineral-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Pengekstrakan Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"mineral-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"mineral-chart-state\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Pengekstrakan Tan Metrik Keseluruhan Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\">\n            Jumlah semua komoditi berasaskan tan metrik.\n            Melaka ditonjolkan dalam warna oren.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"mineral-chart-state-total\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=mineral_extraction&limit=3000';\n\n    var FIXED_STATE =\n      'Melaka';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n\n    Chart.defaults.color =\n      '#64748b';\n\n    Chart.defaults.borderColor =\n      '#dbeafe';\n\n    Chart.defaults.font.family =\n      'Arial, sans-serif';\n\n\n    \/* =====================================================\n       LABELS\n    ====================================================== *\/\n\n    var TYPE_LABELS = {\n\n      energy:\n        'Mineral Tenaga',\n\n      metallic:\n        'Mineral Logam',\n\n      non_metallic:\n        'Mineral Bukan Logam'\n    };\n\n\n    var COMMODITY_LABELS = {\n\n      coal:\n        'Arang Batu',\n\n      bauxite:\n        'Bauksit',\n\n      gold:\n        'Emas',\n\n      silver:\n        'Perak',\n\n      feldspar:\n        'Feldspar',\n\n      feldspare:\n        'Feldspar',\n\n      limestone:\n        'Batu Kapur',\n\n      granite:\n        'Granit',\n\n      silica_sand:\n        'Pasir Silika',\n\n      sand:\n        'Pasir',\n\n      clay:\n        'Tanah Liat',\n\n      kaolin:\n        'Kaolin',\n\n      tin:\n        'Bijih Timah',\n\n      iron_ore:\n        'Bijih Besi',\n\n      manganese:\n        'Mangan',\n\n      copper:\n        'Kuprum'\n    };\n\n\n    \/* =====================================================\n       BASIC HELPERS\n    ====================================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n\n    function yr(row){\n\n      return parseInt(\n        String(\n          row.date || ''\n        ).slice(0,4),\n        10\n      );\n    }\n\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === ''\n      ){\n        return null;\n      }\n\n      var n =\n        Number(value);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n\n    function valid(value){\n\n      return (\n        value !== null &&\n        value !== undefined &&\n        !isNaN(value)\n      );\n    }\n\n\n    function fmt(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n      return Number(value)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:d,\n            maximumFractionDigits:d\n          }\n        );\n    }\n\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,1) +\n        '%'\n      );\n    }\n\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n\n    function titleFromSnakeCase(value){\n\n      return String(value || '')\n        .replace(\/_\/g,' ')\n        .replace(\n          \/\\b\\w\/g,\n          function(c){\n            return c.toUpperCase();\n          }\n        );\n    }\n\n\n    function typeLabel(type){\n\n      if(type === 'all'){\n        return 'Semua Jenis Mineral';\n      }\n\n      return (\n        TYPE_LABELS[type] ||\n        titleFromSnakeCase(type)\n      );\n    }\n\n\n    function commodityLabel(commodity){\n\n      if(commodity === 'all'){\n        return 'Semua Komoditi';\n      }\n\n      return (\n        COMMODITY_LABELS[commodity] ||\n        titleFromSnakeCase(commodity)\n      );\n    }\n\n\n    function isGramCommodity(commodity){\n\n      var c =\n        String(commodity || '')\n        .toLowerCase();\n\n      return (\n        c === 'gold' ||\n        c === 'silver'\n      );\n    }\n\n\n    function commodityUnit(commodity){\n\n      return isGramCommodity(commodity)\n        ? 'gram'\n        : 'tan metrik';\n    }\n\n\n    function percentChange(\n      current,\n      previous\n    ){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n      return (\n        (\n          current -\n          previous\n        ) \/\n        previous\n      ) * 100;\n    }\n\n\n    \/* =====================================================\n       AVAILABLE VALUES\n    ====================================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(yr)\n            .filter(function(year){\n              return !isNaN(year);\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n\n    function getPreviousYear(current){\n\n      var years =\n        availableYears()\n        .filter(function(year){\n          return year < current;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n\n    function getTypes(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(function(row){\n              return row.type;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort(\n        function(a,b){\n\n          return typeLabel(a)\n            .localeCompare(\n              typeLabel(b),\n              'ms'\n            );\n        }\n      );\n    }\n\n\n    function getCommodities(type){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .filter(function(row){\n\n              return (\n                type === 'all' ||\n                row.type === type\n              );\n            })\n            .map(function(row){\n              return row.commodity;\n            })\n            .filter(Boolean)\n        )\n      )\n      .sort(\n        function(a,b){\n\n          return commodityLabel(a)\n            .localeCompare(\n              commodityLabel(b),\n              'ms'\n            );\n        }\n      );\n    }\n\n\n    function getAllCommodities(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.commodity;\n            })\n            .filter(Boolean)\n        )\n      );\n    }\n\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(function(state){\n\n              return (\n                state &#038;&#038;\n                state !== 'Malaysia'\n              );\n            })\n        )\n      )\n      .sort();\n    }\n\n\n    function getSelected(){\n\n      return {\n\n        year:\n          parseInt(\n            el('mineral-year').value,\n            10\n          ),\n\n        type:\n          el('mineral-type').value,\n\n        commodity:\n          el('mineral-commodity').value\n      };\n    }\n\n\n    \/* =====================================================\n       FILTER ROWS\n    ====================================================== *\/\n\n    function rowsFor(\n      source,\n      state,\n      year,\n      type,\n      commodity\n    ){\n\n      return source.filter(\n        function(row){\n\n          return (\n\n            row.state === state &#038;&#038;\n\n            yr(row) === year &#038;&#038;\n\n            (\n              type === 'all' ||\n              row.type === type\n            ) &#038;&#038;\n\n            (\n              commodity === 'all' ||\n              row.commodity === commodity\n            )\n\n          );\n        }\n      );\n    }\n\n\n    function sumProduction(rows){\n\n      var values =\n        rows\n        .map(function(row){\n          return num(row.production);\n        })\n        .filter(valid);\n\n\n      if(!values.length){\n        return null;\n      }\n\n\n      return values.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      );\n    }\n\n\n    \/*\n      Tan metric aggregation.\n\n      Gold and silver are excluded because\n      their production values are in grammes.\n    *\/\n\n    function tonneProduction(rows){\n\n      return sumProduction(\n        rows.filter(\n          function(row){\n            return !isGramCommodity(\n              row.commodity\n            );\n          }\n        )\n      );\n    }\n\n\n    function gramProduction(rows){\n\n      return sumProduction(\n        rows.filter(\n          function(row){\n            return isGramCommodity(\n              row.commodity\n            );\n          }\n        )\n      );\n    }\n\n\n    function specificCommodityProduction(\n      source,\n      state,\n      year,\n      commodity\n    ){\n\n      var rows =\n        rowsFor(\n          source,\n          state,\n          year,\n          'all',\n          commodity\n        );\n\n\n      return sumProduction(rows);\n    }\n\n\n    function melakaSpecific(\n      year,\n      commodity\n    ){\n\n      return specificCommodityProduction(\n        melakaRows,\n        FIXED_STATE,\n        year,\n        commodity\n      );\n    }\n\n\n    function selectedRows(\n      year,\n      type,\n      commodity\n    ){\n\n      return rowsFor(\n        melakaRows,\n        FIXED_STATE,\n        year,\n        type,\n        commodity\n      );\n    }\n\n\n    \/*\n      Returns a chart-friendly value.\n\n      If a single commodity is selected:\n      use its own native unit.\n\n      If all commodities are selected:\n      only tonne-based commodities are aggregated.\n    *\/\n\n    function selectedValue(\n      year,\n      type,\n      commodity\n    ){\n\n      var rows =\n        selectedRows(\n          year,\n          type,\n          commodity\n        );\n\n\n      if(commodity !== 'all'){\n        return sumProduction(rows);\n      }\n\n\n      return tonneProduction(rows);\n    }\n\n\n    function selectedUnit(\n      commodity\n    ){\n\n      if(commodity !== 'all'){\n        return commodityUnit(\n          commodity\n        );\n      }\n\n      return 'tan metrik';\n    }\n\n\n    \/* =====================================================\n       FILTERS\n    ====================================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n\n      el('mineral-year')\n        .innerHTML =\n\n        years.map(\n          function(year){\n\n            return (\n              '<option value=\"' +\n              year +\n              '\">' +\n              year +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(years.length){\n\n        el('mineral-year')\n          .value =\n          years[0];\n      }\n\n\n      var types =\n        getTypes();\n\n\n      el('mineral-type')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Jenis Mineral' +\n        '<\/option>' +\n\n        types.map(\n          function(type){\n\n            return (\n              '<option value=\"' +\n              type +\n              '\">' +\n              typeLabel(type) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      populateCommodityFilter();\n    }\n\n\n    function populateCommodityFilter(){\n\n      var type =\n        el('mineral-type').value ||\n        'all';\n\n\n      var commodities =\n        getCommodities(type);\n\n\n      var current =\n        el('mineral-commodity').value;\n\n\n      el('mineral-commodity')\n        .innerHTML =\n\n        '<option value=\"all\">' +\n        'Semua Komoditi' +\n        '<\/option>' +\n\n        commodities.map(\n          function(commodity){\n\n            return (\n              '<option value=\"' +\n              commodity +\n              '\">' +\n              commodityLabel(\n                commodity\n              ) +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(\n        current &&\n        commodities.indexOf(\n          current\n        ) !== -1\n      ){\n\n        el('mineral-commodity')\n          .value =\n          current;\n\n      }else{\n\n        el('mineral-commodity')\n          .value =\n          'all';\n      }\n    }\n\n\n    \/* =====================================================\n       SUMMARY CARDS\n    ====================================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n\n      var rows =\n        selectedRows(\n          s.year,\n          s.type,\n          s.commodity\n        );\n\n\n      var tonnes =\n        tonneProduction(rows);\n\n\n      var grams =\n        gramProduction(rows);\n\n\n      var current =\n        selectedValue(\n          s.year,\n          s.type,\n          s.commodity\n        );\n\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n\n      var previous =\n        previousYear !== null\n          ? selectedValue(\n              previousYear,\n              s.type,\n              s.commodity\n            )\n          : null;\n\n\n      var change =\n        previousYear !== null\n          ? percentChange(\n              current,\n              previous\n            )\n          : null;\n\n\n      el('mineral-stat-year')\n        .textContent =\n        s.year || '\u2014';\n\n\n      \/*\n        If a gram commodity is selected,\n        tonne card should remain empty.\n      *\/\n\n      if(\n        s.commodity !== 'all' &&\n        isGramCommodity(\n          s.commodity\n        )\n      ){\n\n        el('mineral-stat-tonnes')\n          .textContent =\n          '\u2014';\n\n      }else{\n\n        el('mineral-stat-tonnes')\n          .textContent =\n          valid(tonnes)\n            ? fmt(tonnes,1)\n            : '\u2014';\n      }\n\n\n      el('mineral-stat-grams')\n        .textContent =\n        valid(grams)\n          ? fmt(grams,1)\n          : '\u2014';\n\n\n      el('mineral-stat-change')\n        .textContent =\n        valid(change)\n          ? signedPct(change)\n          : '\u2014';\n\n\n      el('mineral-stat-change-sub')\n        .textContent =\n        previousYear !== null\n          ? (\n              'berbanding ' +\n              previousYear +\n              ' \u00b7 ' +\n              selectedUnit(\n                s.commodity\n              )\n            )\n          : 'tiada tahun sebelumnya';\n\n\n      el('mineral-stats')\n        .style.display =\n        '';\n    }\n\n\n    \/* =====================================================\n       TOOLTIPS\n    ====================================================== *\/\n\n    function tooltipProduction(\n      unit\n    ){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.y,\n                1\n              ) +\n              ' ' +\n              unit\n            );\n          }\n        }\n      };\n    }\n\n\n    function tooltipHorizontalProduction(\n      unit\n    ){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.x,\n                1\n              ) +\n              ' ' +\n              unit\n            );\n          }\n        }\n      };\n    }\n\n\n    \/* =====================================================\n       TREND\n    ====================================================== *\/\n\n    function buildTrendCharts(){\n\n      var s =\n        getSelected();\n\n      var years =\n        availableYears();\n\n      var unit =\n        selectedUnit(\n          s.commodity\n        );\n\n\n      el('mineral-trend-sub')\n        .textContent =\n\n        'Melaka \u00b7 ' +\n        typeLabel(\n          s.type\n        ) +\n        ' \u00b7 ' +\n        commodityLabel(\n          s.commodity\n        ) +\n        ' \u00b7 ' +\n        unit;\n\n\n      destroyChart('trend');\n\n\n      charts.trend =\n        new Chart(\n          el('mineral-chart-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Pengekstrakan',\n\n                data:\n                  years.map(\n                    function(year){\n\n                      return selectedValue(\n                        year,\n                        s.type,\n                        s.commodity\n                      );\n                    }\n                  ),\n\n                borderColor:'#2563eb',\n\n                backgroundColor:\n                  'rgba(37,99,235,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction(\n                    unit\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/*\n        Overall annual tonnage trend.\n        Gold and silver excluded.\n      *\/\n\n      destroyChart(\n        'totalTrend'\n      );\n\n\n      charts.totalTrend =\n        new Chart(\n          el('mineral-chart-total-trend')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Jumlah Tan Metrik',\n\n                data:\n                  years.map(\n                    function(year){\n\n                      return tonneProduction(\n                        rowsFor(\n                          melakaRows,\n                          FIXED_STATE,\n                          year,\n                          'all',\n                          'all'\n                        )\n                      );\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(22,163,74,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction(\n                    'tan metrik'\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       COMMODITY\n    ====================================================== *\/\n\n    function buildCommodityCharts(){\n\n      var s =\n        getSelected();\n\n\n      var commodities =\n        getCommodities(\n          s.type\n        );\n\n\n      \/*\n        Main commodity comparison uses tonne commodities only.\n        Gold and silver are intentionally separated.\n      *\/\n\n      var data =\n        commodities\n        .filter(\n          function(commodity){\n\n            return !isGramCommodity(\n              commodity\n            );\n          }\n        )\n        .map(\n          function(commodity){\n\n            return {\n\n              commodity:\n                commodity,\n\n              label:\n                commodityLabel(\n                  commodity\n                ),\n\n              value:\n                melakaSpecific(\n                  s.year,\n                  commodity\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('mineral-commodity-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 Melaka \u00b7 ' +\n        typeLabel(\n          s.type\n        ) +\n        ' \u00b7 tan metrik';\n\n\n      destroyChart('commodity');\n\n\n      charts.commodity =\n        new Chart(\n          el('mineral-chart-commodity')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengekstrakan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  'rgba(37,99,235,.78)',\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              indexAxis:'y',\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipHorizontalProduction(\n                    'tan metrik'\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var shareData =\n        data.filter(\n          function(item){\n\n            return (\n              valid(item.value) &&\n              item.value > 0\n            );\n          }\n        );\n\n\n      destroyChart('share');\n\n\n      charts.share =\n        new Chart(\n          el('mineral-chart-share')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                shareData.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengekstrakan',\n\n                data:\n                  shareData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#16a34a',\n                  '#f97316',\n                  '#7c3aed',\n                  '#0891b2',\n                  '#dc2626',\n                  '#64748b',\n                  '#0d9488'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' tan metrik (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TYPE\n    ====================================================== *\/\n\n    function buildTypeCharts(){\n\n      var s =\n        getSelected();\n\n      var types =\n        getTypes();\n\n\n      var data =\n        types.map(\n          function(type){\n\n            return {\n\n              type:type,\n\n              label:\n                typeLabel(type),\n\n              value:\n                tonneProduction(\n                  rowsFor(\n                    melakaRows,\n                    FIXED_STATE,\n                    s.year,\n                    type,\n                    'all'\n                  )\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return valid(item.value);\n        });\n\n\n      destroyChart('type');\n\n\n      charts.type =\n        new Chart(\n          el('mineral-chart-type')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengekstrakan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  'rgba(37,99,235,.78)',\n                  'rgba(249,115,22,.78)',\n                  'rgba(22,163,74,.78)'\n                ],\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction(\n                    'tan metrik'\n                  )\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var share =\n        data.filter(\n          function(item){\n\n            return (\n              valid(item.value) &&\n              item.value > 0\n            );\n          }\n        );\n\n\n      destroyChart('typeShare');\n\n\n      charts.typeShare =\n        new Chart(\n          el('mineral-chart-type-share')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:\n                share.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                data:\n                  share.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:[\n                  '#2563eb',\n                  '#f97316',\n                  '#16a34a'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var total =\n                        c.dataset.data\n                        .reduce(\n                          function(a,b){\n                            return a+b;\n                          },\n                          0\n                        );\n\n\n                      var share =\n                        total\n                          ? (\n                              c.parsed \/\n                              total\n                            ) * 100\n                          : 0;\n\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' tan metrik (' +\n                        fmt(share,1) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       HIGH \/ LOW\n    ====================================================== *\/\n\n    function buildHighLowCharts(){\n\n      var s =\n        getSelected();\n\n\n      var data =\n        getCommodities(\n          s.type\n        )\n        .filter(function(commodity){\n\n          return !isGramCommodity(\n            commodity\n          );\n        })\n        .map(\n          function(commodity){\n\n            return {\n\n              label:\n                commodityLabel(\n                  commodity\n                ),\n\n              value:\n                melakaSpecific(\n                  s.year,\n                  commodity\n                )\n            };\n          }\n        )\n        .filter(function(item){\n\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var limit =\n        Math.min(\n          5,\n          data.length\n        );\n\n\n      var high =\n        data.slice(\n          0,\n          limit\n        );\n\n\n      var low =\n        data.slice(\n          -limit\n        )\n        .sort(function(a,b){\n          return a.value-b.value;\n        });\n\n\n      el('mineral-high-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 Melaka \u00b7 komoditi berasaskan tan metrik';\n\n\n      el('mineral-low-sub')\n        .textContent =\n        el('mineral-high-sub')\n          .textContent;\n\n\n      function makeChart(\n        id,\n        name,\n        dataset,\n        color\n      ){\n\n        destroyChart(name);\n\n\n        charts[name] =\n          new Chart(\n            el(id)\n              .getContext('2d'),\n            {\n\n              type:'bar',\n\n              data:{\n\n                labels:\n                  dataset.map(\n                    function(item){\n                      return item.label;\n                    }\n                  ),\n\n                datasets:[{\n\n                  label:\n                    'Pengekstrakan',\n\n                  data:\n                    dataset.map(\n                      function(item){\n                        return item.value;\n                      }\n                    ),\n\n                  backgroundColor:\n                    color,\n\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n\n                responsive:true,\n\n                maintainAspectRatio:false,\n\n                plugins:{\n\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipProduction(\n                      'tan metrik'\n                    )\n                },\n\n                scales:{\n\n                  x:{\n\n                    grid:{\n                      display:false\n                    },\n\n                    ticks:{\n                      maxRotation:45\n                    }\n                  },\n\n                  y:{\n\n                    beginAtZero:true,\n\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(value){\n                        return fmt(value,0);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n      }\n\n\n      makeChart(\n        'mineral-chart-high',\n        'high',\n        high,\n        'rgba(22,163,74,.78)'\n      );\n\n\n      makeChart(\n        'mineral-chart-low',\n        'low',\n        low,\n        'rgba(220,38,38,.78)'\n      );\n    }\n\n\n    \/* =====================================================\n       CHANGE\n    ====================================================== *\/\n\n    function buildChangeCharts(){\n\n      var s =\n        getSelected();\n\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n\n      if(previousYear === null){\n\n        el('mineral-change-sub')\n          .textContent =\n          s.year +\n          ' \u00b7 Tiada tahun sebelumnya tersedia.';\n\n\n        el('mineral-change-trend-sub')\n          .textContent =\n          'Tiada tahun sebelumnya tersedia.';\n\n\n        destroyChart('change');\n        destroyChart('changeTrend');\n\n        return;\n      }\n\n\n      \/*\n        Commodity change comparison:\n        tonne-based commodities only.\n      *\/\n\n      var data =\n        getCommodities(\n          s.type\n        )\n        .filter(function(commodity){\n\n          return !isGramCommodity(\n            commodity\n          );\n        })\n        .map(\n          function(commodity){\n\n            var current =\n              melakaSpecific(\n                s.year,\n                commodity\n              );\n\n\n            var previous =\n              melakaSpecific(\n                previousYear,\n                commodity\n              );\n\n\n            return {\n\n              commodity:\n                commodity,\n\n              label:\n                commodityLabel(\n                  commodity\n                ),\n\n              value:\n                valid(current) &&\n                valid(previous)\n                  ? percentChange(\n                      current,\n                      previous\n                    )\n                  : null\n            };\n          }\n        )\n        .filter(function(item){\n\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('mineral-change-sub')\n        .textContent =\n\n        s.year +\n        ' berbanding ' +\n        previousYear +\n        ' \u00b7 komoditi berasaskan tan metrik';\n\n\n      destroyChart('change');\n\n\n      charts.change =\n        new Chart(\n          el('mineral-chart-change')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.value >= 0\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              indexAxis:'y',\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.x\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                },\n\n                y:{\n                  grid:{\n                    display:false\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var years =\n        availableYears();\n\n      var trend = [];\n\n\n      years.forEach(\n        function(year,index){\n\n          if(index === 0){\n            return;\n          }\n\n\n          var previous =\n            years[index-1];\n\n\n          var currentValue =\n            selectedValue(\n              year,\n              s.type,\n              s.commodity\n            );\n\n\n          var previousValue =\n            selectedValue(\n              previous,\n              s.type,\n              s.commodity\n            );\n\n\n          if(\n            valid(currentValue) &&\n            valid(previousValue)\n          ){\n\n            trend.push({\n\n              label:\n                year +\n                ' berbanding ' +\n                previous,\n\n              value:\n                percentChange(\n                  currentValue,\n                  previousValue\n                )\n            });\n          }\n        }\n      );\n\n\n      el('mineral-change-trend-sub')\n        .textContent =\n\n        typeLabel(\n          s.type\n        ) +\n        ' \u00b7 ' +\n        commodityLabel(\n          s.commodity\n        );\n\n\n      destroyChart('changeTrend');\n\n\n      charts.changeTrend =\n        new Chart(\n          el('mineral-chart-change-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                trend.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Tahunan',\n\n                data:\n                  trend.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n\n                backgroundColor:\n                  'rgba(124,58,237,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,1)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       GOLD + SILVER\n    ====================================================== *\/\n\n    function buildPreciousCharts(){\n\n      var years =\n        availableYears();\n\n\n      function buildMetal(\n        commodity,\n        id,\n        chartName,\n        color\n      ){\n\n        destroyChart(\n          chartName\n        );\n\n\n        charts[chartName] =\n          new Chart(\n            el(id)\n              .getContext('2d'),\n            {\n\n              type:'line',\n\n              data:{\n\n                labels:years,\n\n                datasets:[{\n\n                  label:\n                    commodityLabel(\n                      commodity\n                    ),\n\n                  data:\n                    years.map(\n                      function(year){\n\n                        return melakaSpecific(\n                          year,\n                          commodity\n                        );\n                      }\n                    ),\n\n                  borderColor:\n                    color,\n\n                  backgroundColor:\n                    color === '#f59e0b'\n                      ? 'rgba(245,158,11,.15)'\n                      : 'rgba(100,116,139,.15)',\n\n                  fill:true,\n\n                  tension:.35,\n\n                  borderWidth:2.5,\n\n                  pointRadius:4\n                }]\n              },\n\n              options:{\n\n                responsive:true,\n\n                maintainAspectRatio:false,\n\n                plugins:{\n\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipProduction(\n                      'gram'\n                    )\n                },\n\n                scales:{\n\n                  x:{\n                    grid:{\n                      color:'#dbeafe'\n                    }\n                  },\n\n                  y:{\n\n                    beginAtZero:true,\n\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(value){\n                        return fmt(value,0);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n      }\n\n\n      buildMetal(\n        'gold',\n        'mineral-chart-gold',\n        'gold',\n        '#f59e0b'\n      );\n\n\n      buildMetal(\n        'silver',\n        'mineral-chart-silver',\n        'silver',\n        '#64748b'\n      );\n    }\n\n\n    \/* =====================================================\n       STATE\n    ====================================================== *\/\n\n    function stateValue(\n      state,\n      year,\n      type,\n      commodity\n    ){\n\n      var rows =\n        rowsFor(\n          allRows,\n          state,\n          year,\n          type,\n          commodity\n        );\n\n\n      if(commodity !== 'all'){\n        return sumProduction(rows);\n      }\n\n\n      return tonneProduction(rows);\n    }\n\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n\n      var states =\n        getStates();\n\n\n      var unit =\n        selectedUnit(\n          s.commodity\n        );\n\n\n      var data =\n        states.map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                stateValue(\n                  state,\n                  s.year,\n                  s.type,\n                  s.commodity\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('mineral-state-sub')\n        .textContent =\n\n        s.year +\n        ' \u00b7 ' +\n        typeLabel(\n          s.type\n        ) +\n        ' \u00b7 ' +\n        commodityLabel(\n          s.commodity\n        ) +\n        ' \u00b7 ' +\n        unit;\n\n\n      destroyChart('state');\n\n\n      charts.state =\n        new Chart(\n          el('mineral-chart-state')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengekstrakan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction(\n                    unit\n                  )\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label ===\n                          'Melaka'\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/*\n        Total tonne commodities by state.\n      *\/\n\n      var totalData =\n        states.map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                tonneProduction(\n                  rowsFor(\n                    allRows,\n                    state,\n                    s.year,\n                    'all',\n                    'all'\n                  )\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        })\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      destroyChart(\n        'stateTotal'\n      );\n\n\n      charts.stateTotal =\n        new Chart(\n          el('mineral-chart-state-total')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                totalData.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Jumlah Pengekstrakan',\n\n                data:\n                  totalData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  totalData.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(22,163,74,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  totalData.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  totalData.map(\n                    function(item){\n\n                      return item.state ===\n                        'Melaka'\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipProduction(\n                    'tan metrik'\n                  )\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        'Melaka'\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          'Melaka'\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TAB CONTROL\n    ====================================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#mineral-wrap .tbtn.active'\n        );\n\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n\n    function redraw(){\n\n      updateCards();\n\n\n      var tab =\n        activeTab();\n\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n\n      if(tab === 'commodity'){\n        buildCommodityCharts();\n      }\n\n\n      if(tab === 'type'){\n        buildTypeCharts();\n      }\n\n\n      if(tab === 'highlow'){\n        buildHighLowCharts();\n      }\n\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n\n      if(tab === 'precious'){\n        buildPreciousCharts();\n      }\n\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n    }\n\n\n    \/* =====================================================\n       TAB EVENTS\n    ====================================================== *\/\n\n    document\n      .querySelectorAll(\n        '#mineral-wrap .tbtn'\n      )\n      .forEach(\n        function(btn){\n\n          btn.addEventListener(\n            'click',\n            function(){\n\n              document\n                .querySelectorAll(\n                  '#mineral-wrap .tbtn'\n                )\n                .forEach(\n                  function(button){\n\n                    button.classList\n                      .remove(\n                        'active'\n                      );\n                  }\n                );\n\n\n              btn.classList\n                .add(\n                  'active'\n                );\n\n\n              [\n                'trend',\n                'commodity',\n                'type',\n                'highlow',\n                'change',\n                'precious',\n                'state'\n              ]\n              .forEach(\n                function(tab){\n\n                  el(\n                    'mineral-tab-' +\n                    tab\n                  ).style.display =\n                    'none';\n                }\n              );\n\n\n              el(\n                'mineral-tab-' +\n                btn.getAttribute(\n                  'data-tab'\n                )\n              ).style.display =\n                '';\n\n\n              redraw();\n            }\n          );\n        }\n      );\n\n\n    \/* =====================================================\n       FILTER EVENTS\n    ====================================================== *\/\n\n    el('mineral-type')\n      .addEventListener(\n        'change',\n        function(){\n\n          populateCommodityFilter();\n\n          redraw();\n        }\n      );\n\n\n    [\n      'mineral-year',\n      'mineral-commodity'\n    ]\n    .forEach(\n      function(id){\n\n        el(id)\n          .addEventListener(\n            'change',\n            redraw\n          );\n      }\n    );\n\n\n    \/* =====================================================\n       FETCH\n    ====================================================== *\/\n\n    fetch(API_URL)\n\n      .then(\n        function(response){\n\n          if(!response.ok){\n\n            throw new Error(\n              'HTTP ' +\n              response.status\n            );\n          }\n\n\n          return response.json();\n        }\n      )\n\n      .then(\n        function(data){\n\n          allRows =\n            Array.isArray(data)\n              ? data\n              : [];\n\n\n          melakaRows =\n            allRows.filter(\n              function(row){\n\n                return (\n                  String(\n                    row.state || ''\n                  )\n                  .trim()\n                  .toLowerCase()\n                  ===\n                  'melaka'\n                );\n              }\n            );\n\n\n          if(!melakaRows.length){\n\n            throw new Error(\n              'Tiada rekod pengekstrakan mineral Melaka ditemui.'\n            );\n          }\n\n\n          populateFilters();\n\n\n          el('mineral-loading')\n            .style.display =\n            'none';\n\n\n          el('mineral-area')\n            .style.display =\n            '';\n\n\n          redraw();\n        }\n      )\n\n      .catch(\n        function(error){\n\n          el('mineral-loading')\n            .innerHTML =\n\n            '<div style=\"' +\n            'color:#dc2626;' +\n            'font-size:16px;' +\n            'font-weight:800;' +\n            'margin-bottom:8px' +\n            '\">' +\n\n            'Gagal memuatkan data' +\n\n            '<\/div>' +\n\n            '<p style=\"' +\n            'font-size:13px;' +\n            'color:#64748b' +\n            '\">' +\n\n            error.message +\n\n            '<\/p>';\n        }\n      );\n\n  }\n\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<div style=\"text-align: center; margin: 40px 0 30px 0;\">\n  <h2 style=\"\n    font-size: 32px;\n    font-weight: 700;\n    color: #1f3c88;\n    margin-bottom: 10px;\n    letter-spacing: 0.5px;\n  \">\n    Dashboard Kategori Alam Sekitar\n  <\/h2>\n\n  <div style=\"\n    width: 80px;\n    height: 4px;\n    background-color: #1f3c88;\n    margin: 0 auto;\n    border-radius: 4px;\n  \"><\/div>\n<\/div>\n\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#forest-wrap, #forest-wrap *{\n  box-sizing:border-box;\n}\n\n#forest-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(\n    180deg,\n    #ffffff 0%,\n    var(--bg) 100%\n  );\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#forest-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#forest-wrap .hdr,\n#forest-wrap .cc,\n#forest-wrap .sc,\n#forest-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#forest-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#forest-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#forest-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#forest-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#forest-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(2,1fr);\n  gap:14px;\n  background:#ffffff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#forest-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#forest-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#forest-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#ffffff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#forest-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#forest-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#forest-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#forest-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#forest-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#forest-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n  line-height:1.35;\n}\n\n#forest-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#forest-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#forest-wrap .tbtn.active{\n  background:#ffffff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#forest-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#forest-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#forest-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n  line-height:1.45;\n}\n\n#forest-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#forest-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#forest-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#forest-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#forest-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:forest-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes forest-spin{\n  to{\n    transform:rotate(360deg);\n  }\n}\n\n@media(max-width:1024px){\n\n  #forest-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #forest-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #forest-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #forest-wrap .filter-bar,\n  #forest-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #forest-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #forest-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #forest-wrap .chart-box,\n  #forest-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n\n<div id=\"forest-wrap\">\n\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Hutan Simpan Kekal Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Keluasan Hutan Simpan Kekal Mengikut Negeri\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi keluasan Hutan Simpan Kekal di Melaka mengikut tahun.\n        Dashboard ini menunjukkan trend perubahan keluasan, kedudukan Melaka,\n        perbandingan dengan negeri-negeri lain serta sumbangan Melaka kepada\n        keseluruhan Hutan Simpan Kekal di Semenanjung Malaysia.\n      <\/p>\n\n    <\/div>\n\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n\n        <label>\n          Tahun\n        <\/label>\n\n        <select\n          id=\"forest-year\"\n        ><\/select>\n\n      <\/div>\n\n\n      <div class=\"cg\">\n\n        <label>\n          Negeri Perbandingan\n        <\/label>\n\n        <select\n          id=\"forest-compare-state\"\n        ><\/select>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"forest-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tahun Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"forest-stat-year\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Keluasan Melaka\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"forest-stat-area\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          hektar\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Perubahan Tahunan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"forest-stat-change\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"forest-stat-change-sub\"\n        >\n          berbanding tahun sebelumnya\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Kedudukan Melaka\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"forest-stat-rank\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"forest-stat-rank-sub\"\n        >\n          antara negeri yang mempunyai data\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"forest-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod Hutan Simpan Kekal Melaka.\n      <\/div>\n\n    <\/div>\n\n\n    <!-- DASHBOARD -->\n    <div\n      id=\"forest-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TABS -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"ranking\"\n        >\n          Kedudukan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"share\"\n        >\n          Sumbangan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"comparison\"\n        >\n          Perbandingan\n        <\/button>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           TREND\n      =========================================== -->\n      <div id=\"forest-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Keluasan Hutan Simpan Kekal Melaka\n            <\/div>\n\n            <div class=\"cs\">\n              Keluasan Hutan Simpan Kekal Melaka bagi setiap tahun\n              yang tersedia dalam dataset.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"forest-chart-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Indeks Keluasan Hutan Simpan\n            <\/div>\n\n            <div class=\"cs\">\n              Tahun pertama yang tersedia ditetapkan sebagai indeks 100\n              untuk menunjukkan perubahan relatif keluasan Hutan Simpan Kekal.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"forest-chart-index\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           CHANGE\n      =========================================== -->\n      <div\n        id=\"forest-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Keluasan Tahunan\n            <\/div>\n\n            <div class=\"cs\">\n              Perubahan keluasan dalam hektar berbanding tahun terdahulu\n              yang tersedia.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"forest-chart-change-ha\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Peratus Tahunan\n            <\/div>\n\n            <div class=\"cs\">\n              Kadar peningkatan atau pengurangan keluasan Hutan Simpan Kekal\n              Melaka berbanding tahun sebelumnya.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"forest-chart-change-pct\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           STATE\n      =========================================== -->\n      <div\n        id=\"forest-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Keluasan Hutan Simpan Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"forest-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"forest-chart-state\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Negeri Mengikut Peratus daripada Negeri Tertinggi\n          <\/div>\n\n          <div class=\"cs\">\n            Negeri dengan keluasan tertinggi ditetapkan sebagai 100%\n            untuk menunjukkan kedudukan relatif negeri-negeri lain.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"forest-chart-state-relative\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           RANKING\n      =========================================== -->\n      <div\n        id=\"forest-tab-ranking\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Negeri dengan Keluasan Tertinggi\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"forest-ranking-high-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"forest-chart-high\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Negeri dengan Keluasan Terendah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"forest-ranking-low-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"forest-chart-low\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           SHARE\n      =========================================== -->\n      <div\n        id=\"forest-tab-share\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Sumbangan Melaka kepada Semenanjung Malaysia\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"forest-share-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"forest-chart-share\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Sumbangan Melaka\n            <\/div>\n\n            <div class=\"cs\">\n              Peratus keluasan Hutan Simpan Kekal Melaka berbanding\n              keseluruhan Semenanjung Malaysia mengikut tahun.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"forest-chart-share-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           COMPARISON\n      =========================================== -->\n      <div\n        id=\"forest-tab-comparison\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Trend Melaka berbanding Negeri Pilihan\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"forest-comparison-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"forest-chart-comparison\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbezaan Keluasan dengan Negeri Pilihan\n          <\/div>\n\n          <div class=\"cs\">\n            Nilai positif menunjukkan keluasan Melaka lebih besar,\n            manakala nilai negatif menunjukkan negeri perbandingan\n            mempunyai keluasan lebih besar.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"forest-chart-gap\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=forest_reserve_state&limit=1000';\n\n    var FIXED_STATE =\n      'Melaka';\n\n    var PENINSULAR_STATE =\n      'Semenanjung Malaysia';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n\n    Chart.defaults.color =\n      '#64748b';\n\n    Chart.defaults.borderColor =\n      '#dbeafe';\n\n    Chart.defaults.font.family =\n      'Arial, sans-serif';\n\n\n    \/* =====================================================\n       BASIC HELPERS\n    ====================================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n\n    function yr(row){\n\n      return parseInt(\n        String(\n          row.date || ''\n        ).slice(0,4),\n        10\n      );\n    }\n\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === ''\n      ){\n        return null;\n      }\n\n      var n =\n        Number(value);\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n\n    function valid(value){\n\n      return (\n        value !== null &&\n        value !== undefined &&\n        !isNaN(value)\n      );\n    }\n\n\n    function fmt(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return Number(value)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:\n              digits === undefined\n                ? 1\n                : digits,\n\n            maximumFractionDigits:\n              digits === undefined\n                ? 1\n                : digits\n          }\n        );\n    }\n\n\n    function signed(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(\n          value,\n          digits === undefined\n            ? 1\n            : digits\n        )\n      );\n    }\n\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,2) +\n        '%'\n      );\n    }\n\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n\n    function percentChange(\n      current,\n      previous\n    ){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n      return (\n        (\n          current -\n          previous\n        ) \/\n        previous\n      ) * 100;\n    }\n\n\n    \/* =====================================================\n       DATA HELPERS\n    ====================================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(yr)\n            .filter(function(year){\n              return !isNaN(year);\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n\n    function getPreviousYear(currentYear){\n\n      var years =\n        availableYears()\n        .filter(function(year){\n          return year < currentYear;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(function(state){\n\n              return (\n                state &#038;&#038;\n                state !== PENINSULAR_STATE\n              );\n            })\n        )\n      )\n      .sort();\n    }\n\n\n    function getComparisonStates(){\n\n      return getStates()\n        .filter(function(state){\n          return state !== FIXED_STATE;\n        });\n    }\n\n\n    function stateArea(\n      state,\n      year\n    ){\n\n      var rows =\n        allRows.filter(\n          function(row){\n\n            return (\n              row.state === state &#038;&#038;\n              yr(row) === year\n            );\n          }\n        );\n\n\n      var values =\n        rows\n        .map(function(row){\n          return num(row.area);\n        })\n        .filter(valid);\n\n\n      if(!values.length){\n        return null;\n      }\n\n\n      \/*\n        Dataset should contain one state-year row.\n        Sum is used defensively if duplicates exist.\n      *\/\n\n      return values.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      );\n    }\n\n\n    function melakaArea(year){\n\n      return stateArea(\n        FIXED_STATE,\n        year\n      );\n    }\n\n\n    function peninsularArea(year){\n\n      return stateArea(\n        PENINSULAR_STATE,\n        year\n      );\n    }\n\n\n    function stateDataForYear(year){\n\n      return getStates()\n        .map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                stateArea(\n                  state,\n                  year\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        });\n    }\n\n\n    function getSelected(){\n\n      return {\n\n        year:\n          parseInt(\n            el('forest-year').value,\n            10\n          ),\n\n        compareState:\n          el('forest-compare-state').value\n      };\n    }\n\n\n    \/* =====================================================\n       FILTER POPULATION\n    ====================================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n\n      el('forest-year')\n        .innerHTML =\n\n        years.map(\n          function(year){\n\n            return (\n              '<option value=\"' +\n              year +\n              '\">' +\n              year +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(years.length){\n\n        el('forest-year')\n          .value =\n          years[0];\n      }\n\n\n      var states =\n        getComparisonStates();\n\n\n      el('forest-compare-state')\n        .innerHTML =\n\n        states.map(\n          function(state){\n\n            return (\n              '<option value=\"' +\n              state +\n              '\">' +\n              state +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      \/*\n        Prefer Negeri Sembilan because it is\n        geographically adjacent to Melaka.\n      *\/\n\n      if(\n        states.indexOf(\n          'Negeri Sembilan'\n        ) !== -1\n      ){\n\n        el('forest-compare-state')\n          .value =\n          'Negeri Sembilan';\n      }\n    }\n\n\n    \/* =====================================================\n       SUMMARY CARDS\n    ====================================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n\n      var current =\n        melakaArea(\n          s.year\n        );\n\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n\n      var previous =\n        previousYear !== null\n          ? melakaArea(\n              previousYear\n            )\n          : null;\n\n\n      var change =\n        previousYear !== null\n          ? percentChange(\n              current,\n              previous\n            )\n          : null;\n\n\n      var ranking =\n        stateDataForYear(\n          s.year\n        )\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var rankIndex =\n        ranking.findIndex(\n          function(item){\n            return item.state === FIXED_STATE;\n          }\n        );\n\n\n      el('forest-stat-year')\n        .textContent =\n        s.year || '\u2014';\n\n\n      el('forest-stat-area')\n        .textContent =\n        valid(current)\n          ? fmt(current,1)\n          : '\u2014';\n\n\n      el('forest-stat-change')\n        .textContent =\n        valid(change)\n          ? signedPct(change)\n          : '\u2014';\n\n\n      el('forest-stat-change-sub')\n        .textContent =\n        previousYear !== null\n          ? (\n              'berbanding ' +\n              previousYear\n            )\n          : 'tiada tahun sebelumnya';\n\n\n      el('forest-stat-rank')\n        .textContent =\n        rankIndex >= 0\n          ? (\n              '#' +\n              (rankIndex + 1)\n            )\n          : '\u2014';\n\n\n      el('forest-stat-rank-sub')\n        .textContent =\n        rankIndex >= 0\n          ? (\n              'daripada ' +\n              ranking.length +\n              ' negeri'\n            )\n          : 'tiada data kedudukan';\n\n\n      el('forest-stats')\n        .style.display =\n        '';\n    }\n\n\n    \/* =====================================================\n       TOOLTIP\n    ====================================================== *\/\n\n    function tooltipArea(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.y,\n                1\n              ) +\n              ' hektar'\n            );\n          }\n        }\n      };\n    }\n\n\n    function tooltipHorizontalArea(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.x,\n                1\n              ) +\n              ' hektar'\n            );\n          }\n        }\n      };\n    }\n\n\n    \/* =====================================================\n       TREND\n    ====================================================== *\/\n\n    function buildTrendCharts(){\n\n      var years =\n        availableYears();\n\n\n      var values =\n        years.map(\n          function(year){\n            return melakaArea(year);\n          }\n        );\n\n\n      destroyChart('trend');\n\n\n      charts.trend =\n        new Chart(\n          el('forest-chart-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Keluasan Hutan Simpan',\n\n                data:values,\n\n                borderColor:'#2563eb',\n\n                backgroundColor:\n                  'rgba(37,99,235,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/*\n        Index chart:\n        first available valid year = 100\n      *\/\n\n      var baseValue = null;\n\n\n      for(\n        var i=0;\n        i<values.length;\n        i++\n      ){\n\n        if(\n          valid(values[i]) &#038;&#038;\n          values[i] !== 0\n        ){\n\n          baseValue =\n            values[i];\n\n          break;\n        }\n      }\n\n\n      var indexValues =\n        values.map(\n          function(value){\n\n            if(\n              !valid(value) ||\n              !valid(baseValue) ||\n              baseValue === 0\n            ){\n              return null;\n            }\n\n            return (\n              value \/\n              baseValue\n            ) * 100;\n          }\n        );\n\n\n      destroyChart('index');\n\n\n      charts.index =\n        new Chart(\n          el('forest-chart-index')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Indeks Keluasan',\n\n                data:indexValues,\n\n                borderColor:'#7c3aed',\n\n                backgroundColor:\n                  'rgba(124,58,237,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Indeks: ' +\n                        fmt(\n                          c.parsed.y,\n                          2\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       CHANGE\n    ====================================================== *\/\n\n    function buildChangeCharts(){\n\n      var years =\n        availableYears();\n\n      var changesHa = [];\n      var changesPct = [];\n\n\n      years.forEach(\n        function(year,index){\n\n          if(index === 0){\n            return;\n          }\n\n\n          var previousYear =\n            years[index-1];\n\n\n          var current =\n            melakaArea(\n              year\n            );\n\n\n          var previous =\n            melakaArea(\n              previousYear\n            );\n\n\n          changesHa.push({\n\n            label:\n              year +\n              ' vs ' +\n              previousYear,\n\n            value:\n              valid(current) &#038;&#038;\n              valid(previous)\n                ? current - previous\n                : null\n          });\n\n\n          changesPct.push({\n\n            label:\n              year +\n              ' vs ' +\n              previousYear,\n\n            value:\n              percentChange(\n                current,\n                previous\n              )\n          });\n        }\n      );\n\n\n      destroyChart(\n        'changeHa'\n      );\n\n\n      charts.changeHa =\n        new Chart(\n          el('forest-chart-change-ha')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                changesHa.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Keluasan',\n\n                data:\n                  changesHa.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  changesHa.map(\n                    function(item){\n\n                      return (\n                        valid(item.value) &#038;&#038;\n                        item.value >= 0\n                      )\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signed(\n                          c.parsed.y,\n                          1\n                        ) +\n                        ' hektar'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      destroyChart(\n        'changePct'\n      );\n\n\n      charts.changePct =\n        new Chart(\n          el('forest-chart-change-pct')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                changesPct.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Tahunan',\n\n                data:\n                  changesPct.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n\n                backgroundColor:\n                  'rgba(124,58,237,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,2)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       STATE\n    ====================================================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n\n      var data =\n        stateDataForYear(\n          s.year\n        )\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('forest-state-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 semua negeri yang mempunyai rekod \u00b7 Melaka ditonjolkan dalam warna oren';\n\n\n      destroyChart('state');\n\n\n      charts.state =\n        new Chart(\n          el('forest-chart-state')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Keluasan Hutan Simpan',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        FIXED_STATE\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        FIXED_STATE\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        FIXED_STATE\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        FIXED_STATE\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          FIXED_STATE\n                            ? '800'\n                            : '400'\n                      };\n                    }\n                  }\n                },\n\n                y:{\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var highest =\n        data.length\n          ? data[0].value\n          : null;\n\n\n      var relative =\n        data.map(\n          function(item){\n\n            return {\n\n              state:item.state,\n\n              value:\n                valid(highest) &&\n                highest !== 0\n                  ? (\n                      item.value \/\n                      highest\n                    ) * 100\n                  : null\n            };\n          }\n        );\n\n\n      destroyChart(\n        'stateRelative'\n      );\n\n\n      charts.stateRelative =\n        new Chart(\n          el('forest-chart-state-relative')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                relative.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Peratus Relatif',\n\n                data:\n                  relative.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  relative.map(\n                    function(item){\n\n                      return item.state ===\n                        FIXED_STATE\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(22,163,74,.70)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Kedudukan relatif: ' +\n                        fmt(c.parsed.y,1) +\n                        '%'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  max:110,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return value+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       RANKING\n    ====================================================== *\/\n\n    function buildRankingCharts(){\n\n      var s =\n        getSelected();\n\n\n      var data =\n        stateDataForYear(\n          s.year\n        )\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var limit =\n        Math.min(\n          5,\n          data.length\n        );\n\n\n      var high =\n        data.slice(\n          0,\n          limit\n        );\n\n\n      var low =\n        data.slice(\n          -limit\n        )\n        .sort(function(a,b){\n          return a.value-b.value;\n        });\n\n\n      el('forest-ranking-high-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 lima negeri dengan keluasan tertinggi';\n\n\n      el('forest-ranking-low-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 lima negeri dengan keluasan terendah';\n\n\n      function makeRankingChart(\n        id,\n        name,\n        dataset,\n        baseColor\n      ){\n\n        destroyChart(name);\n\n\n        charts[name] =\n          new Chart(\n            el(id)\n              .getContext('2d'),\n            {\n\n              type:'bar',\n\n              data:{\n\n                labels:\n                  dataset.map(\n                    function(item){\n                      return item.state;\n                    }\n                  ),\n\n                datasets:[{\n\n                  label:\n                    'Keluasan Hutan Simpan',\n\n                  data:\n                    dataset.map(\n                      function(item){\n                        return item.value;\n                      }\n                    ),\n\n                  backgroundColor:\n                    dataset.map(\n                      function(item){\n\n                        return item.state ===\n                          FIXED_STATE\n                          ? 'rgba(249,115,22,.95)'\n                          : baseColor;\n                      }\n                    ),\n\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n\n                responsive:true,\n\n                maintainAspectRatio:false,\n\n                plugins:{\n\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipArea()\n                },\n\n                scales:{\n\n                  x:{\n                    grid:{\n                      display:false\n                    },\n\n                    ticks:{\n                      maxRotation:45\n                    }\n                  },\n\n                  y:{\n\n                    beginAtZero:true,\n\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(value){\n                        return fmt(value,0);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n      }\n\n\n      makeRankingChart(\n        'forest-chart-high',\n        'high',\n        high,\n        'rgba(22,163,74,.78)'\n      );\n\n\n      makeRankingChart(\n        'forest-chart-low',\n        'low',\n        low,\n        'rgba(220,38,38,.78)'\n      );\n    }\n\n\n    \/* =====================================================\n       SHARE OF PENINSULAR MALAYSIA\n    ====================================================== *\/\n\n    function shareOfPeninsular(year){\n\n      var melaka =\n        melakaArea(year);\n\n\n      var peninsular =\n        peninsularArea(year);\n\n\n      if(\n        !valid(melaka) ||\n        !valid(peninsular) ||\n        peninsular === 0\n      ){\n        return null;\n      }\n\n\n      return (\n        melaka \/\n        peninsular\n      ) * 100;\n    }\n\n\n    function buildShareCharts(){\n\n      var s =\n        getSelected();\n\n\n      var melaka =\n        melakaArea(\n          s.year\n        );\n\n\n      var peninsular =\n        peninsularArea(\n          s.year\n        );\n\n\n      var share =\n        shareOfPeninsular(\n          s.year\n        );\n\n\n      el('forest-share-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 Melaka berbanding jumlah Semenanjung Malaysia';\n\n\n      var other =\n        (\n          valid(peninsular) &&\n          valid(melaka)\n        )\n          ? Math.max(\n              peninsular -\n              melaka,\n              0\n            )\n          : null;\n\n\n      destroyChart('share');\n\n\n      charts.share =\n        new Chart(\n          el('forest-chart-share')\n            .getContext('2d'),\n          {\n\n            type:'doughnut',\n\n            data:{\n\n              labels:[\n                'Melaka',\n                'Negeri Lain di Semenanjung Malaysia'\n              ],\n\n              datasets:[{\n\n                data:[\n                  melaka,\n                  other\n                ],\n\n                backgroundColor:[\n                  '#f97316',\n                  '#2563eb'\n                ],\n\n                borderColor:'#ffffff',\n\n                borderWidth:3\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      var pct =\n                        valid(peninsular) &&\n                        peninsular !== 0\n                          ? (\n                              c.parsed \/\n                              peninsular\n                            ) * 100\n                          : null;\n\n\n                      return (\n                        ' ' +\n                        c.label +\n                        ': ' +\n                        fmt(c.parsed,1) +\n                        ' hektar (' +\n                        fmt(pct,2) +\n                        '%)'\n                      );\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var years =\n        availableYears();\n\n\n      destroyChart(\n        'shareTrend'\n      );\n\n\n      charts.shareTrend =\n        new Chart(\n          el('forest-chart-share-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Sumbangan Melaka',\n\n                data:\n                  years.map(\n                    function(year){\n\n                      return shareOfPeninsular(\n                        year\n                      );\n                    }\n                  ),\n\n                borderColor:'#f97316',\n\n                backgroundColor:\n                  'rgba(249,115,22,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Sumbangan: ' +\n                        fmt(c.parsed.y,2) +\n                        '%'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,2)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       STATE COMPARISON\n    ====================================================== *\/\n\n    function buildComparisonCharts(){\n\n      var s =\n        getSelected();\n\n\n      var years =\n        availableYears();\n\n\n      el('forest-comparison-sub')\n        .textContent =\n\n        'Melaka berbanding ' +\n        s.compareState +\n        ' \u00b7 sepanjang tempoh data';\n\n\n      destroyChart(\n        'comparison'\n      );\n\n\n      charts.comparison =\n        new Chart(\n          el('forest-chart-comparison')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[\n\n                {\n                  label:'Melaka',\n\n                  data:\n                    years.map(\n                      function(year){\n\n                        return melakaArea(\n                          year\n                        );\n                      }\n                    ),\n\n                  borderColor:'#f97316',\n\n                  backgroundColor:\n                    'rgba(249,115,22,.10)',\n\n                  tension:.35,\n\n                  borderWidth:3,\n\n                  pointRadius:4\n                },\n\n                {\n                  label:\n                    s.compareState,\n\n                  data:\n                    years.map(\n                      function(year){\n\n                        return stateArea(\n                          s.compareState,\n                          year\n                        );\n                      }\n                    ),\n\n                  borderColor:'#2563eb',\n\n                  backgroundColor:\n                    'rgba(37,99,235,.10)',\n\n                  tension:.35,\n\n                  borderWidth:2.5,\n\n                  pointRadius:4\n                }\n\n              ]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipArea()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var gaps =\n        years.map(\n          function(year){\n\n            var melaka =\n              melakaArea(\n                year\n              );\n\n\n            var comparison =\n              stateArea(\n                s.compareState,\n                year\n              );\n\n\n            return (\n              valid(melaka) &&\n              valid(comparison)\n            )\n              ? melaka - comparison\n              : null;\n          }\n        );\n\n\n      destroyChart('gap');\n\n\n      charts.gap =\n        new Chart(\n          el('forest-chart-gap')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Perbezaan Keluasan',\n\n                data:gaps,\n\n                backgroundColor:\n                  gaps.map(\n                    function(value){\n\n                      return (\n                        valid(value) &&\n                        value >= 0\n                      )\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Melaka - ' +\n                        s.compareState +\n                        ': ' +\n                        signed(\n                          c.parsed.y,\n                          1\n                        ) +\n                        ' hektar'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TAB CONTROL\n    ====================================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#forest-wrap .tbtn.active'\n        );\n\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n\n    function redraw(){\n\n      updateCards();\n\n\n      var tab =\n        activeTab();\n\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n\n\n      if(tab === 'ranking'){\n        buildRankingCharts();\n      }\n\n\n      if(tab === 'share'){\n        buildShareCharts();\n      }\n\n\n      if(tab === 'comparison'){\n        buildComparisonCharts();\n      }\n    }\n\n\n    \/* =====================================================\n       TAB EVENTS\n    ====================================================== *\/\n\n    document\n      .querySelectorAll(\n        '#forest-wrap .tbtn'\n      )\n      .forEach(\n        function(btn){\n\n          btn.addEventListener(\n            'click',\n            function(){\n\n              document\n                .querySelectorAll(\n                  '#forest-wrap .tbtn'\n                )\n                .forEach(\n                  function(button){\n\n                    button.classList\n                      .remove('active');\n                  }\n                );\n\n\n              btn.classList\n                .add('active');\n\n\n              [\n                'trend',\n                'change',\n                'state',\n                'ranking',\n                'share',\n                'comparison'\n              ]\n              .forEach(\n                function(tab){\n\n                  el(\n                    'forest-tab-' +\n                    tab\n                  ).style.display =\n                    'none';\n                }\n              );\n\n\n              el(\n                'forest-tab-' +\n                btn.getAttribute(\n                  'data-tab'\n                )\n              ).style.display =\n                '';\n\n\n              redraw();\n            }\n          );\n        }\n      );\n\n\n    \/* =====================================================\n       FILTER EVENTS\n    ====================================================== *\/\n\n    [\n      'forest-year',\n      'forest-compare-state'\n    ]\n    .forEach(\n      function(id){\n\n        el(id)\n          .addEventListener(\n            'change',\n            redraw\n          );\n      }\n    );\n\n\n    \/* =====================================================\n       API FETCH\n    ====================================================== *\/\n\n    fetch(API_URL)\n\n      .then(\n        function(response){\n\n          if(!response.ok){\n\n            throw new Error(\n              'HTTP ' +\n              response.status\n            );\n          }\n\n\n          return response.json();\n        }\n      )\n\n      .then(\n        function(data){\n\n          allRows =\n            Array.isArray(data)\n              ? data\n              : [];\n\n\n          melakaRows =\n            allRows.filter(\n              function(row){\n\n                return (\n                  String(\n                    row.state || ''\n                  )\n                  .trim()\n                  .toLowerCase()\n                  ===\n                  'melaka'\n                );\n              }\n            );\n\n\n          if(!melakaRows.length){\n\n            throw new Error(\n              'Tiada rekod Hutan Simpan Kekal Melaka ditemui.'\n            );\n          }\n\n\n          populateFilters();\n\n\n          el('forest-loading')\n            .style.display =\n            'none';\n\n\n          el('forest-area')\n            .style.display =\n            '';\n\n\n          redraw();\n        }\n      )\n\n      .catch(\n        function(error){\n\n          el('forest-loading')\n            .innerHTML =\n\n            '<div style=\"' +\n            'color:#dc2626;' +\n            'font-size:16px;' +\n            'font-weight:800;' +\n            'margin-bottom:8px' +\n            '\">' +\n\n            'Gagal memuatkan data' +\n\n            '<\/div>' +\n\n            '<p style=\"' +\n            'font-size:13px;' +\n            'color:#64748b' +\n            '\">' +\n\n            error.message +\n\n            '<\/p>';\n        }\n      );\n\n  }\n\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#water-wrap, #water-wrap *{\n  box-sizing:border-box;\n}\n\n#water-wrap{\n  --bg:#f8fafc;\n  --card:#ffffff;\n  --soft:#eff6ff;\n  --border:#dbeafe;\n  --text:#0f172a;\n  --muted:#64748b;\n  --accent:#2563eb;\n  --green:#16a34a;\n  --red:#dc2626;\n  --orange:#f97316;\n  --purple:#7c3aed;\n  --cyan:#0891b2;\n  --shadow:0 12px 30px rgba(15,23,42,.08);\n\n  width:100%;\n  padding:28px;\n  background:linear-gradient(\n    180deg,\n    #ffffff 0%,\n    var(--bg) 100%\n  );\n  border:1px solid var(--border);\n  border-radius:22px;\n  font-family:Arial,sans-serif;\n  color:var(--text);\n}\n\n#water-wrap .dash{\n  max-width:1180px;\n  margin:0 auto;\n}\n\n#water-wrap .hdr,\n#water-wrap .cc,\n#water-wrap .sc,\n#water-wrap .sbox{\n  background:var(--card);\n  border:1px solid var(--border);\n  box-shadow:var(--shadow);\n}\n\n#water-wrap .hdr{\n  border-radius:20px;\n  padding:26px;\n  margin-bottom:20px;\n}\n\n#water-wrap .eyebrow{\n  color:var(--accent);\n  font-size:12px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n  margin-bottom:10px;\n}\n\n#water-wrap h2{\n  margin:0;\n  font-size:clamp(26px,4vw,44px);\n  line-height:1.1;\n  font-weight:800;\n}\n\n#water-wrap .hdesc{\n  color:var(--muted);\n  margin-top:10px;\n  line-height:1.6;\n}\n\n#water-wrap .filter-bar{\n  display:grid;\n  grid-template-columns:repeat(2,1fr);\n  gap:14px;\n  background:#ffffff;\n  border:1px solid var(--border);\n  border-radius:16px;\n  padding:18px;\n  margin-bottom:20px;\n  box-shadow:var(--shadow);\n}\n\n#water-wrap .cg{\n  display:flex;\n  flex-direction:column;\n  gap:7px;\n}\n\n#water-wrap label{\n  font-size:11px;\n  font-weight:800;\n  color:var(--muted);\n  text-transform:uppercase;\n  letter-spacing:.08em;\n}\n\n#water-wrap select{\n  width:100%;\n  min-height:44px;\n  padding:10px 40px 10px 12px;\n  border:1.5px solid #bfdbfe;\n  border-radius:10px;\n  background-color:#ffffff;\n  color:var(--text);\n  font-size:14px;\n  font-weight:600;\n  outline:none;\n  appearance:none;\n  cursor:pointer;\n\n  background-image:url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n\n  background-repeat:no-repeat;\n  background-position:right 12px center;\n}\n\n#water-wrap select:focus{\n  border-color:var(--accent);\n  box-shadow:0 0 0 4px rgba(37,99,235,.12);\n}\n\n#water-wrap .stats{\n  display:grid;\n  grid-template-columns:repeat(4,1fr);\n  gap:14px;\n  margin-bottom:20px;\n}\n\n#water-wrap .sc{\n  border-radius:16px;\n  padding:18px;\n  border-left:4px solid var(--accent);\n}\n\n#water-wrap .sl{\n  color:var(--muted);\n  font-size:11px;\n  font-weight:800;\n  text-transform:uppercase;\n  margin-bottom:8px;\n}\n\n#water-wrap .sv{\n  font-size:25px;\n  font-weight:800;\n}\n\n#water-wrap .su{\n  color:var(--muted);\n  font-size:12px;\n  margin-top:4px;\n  line-height:1.35;\n}\n\n#water-wrap .tab-bar{\n  display:flex;\n  justify-content:space-between;\n  gap:10px;\n  background:var(--soft);\n  border:1px solid var(--border);\n  border-radius:999px;\n  padding:8px;\n  margin-bottom:18px;\n  overflow-x:auto;\n}\n\n#water-wrap .tbtn{\n  flex:1;\n  border:0;\n  background:transparent;\n  padding:12px 18px;\n  border-radius:999px;\n  color:var(--muted);\n  font-weight:800;\n  cursor:pointer;\n  white-space:nowrap;\n}\n\n#water-wrap .tbtn.active{\n  background:#ffffff;\n  color:var(--accent);\n  box-shadow:0 5px 14px rgba(15,23,42,.08);\n}\n\n#water-wrap .cc{\n  border-radius:20px;\n  padding:24px;\n  margin-bottom:18px;\n}\n\n#water-wrap .ct{\n  font-size:18px;\n  font-weight:800;\n  margin-bottom:5px;\n}\n\n#water-wrap .cs{\n  color:var(--muted);\n  font-size:13px;\n  margin-bottom:18px;\n  line-height:1.45;\n}\n\n#water-wrap .two{\n  display:grid;\n  grid-template-columns:1fr 1fr;\n  gap:18px;\n}\n\n#water-wrap .chart-box{\n  position:relative;\n  height:390px;\n}\n\n#water-wrap .chart-box-sm{\n  position:relative;\n  height:330px;\n}\n\n#water-wrap .sbox{\n  border-radius:20px;\n  padding:60px 24px;\n  text-align:center;\n  color:var(--muted);\n}\n\n#water-wrap .spin{\n  width:38px;\n  height:38px;\n  border:3px solid var(--border);\n  border-top-color:var(--accent);\n  border-radius:50%;\n  animation:water-spin .8s linear infinite;\n  margin:0 auto 16px;\n}\n\n@keyframes water-spin{\n  to{\n    transform:rotate(360deg);\n  }\n}\n\n@media(max-width:1024px){\n\n  #water-wrap .stats{\n    grid-template-columns:repeat(2,1fr);\n  }\n\n  #water-wrap .two{\n    grid-template-columns:1fr;\n  }\n}\n\n@media(max-width:600px){\n\n  #water-wrap{\n    padding:14px;\n    border-radius:16px;\n  }\n\n  #water-wrap .filter-bar,\n  #water-wrap .stats{\n    grid-template-columns:1fr;\n  }\n\n  #water-wrap .tab-bar{\n    justify-content:flex-start;\n  }\n\n  #water-wrap .tbtn{\n    flex:0 0 auto;\n  }\n\n  #water-wrap .chart-box,\n  #water-wrap .chart-box-sm{\n    height:300px;\n  }\n}\n<\/style>\n\n\n<div id=\"water-wrap\">\n\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n\n      <div class=\"eyebrow\">\n        Statistik Pengeluaran Air Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Pengeluaran Air Mengikut Negeri\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi pengeluaran air tahunan di Melaka dalam unit juta liter\n        sehari (MLD). Dashboard ini menunjukkan trend pengeluaran air,\n        perubahan tahunan, kedudukan Melaka, perbandingan dengan negeri-negeri\n        lain serta perbandingan dengan negeri pilihan.\n      <\/p>\n\n    <\/div>\n\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n\n        <label>\n          Tahun\n        <\/label>\n\n        <select\n          id=\"water-year\"\n        ><\/select>\n\n      <\/div>\n\n\n      <div class=\"cg\">\n\n        <label>\n          Negeri Perbandingan\n        <\/label>\n\n        <select\n          id=\"water-compare-state\"\n        ><\/select>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"water-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Tahun Dipilih\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"water-stat-year\"\n        >\n          \u2014\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Pengeluaran Air Melaka\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"water-stat-value\"\n        >\n          \u2014\n        <\/div>\n\n        <div class=\"su\">\n          juta liter sehari (MLD)\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Perubahan Tahunan\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"water-stat-change\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"water-stat-change-sub\"\n        >\n          berbanding tahun sebelumnya\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"sc\">\n\n        <div class=\"sl\">\n          Kedudukan Melaka\n        <\/div>\n\n        <div\n          class=\"sv\"\n          id=\"water-stat-rank\"\n        >\n          \u2014\n        <\/div>\n\n        <div\n          class=\"su\"\n          id=\"water-stat-rank-sub\"\n        >\n          antara negeri yang mempunyai data\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- LOADING -->\n    <div\n      class=\"sbox\"\n      id=\"water-loading\"\n    >\n\n      <div class=\"spin\"><\/div>\n\n      <strong>\n        Memuatkan data&#8230;\n      <\/strong>\n\n      <div>\n        Sedang mendapatkan rekod pengeluaran air Melaka.\n      <\/div>\n\n    <\/div>\n\n\n    <!-- DASHBOARD -->\n    <div\n      id=\"water-area\"\n      style=\"display:none\"\n    >\n\n      <!-- TABS -->\n      <div class=\"tab-bar\">\n\n        <button\n          class=\"tbtn active\"\n          data-tab=\"trend\"\n        >\n          Trend\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"change\"\n        >\n          Perubahan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"state\"\n        >\n          Negeri\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"ranking\"\n        >\n          Kedudukan\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"relative\"\n        >\n          Relatif\n        <\/button>\n\n        <button\n          class=\"tbtn\"\n          data-tab=\"comparison\"\n        >\n          Perbandingan\n        <\/button>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           TREND\n      =========================================== -->\n      <div id=\"water-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Trend Pengeluaran Air Melaka\n            <\/div>\n\n            <div class=\"cs\">\n              Pengeluaran air tahunan Melaka berdasarkan semua tahun\n              yang tersedia dalam dataset.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"water-chart-trend\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Indeks Pengeluaran Air\n            <\/div>\n\n            <div class=\"cs\">\n              Tahun pertama yang mempunyai data ditetapkan sebagai indeks 100\n              untuk menunjukkan perubahan relatif pengeluaran air Melaka.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"water-chart-index\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           CHANGE\n      =========================================== -->\n      <div\n        id=\"water-tab-change\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Perubahan Pengeluaran Air Tahunan\n            <\/div>\n\n            <div class=\"cs\">\n              Perbezaan pengeluaran air Melaka dalam MLD berbanding\n              tahun terdahulu yang tersedia.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"water-chart-change-value\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Kadar Perubahan Tahunan\n            <\/div>\n\n            <div class=\"cs\">\n              Peratus peningkatan atau pengurangan pengeluaran air\n              berbanding tahun terdahulu.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"water-chart-change-pct\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           STATE\n      =========================================== -->\n      <div\n        id=\"water-tab-state\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbandingan Pengeluaran Air Mengikut Negeri\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"water-state-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"water-chart-state\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Pengeluaran Air Negeri Berbanding Purata\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan nilai pengeluaran setiap negeri dengan\n            purata negeri bagi tahun dipilih.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"water-chart-state-average\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           RANKING\n      =========================================== -->\n      <div\n        id=\"water-tab-ranking\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Negeri dengan Pengeluaran Air Tertinggi\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"water-high-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"water-chart-high\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Negeri dengan Pengeluaran Air Terendah\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"water-low-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"water-chart-low\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           RELATIVE\n      =========================================== -->\n      <div\n        id=\"water-tab-relative\"\n        style=\"display:none\"\n      >\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Melaka Berbanding Purata Negeri\n            <\/div>\n\n            <div\n              class=\"cs\"\n              id=\"water-relative-average-sub\"\n            ><\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"water-chart-melaka-average\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n\n          <div class=\"cc\">\n\n            <div class=\"ct\">\n              Kedudukan Relatif Mengikut Negeri\n            <\/div>\n\n            <div class=\"cs\">\n              Negeri dengan pengeluaran tertinggi ditetapkan sebagai 100%.\n              Melaka ditonjolkan dalam warna oren.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n\n              <canvas\n                id=\"water-chart-relative\"\n              ><\/canvas>\n\n            <\/div>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- ==========================================\n           COMPARISON\n      =========================================== -->\n      <div\n        id=\"water-tab-comparison\"\n        style=\"display:none\"\n      >\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Trend Melaka berbanding Negeri Pilihan\n          <\/div>\n\n          <div\n            class=\"cs\"\n            id=\"water-comparison-sub\"\n          ><\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"water-chart-comparison\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"cc\">\n\n          <div class=\"ct\">\n            Perbezaan Pengeluaran Air dengan Negeri Pilihan\n          <\/div>\n\n          <div class=\"cs\">\n            Nilai positif menunjukkan pengeluaran Melaka lebih tinggi,\n            manakala nilai negatif menunjukkan negeri perbandingan mempunyai\n            pengeluaran air yang lebih tinggi.\n          <\/div>\n\n          <div class=\"chart-box\">\n\n            <canvas\n              id=\"water-chart-gap\"\n            ><\/canvas>\n\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n  function init(){\n\n    if(typeof Chart === 'undefined'){\n      setTimeout(init,500);\n      return;\n    }\n\n\n    var API_URL =\n      'https:\/\/api.data.gov.my\/data-catalogue\/?id=water_production&limit=1000';\n\n    var FIXED_STATE =\n      'Melaka';\n\n    var AGGREGATE_STATE =\n      'Malaysia';\n\n    var allRows = [];\n    var melakaRows = [];\n    var charts = {};\n\n\n    Chart.defaults.color =\n      '#64748b';\n\n    Chart.defaults.borderColor =\n      '#dbeafe';\n\n    Chart.defaults.font.family =\n      'Arial, sans-serif';\n\n\n    \/* =====================================================\n       BASIC HELPERS\n    ====================================================== *\/\n\n    function el(id){\n      return document.getElementById(id);\n    }\n\n\n    function yr(row){\n\n      return parseInt(\n        String(\n          row.date || ''\n        ).slice(0,4),\n        10\n      );\n    }\n\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === ''\n      ){\n        return null;\n      }\n\n\n      var n =\n        Number(value);\n\n\n      return isNaN(n)\n        ? null\n        : n;\n    }\n\n\n    function valid(value){\n\n      return (\n        value !== null &&\n        value !== undefined &&\n        !isNaN(value)\n      );\n    }\n\n\n    function fmt(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n\n      var d =\n        digits === undefined\n          ? 1\n          : digits;\n\n\n      return Number(value)\n        .toLocaleString(\n          'ms-MY',\n          {\n            minimumFractionDigits:d,\n            maximumFractionDigits:d\n          }\n        );\n    }\n\n\n    function signed(\n      value,\n      digits\n    ){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(\n          value,\n          digits === undefined\n            ? 1\n            : digits\n        )\n      );\n    }\n\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return '\u2014';\n      }\n\n\n      return (\n        (value > 0 ? '+' : '') +\n        fmt(value,2) +\n        '%'\n      );\n    }\n\n\n    function destroyChart(name){\n\n      if(charts[name]){\n        charts[name].destroy();\n        delete charts[name];\n      }\n    }\n\n\n    function percentChange(\n      current,\n      previous\n    ){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n\n      return (\n        (\n          current -\n          previous\n        ) \/\n        previous\n      ) * 100;\n    }\n\n\n    \/* =====================================================\n       DATA HELPERS\n    ====================================================== *\/\n\n    function availableYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows\n            .map(yr)\n            .filter(function(year){\n              return !isNaN(year);\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n\n    function getPreviousYear(\n      currentYear\n    ){\n\n      var years =\n        availableYears()\n        .filter(function(year){\n          return year < currentYear;\n        })\n        .sort(function(a,b){\n          return b-a;\n        });\n\n\n      return years.length\n        ? years[0]\n        : null;\n    }\n\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .map(function(row){\n              return row.state;\n            })\n            .filter(function(state){\n\n              return (\n                state &#038;&#038;\n                state !== AGGREGATE_STATE\n              );\n            })\n        )\n      )\n      .sort();\n    }\n\n\n    function getComparisonStates(){\n\n      return getStates()\n        .filter(function(state){\n          return state !== FIXED_STATE;\n        });\n    }\n\n\n    function stateValue(\n      state,\n      year\n    ){\n\n      var values =\n        allRows\n        .filter(function(row){\n\n          return (\n            row.state === state &#038;&#038;\n            yr(row) === year\n          );\n        })\n        .map(function(row){\n          return num(row.value);\n        })\n        .filter(valid);\n\n\n      if(!values.length){\n        return null;\n      }\n\n\n      \/*\n        Dataset is expected to contain one state-year row.\n        If duplicate records exist, average them rather than sum,\n        since water production is already a daily production rate (MLD).\n      *\/\n\n      return values.reduce(\n        function(a,b){\n          return a+b;\n        },\n        0\n      ) \/ values.length;\n    }\n\n\n    function melakaValue(year){\n\n      return stateValue(\n        FIXED_STATE,\n        year\n      );\n    }\n\n\n    function malaysiaValue(year){\n\n      return stateValue(\n        AGGREGATE_STATE,\n        year\n      );\n    }\n\n\n    function stateDataForYear(year){\n\n      return getStates()\n        .map(\n          function(state){\n\n            return {\n\n              state:state,\n\n              value:\n                stateValue(\n                  state,\n                  year\n                )\n            };\n          }\n        )\n        .filter(function(item){\n          return valid(item.value);\n        });\n    }\n\n\n    function stateAverage(year){\n\n      var data =\n        stateDataForYear(year);\n\n\n      if(!data.length){\n        return null;\n      }\n\n\n      return data.reduce(\n        function(total,item){\n          return total + item.value;\n        },\n        0\n      ) \/ data.length;\n    }\n\n\n    function getSelected(){\n\n      return {\n\n        year:\n          parseInt(\n            el('water-year').value,\n            10\n          ),\n\n        compareState:\n          el('water-compare-state').value\n      };\n    }\n\n\n    \/* =====================================================\n       FILTER POPULATION\n    ====================================================== *\/\n\n    function populateFilters(){\n\n      var years =\n        availableYears()\n        .slice()\n        .sort(function(a,b){\n          return b-a;\n        });\n\n\n      el('water-year')\n        .innerHTML =\n\n        years.map(\n          function(year){\n\n            return (\n              '<option value=\"' +\n              year +\n              '\">' +\n              year +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      if(years.length){\n\n        el('water-year')\n          .value =\n          years[0];\n      }\n\n\n      var states =\n        getComparisonStates();\n\n\n      el('water-compare-state')\n        .innerHTML =\n\n        states.map(\n          function(state){\n\n            return (\n              '<option value=\"' +\n              state +\n              '\">' +\n              state +\n              '<\/option>'\n            );\n          }\n        ).join('');\n\n\n      \/*\n        Negeri Sembilan is used as the default comparison\n        because it is geographically adjacent to Melaka.\n      *\/\n\n      if(\n        states.indexOf(\n          'Negeri Sembilan'\n        ) !== -1\n      ){\n\n        el('water-compare-state')\n          .value =\n          'Negeri Sembilan';\n\n      }else if(states.length){\n\n        el('water-compare-state')\n          .value =\n          states[0];\n      }\n    }\n\n\n    \/* =====================================================\n       SUMMARY CARDS\n    ====================================================== *\/\n\n    function updateCards(){\n\n      var s =\n        getSelected();\n\n\n      var current =\n        melakaValue(\n          s.year\n        );\n\n\n      var previousYear =\n        getPreviousYear(\n          s.year\n        );\n\n\n      var previous =\n        previousYear !== null\n          ? melakaValue(\n              previousYear\n            )\n          : null;\n\n\n      var change =\n        previousYear !== null\n          ? percentChange(\n              current,\n              previous\n            )\n          : null;\n\n\n      var ranking =\n        stateDataForYear(\n          s.year\n        )\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var rankIndex =\n        ranking.findIndex(\n          function(item){\n            return item.state === FIXED_STATE;\n          }\n        );\n\n\n      el('water-stat-year')\n        .textContent =\n        s.year || '\u2014';\n\n\n      el('water-stat-value')\n        .textContent =\n        valid(current)\n          ? fmt(current,1)\n          : '\u2014';\n\n\n      el('water-stat-change')\n        .textContent =\n        valid(change)\n          ? signedPct(change)\n          : '\u2014';\n\n\n      el('water-stat-change-sub')\n        .textContent =\n        previousYear !== null\n          ? (\n              'berbanding ' +\n              previousYear\n            )\n          : 'tiada tahun sebelumnya';\n\n\n      el('water-stat-rank')\n        .textContent =\n        rankIndex >= 0\n          ? (\n              '#' +\n              (rankIndex + 1)\n            )\n          : '\u2014';\n\n\n      el('water-stat-rank-sub')\n        .textContent =\n        rankIndex >= 0\n          ? (\n              'daripada ' +\n              ranking.length +\n              ' negeri'\n            )\n          : 'tiada data kedudukan';\n\n\n      el('water-stats')\n        .style.display =\n        '';\n    }\n\n\n    \/* =====================================================\n       TOOLTIP\n    ====================================================== *\/\n\n    function tooltipMLD(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.y,\n                1\n              ) +\n              ' MLD'\n            );\n          }\n        }\n      };\n    }\n\n\n    function tooltipHorizontalMLD(){\n\n      return {\n\n        backgroundColor:'#0f172a',\n\n        callbacks:{\n\n          label:function(c){\n\n            return (\n              ' ' +\n              c.dataset.label +\n              ': ' +\n              fmt(\n                c.parsed.x,\n                1\n              ) +\n              ' MLD'\n            );\n          }\n        }\n      };\n    }\n\n\n    \/* =====================================================\n       TREND\n    ====================================================== *\/\n\n    function buildTrendCharts(){\n\n      var years =\n        availableYears();\n\n\n      var values =\n        years.map(\n          function(year){\n            return melakaValue(year);\n          }\n        );\n\n\n      destroyChart('trend');\n\n\n      charts.trend =\n        new Chart(\n          el('water-chart-trend')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Air',\n\n                data:values,\n\n                borderColor:'#2563eb',\n\n                backgroundColor:\n                  'rgba(37,99,235,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipMLD()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  title:{\n                    display:true,\n                    text:'Juta Liter Sehari (MLD)'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/*\n        Index:\n        first valid Melaka value = 100\n      *\/\n\n      var baseValue = null;\n\n\n      for(\n        var i=0;\n        i<values.length;\n        i++\n      ){\n\n        if(\n          valid(values[i]) &#038;&#038;\n          values[i] !== 0\n        ){\n\n          baseValue =\n            values[i];\n\n          break;\n        }\n      }\n\n\n      var indexValues =\n        values.map(\n          function(value){\n\n            if(\n              !valid(value) ||\n              !valid(baseValue) ||\n              baseValue === 0\n            ){\n              return null;\n            }\n\n\n            return (\n              value \/\n              baseValue\n            ) * 100;\n          }\n        );\n\n\n      destroyChart('index');\n\n\n      charts.index =\n        new Chart(\n          el('water-chart-index')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Indeks Pengeluaran Air',\n\n                data:indexValues,\n\n                borderColor:'#7c3aed',\n\n                backgroundColor:\n                  'rgba(124,58,237,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Indeks: ' +\n                        fmt(\n                          c.parsed.y,\n                          2\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       CHANGE\n    ====================================================== *\/\n\n    function buildChangeCharts(){\n\n      var years =\n        availableYears();\n\n\n      var valueChanges = [];\n      var pctChanges = [];\n\n\n      years.forEach(\n        function(year,index){\n\n          if(index === 0){\n            return;\n          }\n\n\n          var previousYear =\n            years[index-1];\n\n\n          var current =\n            melakaValue(\n              year\n            );\n\n\n          var previous =\n            melakaValue(\n              previousYear\n            );\n\n\n          valueChanges.push({\n\n            label:\n              year +\n              ' vs ' +\n              previousYear,\n\n            value:\n              valid(current) &#038;&#038;\n              valid(previous)\n                ? current - previous\n                : null\n          });\n\n\n          pctChanges.push({\n\n            label:\n              year +\n              ' vs ' +\n              previousYear,\n\n            value:\n              percentChange(\n                current,\n                previous\n              )\n          });\n        }\n      );\n\n\n      destroyChart(\n        'changeValue'\n      );\n\n\n      charts.changeValue =\n        new Chart(\n          el('water-chart-change-value')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                valueChanges.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perubahan Pengeluaran',\n\n                data:\n                  valueChanges.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  valueChanges.map(\n                    function(item){\n\n                      return (\n                        valid(item.value) &#038;&#038;\n                        item.value >= 0\n                      )\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signed(\n                          c.parsed.y,\n                          1\n                        ) +\n                        ' MLD'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      destroyChart(\n        'changePct'\n      );\n\n\n      charts.changePct =\n        new Chart(\n          el('water-chart-change-pct')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:\n                pctChanges.map(\n                  function(item){\n                    return item.label;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Kadar Perubahan',\n\n                data:\n                  pctChanges.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                borderColor:'#7c3aed',\n\n                backgroundColor:\n                  'rgba(124,58,237,.15)',\n\n                fill:true,\n\n                tension:.35,\n\n                borderWidth:2.5,\n\n                pointRadius:4\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perubahan: ' +\n                        signedPct(\n                          c.parsed.y\n                        )\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,2)+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       STATE\n    ====================================================== *\/\n\n    function buildStateCharts(){\n\n      var s =\n        getSelected();\n\n\n      var data =\n        stateDataForYear(\n          s.year\n        )\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      el('water-state-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 semua negeri yang mempunyai rekod \u00b7 Melaka ditonjolkan dalam warna oren';\n\n\n      destroyChart('state');\n\n\n      charts.state =\n        new Chart(\n          el('water-chart-state')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                data.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Air',\n\n                data:\n                  data.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        FIXED_STATE\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.70)';\n                    }\n                  ),\n\n                borderColor:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        FIXED_STATE\n                        ? '#f97316'\n                        : 'transparent';\n                    }\n                  ),\n\n                borderWidth:\n                  data.map(\n                    function(item){\n\n                      return item.state ===\n                        FIXED_STATE\n                        ? 3\n                        : 0;\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipMLD()\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n\n                    maxRotation:45,\n\n                    color:function(context){\n\n                      return (\n                        context.tick.label ===\n                        FIXED_STATE\n                      )\n                        ? '#f97316'\n                        : '#64748b';\n                    },\n\n                    font:function(context){\n\n                      return {\n\n                        weight:\n                          context.tick.label ===\n                          FIXED_STATE\n                            ? '800'\n                            : '400',\n\n                        size:\n                          context.tick.label ===\n                          FIXED_STATE\n                            ? 13\n                            : 12\n                      };\n                    }\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  title:{\n                    display:true,\n                    text:'MLD'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      \/*\n        Difference versus average state production\n      *\/\n\n      var average =\n        stateAverage(\n          s.year\n        );\n\n\n      var averageData =\n        data.map(\n          function(item){\n\n            return {\n\n              state:\n                item.state,\n\n              value:\n                valid(average)\n                  ? item.value - average\n                  : null\n            };\n          }\n        );\n\n\n      destroyChart(\n        'stateAverage'\n      );\n\n\n      charts.stateAverage =\n        new Chart(\n          el('water-chart-state-average')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                averageData.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Perbezaan daripada Purata',\n\n                data:\n                  averageData.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  averageData.map(\n                    function(item){\n\n                      if(\n                        item.state ===\n                        FIXED_STATE\n                      ){\n                        return 'rgba(249,115,22,.95)';\n                      }\n\n\n                      return (\n                        valid(item.value) &&\n                        item.value >= 0\n                      )\n                        ? 'rgba(22,163,74,.70)'\n                        : 'rgba(220,38,38,.70)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Perbezaan daripada purata: ' +\n                        signed(\n                          c.parsed.y,\n                          1\n                        ) +\n                        ' MLD'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       RANKING\n    ====================================================== *\/\n\n    function buildRankingCharts(){\n\n      var s =\n        getSelected();\n\n\n      var data =\n        stateDataForYear(\n          s.year\n        )\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var limit =\n        Math.min(\n          5,\n          data.length\n        );\n\n\n      var high =\n        data.slice(\n          0,\n          limit\n        );\n\n\n      var low =\n        data.slice(\n          -limit\n        )\n        .sort(function(a,b){\n          return a.value-b.value;\n        });\n\n\n      el('water-high-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 lima negeri dengan pengeluaran air tertinggi';\n\n\n      el('water-low-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 lima negeri dengan pengeluaran air terendah';\n\n\n      function makeRankingChart(\n        id,\n        name,\n        dataset,\n        baseColor\n      ){\n\n        destroyChart(name);\n\n\n        charts[name] =\n          new Chart(\n            el(id)\n              .getContext('2d'),\n            {\n\n              type:'bar',\n\n              data:{\n\n                labels:\n                  dataset.map(\n                    function(item){\n                      return item.state;\n                    }\n                  ),\n\n                datasets:[{\n\n                  label:\n                    'Pengeluaran Air',\n\n                  data:\n                    dataset.map(\n                      function(item){\n                        return item.value;\n                      }\n                    ),\n\n                  backgroundColor:\n                    dataset.map(\n                      function(item){\n\n                        return item.state ===\n                          FIXED_STATE\n                          ? 'rgba(249,115,22,.95)'\n                          : baseColor;\n                      }\n                    ),\n\n                  borderRadius:8\n                }]\n              },\n\n              options:{\n\n                responsive:true,\n\n                maintainAspectRatio:false,\n\n                plugins:{\n\n                  legend:{\n                    display:false\n                  },\n\n                  tooltip:\n                    tooltipMLD()\n                },\n\n                scales:{\n\n                  x:{\n\n                    grid:{\n                      display:false\n                    },\n\n                    ticks:{\n                      maxRotation:45\n                    }\n                  },\n\n                  y:{\n\n                    beginAtZero:true,\n\n                    grid:{\n                      color:'#dbeafe'\n                    },\n\n                    ticks:{\n                      callback:function(value){\n                        return fmt(value,0);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          );\n      }\n\n\n      makeRankingChart(\n        'water-chart-high',\n        'high',\n        high,\n        'rgba(22,163,74,.78)'\n      );\n\n\n      makeRankingChart(\n        'water-chart-low',\n        'low',\n        low,\n        'rgba(220,38,38,.78)'\n      );\n    }\n\n\n    \/* =====================================================\n       RELATIVE\n    ====================================================== *\/\n\n    function buildRelativeCharts(){\n\n      var s =\n        getSelected();\n\n\n      var data =\n        stateDataForYear(\n          s.year\n        )\n        .sort(function(a,b){\n          return b.value-a.value;\n        });\n\n\n      var melaka =\n        melakaValue(\n          s.year\n        );\n\n\n      var average =\n        stateAverage(\n          s.year\n        );\n\n\n      el('water-relative-average-sub')\n        .textContent =\n        s.year +\n        ' \u00b7 perbandingan nilai Melaka dengan purata semua negeri';\n\n\n      destroyChart(\n        'melakaAverage'\n      );\n\n\n      charts.melakaAverage =\n        new Chart(\n          el('water-chart-melaka-average')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:[\n                'Melaka',\n                'Purata Negeri'\n              ],\n\n              datasets:[{\n\n                label:\n                  'Pengeluaran Air',\n\n                data:[\n                  melaka,\n                  average\n                ],\n\n                backgroundColor:[\n                  'rgba(249,115,22,.95)',\n                  'rgba(37,99,235,.75)'\n                ],\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:\n                  tooltipMLD()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var highest =\n        data.length\n          ? data[0].value\n          : null;\n\n\n      var relative =\n        data.map(\n          function(item){\n\n            return {\n\n              state:item.state,\n\n              value:\n                valid(highest) &&\n                highest !== 0\n                  ? (\n                      item.value \/\n                      highest\n                    ) * 100\n                  : null\n            };\n          }\n        );\n\n\n      destroyChart('relative');\n\n\n      charts.relative =\n        new Chart(\n          el('water-chart-relative')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:\n                relative.map(\n                  function(item){\n                    return item.state;\n                  }\n                ),\n\n              datasets:[{\n\n                label:\n                  'Kedudukan Relatif',\n\n                data:\n                  relative.map(\n                    function(item){\n                      return item.value;\n                    }\n                  ),\n\n                backgroundColor:\n                  relative.map(\n                    function(item){\n\n                      return item.state ===\n                        FIXED_STATE\n                        ? 'rgba(249,115,22,.95)'\n                        : 'rgba(37,99,235,.70)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Kedudukan relatif: ' +\n                        fmt(\n                          c.parsed.y,\n                          1\n                        ) +\n                        '%'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n\n                  grid:{\n                    display:false\n                  },\n\n                  ticks:{\n                    maxRotation:45\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n                  max:110,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return value+'%';\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       COMPARISON\n    ====================================================== *\/\n\n    function buildComparisonCharts(){\n\n      var s =\n        getSelected();\n\n\n      var years =\n        availableYears();\n\n\n      el('water-comparison-sub')\n        .textContent =\n        'Melaka berbanding ' +\n        s.compareState +\n        ' \u00b7 sepanjang tempoh data';\n\n\n      destroyChart('comparison');\n\n\n      charts.comparison =\n        new Chart(\n          el('water-chart-comparison')\n            .getContext('2d'),\n          {\n\n            type:'line',\n\n            data:{\n\n              labels:years,\n\n              datasets:[\n\n                {\n                  label:'Melaka',\n\n                  data:\n                    years.map(\n                      function(year){\n\n                        return melakaValue(\n                          year\n                        );\n                      }\n                    ),\n\n                  borderColor:'#f97316',\n\n                  backgroundColor:\n                    'rgba(249,115,22,.10)',\n\n                  tension:.35,\n\n                  borderWidth:3,\n\n                  pointRadius:4\n                },\n\n                {\n                  label:\n                    s.compareState,\n\n                  data:\n                    years.map(\n                      function(year){\n\n                        return stateValue(\n                          s.compareState,\n                          year\n                        );\n                      }\n                    ),\n\n                  borderColor:'#2563eb',\n\n                  backgroundColor:\n                    'rgba(37,99,235,.10)',\n\n                  tension:.35,\n\n                  borderWidth:2.5,\n\n                  pointRadius:4\n                }\n\n              ]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              interaction:{\n                mode:'index',\n                intersect:false\n              },\n\n              plugins:{\n\n                legend:{\n                  position:'bottom'\n                },\n\n                tooltip:\n                  tooltipMLD()\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    color:'#dbeafe'\n                  }\n                },\n\n                y:{\n\n                  beginAtZero:true,\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  title:{\n                    display:true,\n                    text:'MLD'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n\n\n      var gaps =\n        years.map(\n          function(year){\n\n            var melaka =\n              melakaValue(\n                year\n              );\n\n\n            var comparison =\n              stateValue(\n                s.compareState,\n                year\n              );\n\n\n            return (\n              valid(melaka) &&\n              valid(comparison)\n            )\n              ? melaka - comparison\n              : null;\n          }\n        );\n\n\n      destroyChart('gap');\n\n\n      charts.gap =\n        new Chart(\n          el('water-chart-gap')\n            .getContext('2d'),\n          {\n\n            type:'bar',\n\n            data:{\n\n              labels:years,\n\n              datasets:[{\n\n                label:\n                  'Perbezaan Pengeluaran',\n\n                data:gaps,\n\n                backgroundColor:\n                  gaps.map(\n                    function(value){\n\n                      return (\n                        valid(value) &&\n                        value >= 0\n                      )\n                        ? 'rgba(22,163,74,.78)'\n                        : 'rgba(220,38,38,.78)';\n                    }\n                  ),\n\n                borderRadius:8\n              }]\n            },\n\n            options:{\n\n              responsive:true,\n\n              maintainAspectRatio:false,\n\n              plugins:{\n\n                legend:{\n                  display:false\n                },\n\n                tooltip:{\n\n                  backgroundColor:'#0f172a',\n\n                  callbacks:{\n\n                    label:function(c){\n\n                      return (\n                        ' Melaka - ' +\n                        s.compareState +\n                        ': ' +\n                        signed(\n                          c.parsed.y,\n                          1\n                        ) +\n                        ' MLD'\n                      );\n                    }\n                  }\n                }\n              },\n\n              scales:{\n\n                x:{\n                  grid:{\n                    display:false\n                  }\n                },\n\n                y:{\n\n                  grid:{\n                    color:'#dbeafe'\n                  },\n\n                  ticks:{\n                    callback:function(value){\n                      return fmt(value,0);\n                    }\n                  }\n                }\n              }\n            }\n          }\n        );\n    }\n\n\n    \/* =====================================================\n       TAB CONTROL\n    ====================================================== *\/\n\n    function activeTab(){\n\n      var active =\n        document.querySelector(\n          '#water-wrap .tbtn.active'\n        );\n\n\n      return active\n        ? active.getAttribute(\n            'data-tab'\n          )\n        : 'trend';\n    }\n\n\n    function redraw(){\n\n      updateCards();\n\n\n      var tab =\n        activeTab();\n\n\n      if(tab === 'trend'){\n        buildTrendCharts();\n      }\n\n\n      if(tab === 'change'){\n        buildChangeCharts();\n      }\n\n\n      if(tab === 'state'){\n        buildStateCharts();\n      }\n\n\n      if(tab === 'ranking'){\n        buildRankingCharts();\n      }\n\n\n      if(tab === 'relative'){\n        buildRelativeCharts();\n      }\n\n\n      if(tab === 'comparison'){\n        buildComparisonCharts();\n      }\n    }\n\n\n    \/* =====================================================\n       TAB EVENTS\n    ====================================================== *\/\n\n    document\n      .querySelectorAll(\n        '#water-wrap .tbtn'\n      )\n      .forEach(\n        function(btn){\n\n          btn.addEventListener(\n            'click',\n            function(){\n\n              document\n                .querySelectorAll(\n                  '#water-wrap .tbtn'\n                )\n                .forEach(\n                  function(button){\n\n                    button.classList\n                      .remove('active');\n                  }\n                );\n\n\n              btn.classList\n                .add('active');\n\n\n              [\n                'trend',\n                'change',\n                'state',\n                'ranking',\n                'relative',\n                'comparison'\n              ]\n              .forEach(\n                function(tab){\n\n                  el(\n                    'water-tab-' +\n                    tab\n                  ).style.display =\n                    'none';\n                }\n              );\n\n\n              el(\n                'water-tab-' +\n                btn.getAttribute(\n                  'data-tab'\n                )\n              ).style.display =\n                '';\n\n\n              redraw();\n            }\n          );\n        }\n      );\n\n\n    \/* =====================================================\n       FILTER EVENTS\n    ====================================================== *\/\n\n    [\n      'water-year',\n      'water-compare-state'\n    ]\n    .forEach(\n      function(id){\n\n        el(id)\n          .addEventListener(\n            'change',\n            redraw\n          );\n      }\n    );\n\n\n    \/* =====================================================\n       API FETCH\n    ====================================================== *\/\n\n    fetch(API_URL)\n\n      .then(\n        function(response){\n\n          if(!response.ok){\n\n            throw new Error(\n              'HTTP ' +\n              response.status\n            );\n          }\n\n\n          return response.json();\n        }\n      )\n\n      .then(\n        function(data){\n\n          allRows =\n            Array.isArray(data)\n              ? data\n              : [];\n\n\n          melakaRows =\n            allRows.filter(\n              function(row){\n\n                return (\n                  String(\n                    row.state || ''\n                  )\n                  .trim()\n                  .toLowerCase()\n                  ===\n                  'melaka'\n                );\n              }\n            );\n\n\n          if(!melakaRows.length){\n\n            throw new Error(\n              'Tiada rekod pengeluaran air Melaka ditemui.'\n            );\n          }\n\n\n          populateFilters();\n\n\n          el('water-loading')\n            .style.display =\n            'none';\n\n\n          el('water-area')\n            .style.display =\n            '';\n\n\n          redraw();\n        }\n      )\n\n      .catch(\n        function(error){\n\n          el('water-loading')\n            .innerHTML =\n\n            '<div style=\"' +\n            'color:#dc2626;' +\n            'font-size:16px;' +\n            'font-weight:800;' +\n            'margin-bottom:8px' +\n            '\">' +\n\n            'Gagal memuatkan data' +\n\n            '<\/div>' +\n\n            '<p style=\"' +\n            'font-size:13px;' +\n            'color:#64748b' +\n            '\">' +\n\n            error.message +\n\n            '<\/p>';\n        }\n      );\n\n  }\n\n\n  if(\n    document.readyState ===\n    'loading'\n  ){\n\n    document.addEventListener(\n      'DOMContentLoaded',\n      init\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#watercons-wrap,\n#watercons-wrap * {\n  box-sizing: border-box;\n}\n\n#watercons-wrap {\n  --bg: #f8fafc;\n  --card: #ffffff;\n  --soft: #eff6ff;\n  --border: #dbeafe;\n  --text: #0f172a;\n  --muted: #64748b;\n  --accent: #2563eb;\n  --green: #16a34a;\n  --red: #dc2626;\n  --orange: #f97316;\n  --purple: #7c3aed;\n  --cyan: #0891b2;\n  --shadow: 0 12px 30px rgba(15,23,42,.08);\n\n  width: 100%;\n  padding: 28px;\n  background: linear-gradient(180deg,#fff 0%,var(--bg) 100%);\n  border: 1px solid var(--border);\n  border-radius: 22px;\n  font-family: Arial,sans-serif;\n  color: var(--text);\n}\n\n#watercons-wrap .dash {\n  max-width: 1180px;\n  margin: 0 auto;\n}\n\n#watercons-wrap .hdr,\n#watercons-wrap .filter-bar,\n#watercons-wrap .sc,\n#watercons-wrap .cc,\n#watercons-wrap .sbox {\n  background: var(--card);\n  border: 1px solid var(--border);\n  box-shadow: var(--shadow);\n}\n\n#watercons-wrap .hdr {\n  border-radius: 20px;\n  padding: 26px;\n  margin-bottom: 20px;\n}\n\n#watercons-wrap .eyebrow {\n  color: var(--accent);\n  font-size: 12px;\n  font-weight: 800;\n  letter-spacing: .12em;\n  text-transform: uppercase;\n  margin-bottom: 10px;\n}\n\n#watercons-wrap h2 {\n  margin: 0;\n  font-size: clamp(26px,4vw,44px);\n  line-height: 1.12;\n  font-weight: 800;\n}\n\n#watercons-wrap .hdesc {\n  color: var(--muted);\n  margin: 12px 0 0;\n  line-height: 1.65;\n  font-size: 14px;\n}\n\n#watercons-wrap .filter-bar {\n  display: grid;\n  grid-template-columns: repeat(3,minmax(0,1fr));\n  gap: 14px;\n  border-radius: 16px;\n  padding: 18px;\n  margin-bottom: 20px;\n}\n\n#watercons-wrap .cg {\n  display: flex;\n  flex-direction: column;\n  gap: 7px;\n  min-width: 0;\n}\n\n#watercons-wrap label {\n  font-size: 11px;\n  font-weight: 800;\n  color: var(--muted);\n  text-transform: uppercase;\n  letter-spacing: .08em;\n}\n\n#watercons-wrap select {\n  width: 100%;\n  min-height: 44px;\n  padding: 10px 40px 10px 12px;\n  border: 1.5px solid #bfdbfe;\n  border-radius: 10px;\n  background-color: #fff;\n  color: var(--text);\n  font-size: 14px;\n  font-weight: 600;\n  outline: none;\n  appearance: none;\n  cursor: pointer;\n  background-image: url(\"data:image\/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%232563eb' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'\/%3E%3C\/svg%3E\");\n  background-repeat: no-repeat;\n  background-position: right 12px center;\n}\n\n#watercons-wrap select:focus {\n  border-color: var(--accent);\n  box-shadow: 0 0 0 4px rgba(37,99,235,.12);\n}\n\n#watercons-wrap .stats {\n  display: grid;\n  grid-template-columns: repeat(4,minmax(0,1fr));\n  gap: 14px;\n  margin-bottom: 20px;\n}\n\n#watercons-wrap .sc {\n  border-radius: 16px;\n  padding: 18px;\n  border-left: 4px solid var(--accent);\n  min-width: 0;\n}\n\n#watercons-wrap .sc:nth-child(2) {\n  border-left-color: var(--cyan);\n}\n\n#watercons-wrap .sc:nth-child(3) {\n  border-left-color: var(--purple);\n}\n\n#watercons-wrap .sc:nth-child(4) {\n  border-left-color: var(--orange);\n}\n\n#watercons-wrap .sl {\n  color: var(--muted);\n  font-size: 11px;\n  font-weight: 800;\n  text-transform: uppercase;\n  margin-bottom: 8px;\n  line-height: 1.4;\n}\n\n#watercons-wrap .sv {\n  font-size: clamp(19px,2.3vw,25px);\n  font-weight: 800;\n  overflow-wrap: anywhere;\n}\n\n#watercons-wrap .su {\n  color: var(--muted);\n  font-size: 12px;\n  margin-top: 5px;\n  line-height: 1.45;\n}\n\n#watercons-wrap .tab-bar {\n  display: flex;\n  justify-content: space-between;\n  gap: 8px;\n  background: var(--soft);\n  border: 1px solid var(--border);\n  border-radius: 999px;\n  padding: 8px;\n  margin-bottom: 18px;\n  overflow-x: auto;\n  scrollbar-width: thin;\n}\n\n#watercons-wrap .tbtn {\n  flex: 1;\n  border: 0;\n  background: transparent;\n  padding: 12px 14px;\n  border-radius: 999px;\n  color: var(--muted);\n  font-weight: 800;\n  font-size: 13px;\n  cursor: pointer;\n  white-space: nowrap;\n  font-family: inherit;\n}\n\n#watercons-wrap .tbtn.active {\n  background: #fff;\n  color: var(--accent);\n  box-shadow: 0 5px 14px rgba(15,23,42,.08);\n}\n\n#watercons-wrap .tbtn:focus-visible {\n  outline: 2px solid var(--accent);\n  outline-offset: 2px;\n}\n\n#watercons-wrap .cc {\n  border-radius: 20px;\n  padding: 24px;\n  margin-bottom: 18px;\n  min-width: 0;\n}\n\n#watercons-wrap .ct {\n  font-size: 18px;\n  font-weight: 800;\n  margin-bottom: 6px;\n  line-height: 1.4;\n}\n\n#watercons-wrap .cs {\n  color: var(--muted);\n  font-size: 13px;\n  margin-bottom: 18px;\n  line-height: 1.5;\n}\n\n#watercons-wrap .two {\n  display: grid;\n  grid-template-columns: repeat(2,minmax(0,1fr));\n  gap: 18px;\n}\n\n#watercons-wrap .chart-box {\n  position: relative;\n  height: 390px;\n  width: 100%;\n}\n\n#watercons-wrap .chart-box-sm {\n  position: relative;\n  height: 330px;\n  width: 100%;\n}\n\n#watercons-wrap canvas {\n  max-width: 100%;\n}\n\n#watercons-wrap .sbox {\n  border-radius: 20px;\n  padding: 60px 24px;\n  text-align: center;\n  color: var(--muted);\n  line-height: 1.6;\n}\n\n#watercons-wrap .spin {\n  width: 38px;\n  height: 38px;\n  border: 3px solid var(--border);\n  border-top-color: var(--accent);\n  border-radius: 50%;\n  animation: watercons-spin .8s linear infinite;\n  margin: 0 auto 16px;\n}\n\n@keyframes watercons-spin {\n  to { transform: rotate(360deg); }\n}\n\n#watercons-wrap .note {\n  background: #eff6ff;\n  border: 1px solid #bfdbfe;\n  border-radius: 12px;\n  padding: 13px 16px;\n  color: #475569;\n  font-size: 12px;\n  line-height: 1.6;\n  margin-top: 2px;\n}\n\n@media(max-width:1024px) {\n  #watercons-wrap .stats {\n    grid-template-columns: repeat(2,minmax(0,1fr));\n  }\n\n  #watercons-wrap .two {\n    grid-template-columns: 1fr;\n  }\n}\n\n@media(max-width:700px) {\n  #watercons-wrap .filter-bar {\n    grid-template-columns: 1fr;\n  }\n}\n\n@media(max-width:600px) {\n  #watercons-wrap {\n    padding: 14px;\n    border-radius: 16px;\n  }\n\n  #watercons-wrap .hdr,\n  #watercons-wrap .cc {\n    padding: 19px;\n  }\n\n  #watercons-wrap .stats {\n    grid-template-columns: 1fr;\n  }\n\n  #watercons-wrap .tab-bar {\n    justify-content: flex-start;\n  }\n\n  #watercons-wrap .tbtn {\n    flex: 0 0 auto;\n  }\n\n  #watercons-wrap .chart-box,\n  #watercons-wrap .chart-box-sm {\n    height: 310px;\n  }\n}\n<\/style>\n\n\n<div id=\"watercons-wrap\">\n  <div class=\"dash\">\n\n    <!-- HEADER -->\n    <div class=\"hdr\">\n      <div class=\"eyebrow\">\n        Statistik Penggunaan Air Melaka\n      <\/div>\n\n      <h2>\n        Papan Pemuka Penggunaan Air Mengikut Negeri dan Sektor\n      <\/h2>\n\n      <p class=\"hdesc\">\n        Visualisasi penggunaan air tahunan di Melaka mengikut sektor\n        domestik dan bukan domestik. Terokai trend penggunaan,\n        komposisi sektor, perubahan tahunan, perbandingan daerah\n        tidak disediakan dalam dataset ini, serta perbandingan\n        Melaka dengan negeri-negeri lain.\n        Semua nilai penggunaan air dinyatakan dalam juta liter\n        sehari (MLD).\n      <\/p>\n    <\/div>\n\n\n    <!-- FILTER -->\n    <div class=\"filter-bar\">\n\n      <div class=\"cg\">\n        <label for=\"watercons-year\">Tahun<\/label>\n        <select id=\"watercons-year\"><\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label for=\"watercons-sector\">Sektor<\/label>\n\n        <select id=\"watercons-sector\">\n          <option value=\"both\">Kedua-dua Sektor<\/option>\n          <option value=\"domestic\">Domestik<\/option>\n          <option value=\"nondomestic\">Bukan Domestik<\/option>\n        <\/select>\n      <\/div>\n\n      <div class=\"cg\">\n        <label for=\"watercons-compare-state\">\n          Negeri Perbandingan\n        <\/label>\n        <select id=\"watercons-compare-state\"><\/select>\n      <\/div>\n\n    <\/div>\n\n\n    <!-- SUMMARY -->\n    <div\n      class=\"stats\"\n      id=\"watercons-stats\"\n      style=\"display:none\"\n    >\n\n      <div class=\"sc\">\n        <div class=\"sl\">Tahun Dipilih<\/div>\n        <div class=\"sv\" id=\"watercons-stat-year\">\u2014<\/div>\n        <div class=\"su\" id=\"watercons-stat-sector\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Penggunaan Air Melaka<\/div>\n        <div class=\"sv\" id=\"watercons-stat-total\">\u2014<\/div>\n        <div class=\"su\">juta liter sehari (MLD)<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Perubahan Tahunan<\/div>\n        <div class=\"sv\" id=\"watercons-stat-change\">\u2014<\/div>\n        <div class=\"su\" id=\"watercons-stat-change-sub\">\u2014<\/div>\n      <\/div>\n\n      <div class=\"sc\">\n        <div class=\"sl\">Kedudukan Melaka<\/div>\n        <div class=\"sv\" id=\"watercons-stat-rank\">\u2014<\/div>\n        <div class=\"su\" id=\"watercons-stat-rank-sub\">\u2014<\/div>\n      <\/div>\n\n    <\/div>\n\n\n    <!-- LOADING -->\n    <div class=\"sbox\" id=\"watercons-loading\" role=\"status\">\n      <div class=\"spin\"><\/div>\n\n      <strong>Memuatkan data&#8230;<\/strong>\n\n      <div>\n        Sedang mendapatkan rekod penggunaan air Melaka.\n      <\/div>\n    <\/div>\n\n\n    <!-- DASHBOARD -->\n    <div id=\"watercons-area\" style=\"display:none\">\n\n      <!-- TABS -->\n      <div class=\"tab-bar\" role=\"tablist\" aria-label=\"Paparan carta\">\n\n        <button class=\"tbtn active\" data-tab=\"trend\"\n          type=\"button\" role=\"tab\" aria-selected=\"true\">\n          Trend\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"sector\"\n          type=\"button\" role=\"tab\" aria-selected=\"false\">\n          Sektor\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"change\"\n          type=\"button\" role=\"tab\" aria-selected=\"false\">\n          Perubahan\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"state\"\n          type=\"button\" role=\"tab\" aria-selected=\"false\">\n          Negeri\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"ranking\"\n          type=\"button\" role=\"tab\" aria-selected=\"false\">\n          Kedudukan\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"relative\"\n          type=\"button\" role=\"tab\" aria-selected=\"false\">\n          Relatif\n        <\/button>\n\n        <button class=\"tbtn\" data-tab=\"comparison\"\n          type=\"button\" role=\"tab\" aria-selected=\"false\">\n          Perbandingan\n        <\/button>\n\n      <\/div>\n\n\n      <!-- TREND -->\n      <div id=\"watercons-tab-trend\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Trend Penggunaan Air Melaka\n            <\/div>\n\n            <div class=\"cs\" id=\"watercons-trend-sub\">\n              Penggunaan air mengikut tahun.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-trend\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Indeks Penggunaan Air\n            <\/div>\n\n            <div class=\"cs\" id=\"watercons-index-sub\">\n              Tahun asas ditetapkan sebagai indeks 100.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-index\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">\n            Trend Penggunaan Air Mengikut Sektor\n          <\/div>\n\n          <div class=\"cs\">\n            Perbandingan sektor domestik dan bukan domestik\n            di Melaka sepanjang tempoh data yang tersedia.\n            Carta ini sentiasa memaparkan kedua-dua sektor.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"watercons-chart-sector-trend\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n\n      <!-- SECTOR -->\n      <div id=\"watercons-tab-sector\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Komposisi Penggunaan Air Mengikut Sektor\n            <\/div>\n\n            <div class=\"cs\" id=\"watercons-share-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-share\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Perbandingan Penggunaan Air Mengikut Sektor\n            <\/div>\n\n            <div class=\"cs\" id=\"watercons-sector-bar-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-sector-bar\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">\n            Trend Peratus Sumbangan Sektor\n          <\/div>\n\n          <div class=\"cs\">\n            Peratus penggunaan sektor domestik dan bukan domestik\n            daripada jumlah penggunaan air Melaka mengikut tahun.\n            Hanya tahun yang mempunyai rekod kedua-dua sektor digunakan.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"watercons-chart-share-trend\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n\n      <!-- CHANGE -->\n      <div id=\"watercons-tab-change\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Perubahan Penggunaan Air Tahunan\n            <\/div>\n\n            <div class=\"cs\">\n              Perbezaan penggunaan air Melaka dalam MLD\n              berbanding tahun terdahulu yang tersedia.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-change-value\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Kadar Perubahan Tahunan\n            <\/div>\n\n            <div class=\"cs\">\n              Perubahan penggunaan air dalam peratus\n              berbanding tahun terdahulu yang tersedia.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-change-pct\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">\n            Perubahan Tahunan Mengikut Sektor\n          <\/div>\n\n          <div class=\"cs\">\n            Perubahan penggunaan air domestik dan bukan domestik\n            berbanding tahun terdahulu yang mempunyai rekod\n            bagi sektor masing-masing.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"watercons-chart-sector-change\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n\n      <!-- STATE -->\n      <div id=\"watercons-tab-state\" style=\"display:none\">\n\n        <div class=\"cc\">\n          <div class=\"ct\">\n            Perbandingan Penggunaan Air Mengikut Negeri\n          <\/div>\n\n          <div class=\"cs\" id=\"watercons-state-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"watercons-chart-state\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">\n            Penggunaan Air Negeri Berbanding Purata\n          <\/div>\n\n          <div class=\"cs\" id=\"watercons-state-average-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"watercons-chart-state-average\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n\n      <!-- RANKING -->\n      <div id=\"watercons-tab-ranking\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Negeri dengan Penggunaan Air Tertinggi\n            <\/div>\n\n            <div class=\"cs\" id=\"watercons-high-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-high\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Negeri dengan Penggunaan Air Terendah\n            <\/div>\n\n            <div class=\"cs\" id=\"watercons-low-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-low\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- RELATIVE -->\n      <div id=\"watercons-tab-relative\" style=\"display:none\">\n\n        <div class=\"two\">\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Melaka Berbanding Purata Negeri\n            <\/div>\n\n            <div class=\"cs\" id=\"watercons-relative-sub\"><\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-melaka-average\"><\/canvas>\n            <\/div>\n          <\/div>\n\n          <div class=\"cc\">\n            <div class=\"ct\">\n              Perbandingan dengan Negeri Tertinggi\n            <\/div>\n\n            <div class=\"cs\">\n              Negeri dengan penggunaan air tertinggi ditetapkan\n              sebagai 100%. Melaka ditonjolkan dalam warna oren.\n            <\/div>\n\n            <div class=\"chart-box-sm\">\n              <canvas id=\"watercons-chart-relative\"><\/canvas>\n            <\/div>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <!-- COMPARISON -->\n      <div id=\"watercons-tab-comparison\" style=\"display:none\">\n\n        <div class=\"cc\">\n          <div class=\"ct\">\n            Trend Melaka berbanding Negeri Pilihan\n          <\/div>\n\n          <div class=\"cs\" id=\"watercons-comparison-sub\"><\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"watercons-chart-comparison\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"cc\">\n          <div class=\"ct\">\n            Perbezaan Penggunaan Air dengan Negeri Pilihan\n          <\/div>\n\n          <div class=\"cs\">\n            Nilai positif menunjukkan penggunaan Melaka lebih tinggi.\n            Nilai negatif menunjukkan negeri perbandingan mempunyai\n            penggunaan air yang lebih tinggi.\n          <\/div>\n\n          <div class=\"chart-box\">\n            <canvas id=\"watercons-chart-gap\"><\/canvas>\n          <\/div>\n        <\/div>\n\n      <\/div>\n\n\n      <!-- FOOTNOTE -->\n      <div class=\"note\">\n        <strong>Nota:<\/strong>\n        MLD bermaksud juta liter sehari.\n        Jumlah kedua-dua sektor hanya dikira apabila rekod domestik\n        dan bukan domestik tersedia. Rekod agregat Malaysia\n        dikecualikan daripada perbandingan negeri.\n        Perubahan dikira menggunakan tahun terdahulu yang tersedia,\n        yang tidak semestinya tahun kalendar berturutan.\n        Kedudukan menunjukkan susunan penggunaan air, bukan\n        penilaian kecekapan penggunaan air.\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n  \"use strict\";\n\n  var ROOT_ID = \"watercons-wrap\";\n\n  \/*\n    Avoid duplicate initialization when WordPress\n    or a page builder executes the same script again.\n  *\/\n\n  var root = document.getElementById(ROOT_ID);\n\n  if(!root || root.dataset.initialized === \"true\"){\n    return;\n  }\n\n  root.dataset.initialized = \"true\";\n\n\n  function init(){\n\n    if(typeof Chart === \"undefined\"){\n      setTimeout(init,500);\n      return;\n    }\n\n\n    \/* =====================================================\n       CONFIGURATION\n    ====================================================== *\/\n\n    var API_URL =\n      \"https:\/\/api.data.gov.my\/data-catalogue\/?id=water_consumption&limit=1000\";\n\n    var FIXED_STATE = \"Melaka\";\n\n    var AGGREGATE_STATE = \"Malaysia\";\n\n    var COLORS = {\n      blue: \"#2563eb\",\n      orange: \"#f97316\",\n      purple: \"#7c3aed\",\n      cyan: \"#0891b2\",\n      green: \"#16a34a\",\n      red: \"#dc2626\",\n      grid: \"#dbeafe\",\n      muted: \"#64748b\",\n      dark: \"#0f172a\"\n    };\n\n\n    var allRows = [];\n\n    var melakaRows = [];\n\n    var charts = {};\n\n\n    Chart.defaults.color = COLORS.muted;\n\n    Chart.defaults.borderColor = COLORS.grid;\n\n    Chart.defaults.font.family = \"Arial, sans-serif\";\n\n\n    \/* =====================================================\n       ELEMENT HELPERS\n    ====================================================== *\/\n\n    function el(id){\n      return root.querySelector(\"#\" + id);\n    }\n\n\n    function setText(id,value){\n      var element = el(id);\n\n      if(element){\n        element.textContent = value;\n      }\n    }\n\n\n    function show(id){\n      el(id).style.display = \"\";\n    }\n\n\n    function hide(id){\n      el(id).style.display = \"none\";\n    }\n\n\n    \/* =====================================================\n       NUMBER HELPERS\n    ====================================================== *\/\n\n    function num(value){\n\n      if(\n        value === null ||\n        value === undefined ||\n        value === \"\"\n      ){\n        return null;\n      }\n\n      var n = Number(value);\n\n      return Number.isFinite(n) ? n : null;\n    }\n\n\n    function valid(value){\n\n      return (\n        typeof value === \"number\" &&\n        Number.isFinite(value)\n      );\n    }\n\n\n    function fmt(value,digits){\n\n      if(!valid(value)){\n        return \"\u2014\";\n      }\n\n      var d = digits === undefined ? 1 : digits;\n\n      return value.toLocaleString(\"ms-MY\",{\n        minimumFractionDigits: d,\n        maximumFractionDigits: d\n      });\n    }\n\n\n    function signed(value,digits){\n\n      if(!valid(value)){\n        return \"\u2014\";\n      }\n\n      return (\n        (value > 0 ? \"+\" : \"\") +\n        fmt(value,digits === undefined ? 1 : digits)\n      );\n    }\n\n\n    function signedPct(value){\n\n      if(!valid(value)){\n        return \"\u2014\";\n      }\n\n      return (\n        (value > 0 ? \"+\" : \"\") +\n        fmt(value,2) +\n        \"%\"\n      );\n    }\n\n\n    function pct(value){\n\n      return valid(value)\n        ? fmt(value,2) + \"%\"\n        : \"\u2014\";\n    }\n\n\n    function percentChange(current,previous){\n\n      if(\n        !valid(current) ||\n        !valid(previous) ||\n        previous === 0\n      ){\n        return null;\n      }\n\n      return ((current - previous) \/ previous) * 100;\n    }\n\n\n    function sum(values){\n\n      return values.reduce(function(total,value){\n        return total + value;\n      },0);\n    }\n\n\n    function average(values){\n\n      var clean = values.filter(valid);\n\n      return clean.length\n        ? sum(clean) \/ clean.length\n        : null;\n    }\n\n\n    \/* =====================================================\n       DATA NORMALIZATION\n    ====================================================== *\/\n\n    function yearOf(row){\n\n      var year = parseInt(\n        String(row.date || \"\").slice(0,4),\n        10\n      );\n\n      return Number.isFinite(year) ? year : null;\n    }\n\n\n    function normalizeState(value){\n\n      return String(value || \"\")\n        .trim()\n        .toLowerCase();\n    }\n\n\n    function normalizeSector(value){\n\n      var sector = String(value || \"\")\n        .trim()\n        .toLowerCase()\n        .replace(\/[\\s_-]\/g,\"\");\n\n      if(sector === \"domestic\"){\n        return \"domestic\";\n      }\n\n      if(sector === \"nondomestic\"){\n        return \"nondomestic\";\n      }\n\n      return null;\n    }\n\n\n    function sectorLabel(sector){\n\n      if(sector === \"domestic\"){\n        return \"Domestik\";\n      }\n\n      if(sector === \"nondomestic\"){\n        return \"Bukan Domestik\";\n      }\n\n      return \"Kedua-dua Sektor\";\n    }\n\n\n    \/*\n      One state-year-sector should have one value.\n\n      If identical rows are duplicated, use their mean\n      rather than summing repeated MLD values.\n    *\/\n\n    function sectorValue(state,year,sector){\n\n      var values = allRows\n        .filter(function(row){\n\n          return (\n            row.stateKey === normalizeState(state) &&\n            row.year === year &&\n            row.sector === sector\n          );\n\n        })\n        .map(function(row){\n          return row.value;\n        })\n        .filter(valid);\n\n      return average(values);\n    }\n\n\n    \/*\n      Total is calculated only when BOTH sectors exist.\n\n      This prevents a missing sector from being treated\n      as zero and producing a misleading total.\n    *\/\n\n    function stateValue(state,year,sector){\n\n      if(sector !== \"both\"){\n        return sectorValue(state,year,sector);\n      }\n\n      var domestic = sectorValue(\n        state,year,\"domestic\"\n      );\n\n      var nondomestic = sectorValue(\n        state,year,\"nondomestic\"\n      );\n\n      if(!valid(domestic) || !valid(nondomestic)){\n        return null;\n      }\n\n      return domestic + nondomestic;\n    }\n\n\n    function melakaValue(year,sector){\n\n      return stateValue(\n        FIXED_STATE,\n        year,\n        sector\n      );\n    }\n\n\n    function availableYears(sector){\n\n      var years = Array.from(\n        new Set(\n          melakaRows.map(function(row){\n            return row.year;\n          })\n        )\n      );\n\n      return years\n        .filter(function(year){\n\n          return (\n            valid(year) &&\n            valid(melakaValue(year,sector))\n          );\n\n        })\n        .sort(function(a,b){\n          return a-b;\n        });\n    }\n\n\n    function allMelakaYears(){\n\n      return Array.from(\n        new Set(\n          melakaRows.map(function(row){\n            return row.year;\n          })\n        )\n      )\n      .filter(valid)\n      .sort(function(a,b){\n        return a-b;\n      });\n    }\n\n\n    function previousYear(year,sector){\n\n      var years = availableYears(sector)\n        .filter(function(item){\n          return item < year;\n        });\n\n      return years.length\n        ? years[years.length - 1]\n        : null;\n    }\n\n\n    function getStates(){\n\n      return Array.from(\n        new Set(\n          allRows\n            .filter(function(row){\n\n              return (\n                row.stateKey !==\n                normalizeState(AGGREGATE_STATE)\n              );\n\n            })\n            .map(function(row){\n              return row.state;\n            })\n        )\n      )\n      .sort(function(a,b){\n        return a.localeCompare(b);\n      });\n    }\n\n\n    function getComparisonStates(){\n\n      return getStates().filter(function(state){\n\n        return (\n          normalizeState(state) !==\n          normalizeState(FIXED_STATE)\n        );\n\n      });\n    }\n\n\n    function stateDataForYear(year,sector){\n\n      return getStates()\n        .map(function(state){\n\n          return {\n            state: state,\n            value: stateValue(state,year,sector)\n          };\n\n        })\n        .filter(function(item){\n          return valid(item.value);\n        });\n    }\n\n\n    function stateAverage(year,sector){\n\n      return average(\n        stateDataForYear(year,sector)\n          .map(function(item){\n            return item.value;\n          })\n      );\n    }\n\n\n    function getSelected(){\n\n      return {\n        year: parseInt(el(\"watercons-year\").value,10),\n        sector: el(\"watercons-sector\").value,\n        compareState: el(\"watercons-compare-state\").value\n      };\n    }\n\n\n    \/* =====================================================\n       FILTERS\n    ====================================================== *\/\n\n    function populateFilters(){\n\n      var sector = el(\"watercons-sector\").value;\n\n      var years = availableYears(sector)\n        .slice()\n        .reverse();\n\n      var yearSelect = el(\"watercons-year\");\n\n      var previousSelection = parseInt(\n        yearSelect.value,\n        10\n      );\n\n      yearSelect.innerHTML = \"\";\n\n      years.forEach(function(year){\n\n        var option = document.createElement(\"option\");\n\n        option.value = year;\n\n        option.textContent = year;\n\n        yearSelect.appendChild(option);\n\n      });\n\n\n      if(years.includes(previousSelection)){\n\n        yearSelect.value = String(previousSelection);\n\n      }else if(years.length){\n\n        yearSelect.value = String(years[0]);\n      }\n\n\n      var compareSelect =\n        el(\"watercons-compare-state\");\n\n      var states = getComparisonStates();\n\n      compareSelect.innerHTML = \"\";\n\n      states.forEach(function(state){\n\n        var option = document.createElement(\"option\");\n\n        option.value = state;\n\n        option.textContent = state;\n\n        compareSelect.appendChild(option);\n\n      });\n\n\n      if(states.includes(\"Negeri Sembilan\")){\n\n        compareSelect.value = \"Negeri Sembilan\";\n\n      }else if(states.length){\n\n        compareSelect.value = states[0];\n      }\n    }\n\n\n    function updateYearFilter(){\n\n      var sector = el(\"watercons-sector\").value;\n\n      var yearSelect = el(\"watercons-year\");\n\n      var currentYear = parseInt(yearSelect.value,10);\n\n      var years = availableYears(sector)\n        .slice()\n        .reverse();\n\n      yearSelect.innerHTML = \"\";\n\n      years.forEach(function(year){\n\n        var option = document.createElement(\"option\");\n\n        option.value = year;\n\n        option.textContent = year;\n\n        yearSelect.appendChild(option);\n\n      });\n\n\n      if(years.includes(currentYear)){\n\n        yearSelect.value = String(currentYear);\n\n      }else if(years.length){\n\n        yearSelect.value = String(years[0]);\n      }\n    }\n\n\n    \/* =====================================================\n       CHART HELPERS\n    ====================================================== *\/\n\n    function destroyChart(name){\n\n      if(charts[name]){\n\n        charts[name].destroy();\n\n        delete charts[name];\n      }\n    }\n\n\n    function makeChart(name,canvasId,config){\n\n      destroyChart(name);\n\n      var canvas = el(canvasId);\n\n      if(!canvas){\n        return;\n      }\n\n      charts[name] = new Chart(\n        canvas.getContext(\"2d\"),\n        config\n      );\n    }\n\n\n    function baseOptions(){\n\n      return {\n        responsive: true,\n        maintainAspectRatio: false,\n        interaction: {\n          mode: \"nearest\",\n          intersect: false\n        },\n        plugins: {\n          legend: {\n            position: \"bottom\",\n            labels: {\n              usePointStyle: true,\n              padding: 18\n            }\n          },\n          tooltip: {\n            backgroundColor: COLORS.dark\n          }\n        }\n      };\n    }\n\n\n    function valueTooltip(horizontal){\n\n      return {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            var value = horizontal\n              ? context.parsed.x\n              : context.parsed.y;\n\n            return (\n              \" \" +\n              context.dataset.label +\n              \": \" +\n              fmt(value,1) +\n              \" MLD\"\n            );\n          }\n        }\n      };\n    }\n\n\n    function percentTooltip(){\n\n      return {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            return (\n              \" \" +\n              context.dataset.label +\n              \": \" +\n              fmt(context.parsed.y,2) +\n              \"%\"\n            );\n          }\n        }\n      };\n    }\n\n\n    function xScale(){\n\n      return {\n        grid: {\n          display: false\n        },\n        ticks: {\n          maxRotation: 45,\n          autoSkip: true\n        }\n      };\n    }\n\n\n    function yScale(unit){\n\n      return {\n        beginAtZero: true,\n        grid: {\n          color: COLORS.grid\n        },\n        ticks: {\n          callback: function(value){\n\n            return (\n              fmt(value,0) +\n              (unit === \"%\" ? \"%\" : \"\")\n            );\n          }\n        }\n      };\n    }\n\n\n    function lineDataset(label,data,color,fill){\n\n      return {\n        label: label,\n        data: data,\n        borderColor: color,\n        backgroundColor: color + \"20\",\n        fill: !!fill,\n        tension: .3,\n        borderWidth: 2.5,\n        pointRadius: 3,\n        pointHoverRadius: 6,\n        spanGaps: false\n      };\n    }\n\n\n    function stateColors(data){\n\n      return data.map(function(item){\n\n        return (\n          normalizeState(item.state) ===\n          normalizeState(FIXED_STATE)\n        )\n          ? COLORS.orange\n          : \"rgba(37,99,235,.75)\";\n      });\n    }\n\n\n    function stateTickColor(context){\n\n      return (\n        context.tick &#038;&#038;\n        context.tick.label === FIXED_STATE\n      )\n        ? COLORS.orange\n        : COLORS.muted;\n    }\n\n\n    function stateTickFont(context){\n\n      return {\n        weight: (\n          context.tick &#038;&#038;\n          context.tick.label === FIXED_STATE\n        )\n          ? \"bold\"\n          : \"normal\"\n      };\n    }\n\n\n    \/* =====================================================\n       SUMMARY CARDS\n    ====================================================== *\/\n\n    function updateCards(){\n\n      var s = getSelected();\n\n      var current = melakaValue(\n        s.year,\n        s.sector\n      );\n\n      var prevYear = previousYear(\n        s.year,\n        s.sector\n      );\n\n      var previous = prevYear !== null\n        ? melakaValue(prevYear,s.sector)\n        : null;\n\n      var change = percentChange(\n        current,\n        previous\n      );\n\n\n      var ranking = stateDataForYear(\n        s.year,\n        s.sector\n      )\n      .sort(function(a,b){\n        return b.value - a.value;\n      });\n\n\n      var rankIndex = ranking.findIndex(function(item){\n\n        return (\n          normalizeState(item.state) ===\n          normalizeState(FIXED_STATE)\n        );\n\n      });\n\n\n      setText(\n        \"watercons-stat-year\",\n        valid(s.year) ? s.year : \"\u2014\"\n      );\n\n      setText(\n        \"watercons-stat-sector\",\n        sectorLabel(s.sector)\n      );\n\n      setText(\n        \"watercons-stat-total\",\n        fmt(current,1)\n      );\n\n      setText(\n        \"watercons-stat-change\",\n        signedPct(change)\n      );\n\n      setText(\n        \"watercons-stat-change-sub\",\n        prevYear !== null\n          ? \"berbanding \" + prevYear\n          : \"tiada tahun terdahulu\"\n      );\n\n      setText(\n        \"watercons-stat-rank\",\n        rankIndex >= 0\n          ? \"#\" + (rankIndex + 1)\n          : \"\u2014\"\n      );\n\n      setText(\n        \"watercons-stat-rank-sub\",\n        rankIndex >= 0\n          ? \"daripada \" + ranking.length + \" negeri\"\n          : \"tiada data kedudukan\"\n      );\n\n\n      show(\"watercons-stats\");\n    }\n\n\n    \/* =====================================================\n       TREND CHARTS\n    ====================================================== *\/\n\n    function buildTrendCharts(){\n\n      var s = getSelected();\n\n      var years = availableYears(s.sector);\n\n      var values = years.map(function(year){\n\n        return melakaValue(year,s.sector);\n\n      });\n\n\n      setText(\n        \"watercons-trend-sub\",\n        sectorLabel(s.sector) +\n        \" \u00b7 semua tahun yang tersedia \u00b7 MLD\"\n      );\n\n\n      var options = baseOptions();\n\n      options.plugins.legend.display = false;\n\n      options.plugins.tooltip = valueTooltip(false);\n\n      options.scales = {\n        x: xScale(),\n        y: yScale()\n      };\n\n\n      makeChart(\n        \"trend\",\n        \"watercons-chart-trend\",\n        {\n          type: \"line\",\n          data: {\n            labels: years,\n            datasets: [\n              lineDataset(\n                sectorLabel(s.sector),\n                values,\n                COLORS.blue,\n                true\n              )\n            ]\n          },\n          options: options\n        }\n      );\n\n\n      \/* INDEX *\/\n\n      var baseIndex = values.find(function(value){\n\n        return valid(value) && value !== 0;\n\n      });\n\n\n      var baseYear = null;\n\n      if(baseIndex !== undefined){\n\n        var basePosition = values.indexOf(baseIndex);\n\n        baseYear = years[basePosition];\n      }\n\n\n      var indexValues = values.map(function(value){\n\n        if(\n          !valid(value) ||\n          !valid(baseIndex) ||\n          baseIndex === 0\n        ){\n          return null;\n        }\n\n        return (value \/ baseIndex) * 100;\n\n      });\n\n\n      setText(\n        \"watercons-index-sub\",\n        baseYear !== null\n          ? \"Tahun asas \" + baseYear + \" = 100\"\n          : \"Tiada tahun asas yang sah\"\n      );\n\n\n      var indexOptions = baseOptions();\n\n      indexOptions.plugins.legend.display = false;\n\n      indexOptions.plugins.tooltip = {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            return (\n              \" Indeks: \" +\n              fmt(context.parsed.y,2)\n            );\n          }\n        }\n      };\n\n      indexOptions.scales = {\n        x: xScale(),\n        y: {\n          grid: {\n            color: COLORS.grid\n          },\n          ticks: {\n            callback: function(value){\n              return fmt(value,0);\n            }\n          }\n        }\n      };\n\n\n      makeChart(\n        \"index\",\n        \"watercons-chart-index\",\n        {\n          type: \"line\",\n          data: {\n            labels: years,\n            datasets: [\n              lineDataset(\n                \"Indeks Penggunaan\",\n                indexValues,\n                COLORS.purple,\n                true\n              )\n            ]\n          },\n          options: indexOptions\n        }\n      );\n\n\n      \/* SECTOR TREND *\/\n\n      var sectorYears = allMelakaYears();\n\n      var domesticValues = sectorYears.map(function(year){\n\n        return melakaValue(year,\"domestic\");\n\n      });\n\n      var nondomesticValues = sectorYears.map(function(year){\n\n        return melakaValue(year,\"nondomestic\");\n\n      });\n\n\n      var sectorOptions = baseOptions();\n\n      sectorOptions.plugins.tooltip = valueTooltip(false);\n\n      sectorOptions.scales = {\n        x: xScale(),\n        y: yScale()\n      };\n\n\n      makeChart(\n        \"sectorTrend\",\n        \"watercons-chart-sector-trend\",\n        {\n          type: \"line\",\n          data: {\n            labels: sectorYears,\n            datasets: [\n              lineDataset(\n                \"Domestik\",\n                domesticValues,\n                COLORS.blue,\n                false\n              ),\n              lineDataset(\n                \"Bukan Domestik\",\n                nondomesticValues,\n                COLORS.orange,\n                false\n              )\n            ]\n          },\n          options: sectorOptions\n        }\n      );\n\n    }\n\n\n    \/* =====================================================\n       SECTOR CHARTS\n    ====================================================== *\/\n\n    function buildSectorCharts(){\n\n      var s = getSelected();\n\n      var domestic = melakaValue(\n        s.year,\n        \"domestic\"\n      );\n\n      var nondomestic = melakaValue(\n        s.year,\n        \"nondomestic\"\n      );\n\n      var total = melakaValue(\n        s.year,\n        \"both\"\n      );\n\n\n      setText(\n        \"watercons-share-sub\",\n        s.year +\n        \" \u00b7 pecahan penggunaan air Melaka \u00b7 MLD\"\n      );\n\n      setText(\n        \"watercons-sector-bar-sub\",\n        s.year +\n        \" \u00b7 domestik berbanding bukan domestik\"\n      );\n\n\n      \/* DOUGHNUT *\/\n\n      var shareOptions = baseOptions();\n\n      shareOptions.plugins.tooltip = {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            var value = context.parsed;\n\n            var percentage = (\n              valid(total) &&\n              total !== 0\n            )\n              ? (value \/ total) * 100\n              : null;\n\n            return (\n              \" \" +\n              context.label +\n              \": \" +\n              fmt(value,1) +\n              \" MLD (\" +\n              pct(percentage) +\n              \")\"\n            );\n          }\n        }\n      };\n\n\n      makeChart(\n        \"share\",\n        \"watercons-chart-share\",\n        {\n          type: \"doughnut\",\n          data: {\n            labels: [\n              \"Domestik\",\n              \"Bukan Domestik\"\n            ],\n            datasets: [{\n              data: [\n                domestic,\n                nondomestic\n              ],\n              backgroundColor: [\n                COLORS.blue,\n                COLORS.orange\n              ],\n              borderColor: \"#ffffff\",\n              borderWidth: 3,\n              hoverOffset: 8\n            }]\n          },\n          options: shareOptions\n        }\n      );\n\n\n      \/* SECTOR BAR *\/\n\n      var barOptions = baseOptions();\n\n      barOptions.plugins.legend.display = false;\n\n      barOptions.plugins.tooltip = valueTooltip(false);\n\n      barOptions.scales = {\n        x: xScale(),\n        y: yScale()\n      };\n\n\n      makeChart(\n        \"sectorBar\",\n        \"watercons-chart-sector-bar\",\n        {\n          type: \"bar\",\n          data: {\n            labels: [\n              \"Domestik\",\n              \"Bukan Domestik\"\n            ],\n            datasets: [{\n              label: \"Penggunaan Air\",\n              data: [\n                domestic,\n                nondomestic\n              ],\n              backgroundColor: [\n                COLORS.blue,\n                COLORS.orange\n              ],\n              borderRadius: 8\n            }]\n          },\n          options: barOptions\n        }\n      );\n\n\n      \/* SHARE TREND *\/\n\n      var years = availableYears(\"both\");\n\n      var domesticShare = [];\n\n      var nondomesticShare = [];\n\n\n      years.forEach(function(year){\n\n        var d = melakaValue(year,\"domestic\");\n\n        var n = melakaValue(year,\"nondomestic\");\n\n        var t = melakaValue(year,\"both\");\n\n\n        if(!valid(t) || t === 0){\n\n          domesticShare.push(null);\n\n          nondomesticShare.push(null);\n\n          return;\n        }\n\n\n        domesticShare.push(\n          (d \/ t) * 100\n        );\n\n        nondomesticShare.push(\n          (n \/ t) * 100\n        );\n\n      });\n\n\n      var trendOptions = baseOptions();\n\n      trendOptions.plugins.tooltip = percentTooltip();\n\n      trendOptions.scales = {\n        x: xScale(),\n        y: {\n          min: 0,\n          max: 100,\n          grid: {\n            color: COLORS.grid\n          },\n          ticks: {\n            callback: function(value){\n              return value + \"%\";\n            }\n          }\n        }\n      };\n\n\n      makeChart(\n        \"shareTrend\",\n        \"watercons-chart-share-trend\",\n        {\n          type: \"line\",\n          data: {\n            labels: years,\n            datasets: [\n              lineDataset(\n                \"Domestik\",\n                domesticShare,\n                COLORS.blue,\n                false\n              ),\n              lineDataset(\n                \"Bukan Domestik\",\n                nondomesticShare,\n                COLORS.orange,\n                false\n              )\n            ]\n          },\n          options: trendOptions\n        }\n      );\n\n    }\n\n\n    \/* =====================================================\n       CHANGE CHARTS\n    ====================================================== *\/\n\n    function buildChangeCharts(){\n\n      var s = getSelected();\n\n      var years = availableYears(s.sector);\n\n      var labels = [];\n\n      var changes = [];\n\n      var percentages = [];\n\n\n      for(var i = 1; i < years.length; i++){\n\n        var currentYear = years[i];\n\n        var prevYear = years[i - 1];\n\n        var current = melakaValue(\n          currentYear,\n          s.sector\n        );\n\n        var previous = melakaValue(\n          prevYear,\n          s.sector\n        );\n\n\n        labels.push(\n          currentYear + \" vs \" + prevYear\n        );\n\n\n        changes.push(\n          valid(current) &#038;&#038; valid(previous)\n            ? current - previous\n            : null\n        );\n\n\n        percentages.push(\n          percentChange(current,previous)\n        );\n\n      }\n\n\n      \/* ABSOLUTE CHANGE *\/\n\n      var valueOptions = baseOptions();\n\n      valueOptions.plugins.legend.display = false;\n\n      valueOptions.plugins.tooltip = {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            return (\n              \" Perubahan: \" +\n              signed(context.parsed.y,1) +\n              \" MLD\"\n            );\n          }\n        }\n      };\n\n      valueOptions.scales = {\n        x: xScale(),\n        y: {\n          grid: {\n            color: COLORS.grid\n          }\n        }\n      };\n\n\n      makeChart(\n        \"changeValue\",\n        \"watercons-chart-change-value\",\n        {\n          type: \"bar\",\n          data: {\n            labels: labels,\n            datasets: [{\n              label: \"Perubahan Penggunaan\",\n              data: changes,\n              backgroundColor: changes.map(function(value){\n\n                return valid(value) &#038;&#038; value >= 0\n                  ? COLORS.green\n                  : COLORS.red;\n\n              }),\n              borderRadius: 7\n            }]\n          },\n          options: valueOptions\n        }\n      );\n\n\n      \/* PERCENTAGE CHANGE *\/\n\n      var pctOptions = baseOptions();\n\n      pctOptions.plugins.legend.display = false;\n\n      pctOptions.plugins.tooltip = {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            return (\n              \" Perubahan: \" +\n              signedPct(context.parsed.y)\n            );\n          }\n        }\n      };\n\n      pctOptions.scales = {\n        x: xScale(),\n        y: {\n          grid: {\n            color: COLORS.grid\n          },\n          ticks: {\n            callback: function(value){\n              return fmt(value,1) + \"%\";\n            }\n          }\n        }\n      };\n\n\n      makeChart(\n        \"changePct\",\n        \"watercons-chart-change-pct\",\n        {\n          type: \"line\",\n          data: {\n            labels: labels,\n            datasets: [\n              lineDataset(\n                \"Perubahan Tahunan\",\n                percentages,\n                COLORS.purple,\n                true\n              )\n            ]\n          },\n          options: pctOptions\n        }\n      );\n\n\n      \/* CHANGE BY SECTOR *\/\n\n      var sectorYears = allMelakaYears();\n\n      var sectorLabels = [];\n\n      var domesticChanges = [];\n\n      var nondomesticChanges = [];\n\n\n      [\"domestic\",\"nondomestic\"].forEach(function(sector){\n\n        \/*\n          Each sector is compared against its own\n          latest earlier available year.\n        *\/\n\n      });\n\n\n      sectorYears.forEach(function(year){\n\n        var domesticPrev = previousYear(\n          year,\n          \"domestic\"\n        );\n\n        var nondomesticPrev = previousYear(\n          year,\n          \"nondomestic\"\n        );\n\n\n        var dCurrent = melakaValue(\n          year,\n          \"domestic\"\n        );\n\n        var dPrevious = domesticPrev !== null\n          ? melakaValue(domesticPrev,\"domestic\")\n          : null;\n\n\n        var nCurrent = melakaValue(\n          year,\n          \"nondomestic\"\n        );\n\n        var nPrevious = nondomesticPrev !== null\n          ? melakaValue(nondomesticPrev,\"nondomestic\")\n          : null;\n\n\n        if(\n          domesticPrev === null &&\n          nondomesticPrev === null\n        ){\n          return;\n        }\n\n\n        sectorLabels.push(year);\n\n\n        domesticChanges.push(\n          valid(dCurrent) && valid(dPrevious)\n            ? dCurrent - dPrevious\n            : null\n        );\n\n\n        nondomesticChanges.push(\n          valid(nCurrent) && valid(nPrevious)\n            ? nCurrent - nPrevious\n            : null\n        );\n\n      });\n\n\n      var sectorOptions = baseOptions();\n\n      sectorOptions.plugins.tooltip = {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            return (\n              \" \" +\n              context.dataset.label +\n              \": \" +\n              signed(context.parsed.y,1) +\n              \" MLD\"\n            );\n          }\n        }\n      };\n\n      sectorOptions.scales = {\n        x: xScale(),\n        y: {\n          grid: {\n            color: COLORS.grid\n          }\n        }\n      };\n\n\n      makeChart(\n        \"sectorChange\",\n        \"watercons-chart-sector-change\",\n        {\n          type: \"bar\",\n          data: {\n            labels: sectorLabels,\n            datasets: [\n              {\n                label: \"Domestik\",\n                data: domesticChanges,\n                backgroundColor: COLORS.blue,\n                borderRadius: 5\n              },\n              {\n                label: \"Bukan Domestik\",\n                data: nondomesticChanges,\n                backgroundColor: COLORS.orange,\n                borderRadius: 5\n              }\n            ]\n          },\n          options: sectorOptions\n        }\n      );\n\n    }\n\n\n    \/* =====================================================\n       STATE COMPARISON\n    ====================================================== *\/\n\n    function buildStateCharts(){\n\n      var s = getSelected();\n\n      var data = stateDataForYear(\n        s.year,\n        s.sector\n      )\n      .sort(function(a,b){\n        return b.value - a.value;\n      });\n\n\n      setText(\n        \"watercons-state-sub\",\n        s.year +\n        \" \u00b7 \" +\n        sectorLabel(s.sector) +\n        \" \u00b7 Melaka ditonjolkan dalam warna oren\"\n      );\n\n\n      \/* STATE BAR *\/\n\n      var options = baseOptions();\n\n      options.plugins.legend.display = false;\n\n      options.plugins.tooltip = valueTooltip(false);\n\n      options.scales = {\n        x: {\n          grid: {\n            display: false\n          },\n          ticks: {\n            maxRotation: 45,\n            color: stateTickColor,\n            font: stateTickFont\n          }\n        },\n        y: yScale()\n      };\n\n\n      makeChart(\n        \"state\",\n        \"watercons-chart-state\",\n        {\n          type: \"bar\",\n          data: {\n            labels: data.map(function(item){\n              return item.state;\n            }),\n            datasets: [{\n              label: \"Penggunaan Air\",\n              data: data.map(function(item){\n                return item.value;\n              }),\n              backgroundColor: stateColors(data),\n              borderRadius: 7\n            }]\n          },\n          options: options\n        }\n      );\n\n\n      \/* STATE VERSUS AVERAGE *\/\n\n      var mean = stateAverage(\n        s.year,\n        s.sector\n      );\n\n\n      var differences = data.map(function(item){\n\n        return valid(mean)\n          ? item.value - mean\n          : null;\n\n      });\n\n\n      setText(\n        \"watercons-state-average-sub\",\n        s.year +\n        \" \u00b7 purata \" +\n        fmt(mean,1) +\n        \" MLD \u00b7 nilai positif melebihi purata\"\n      );\n\n\n      var avgOptions = baseOptions();\n\n      avgOptions.plugins.legend.display = false;\n\n      avgOptions.plugins.tooltip = {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            return (\n              \" Perbezaan daripada purata: \" +\n              signed(context.parsed.y,1) +\n              \" MLD\"\n            );\n          }\n        }\n      };\n\n      avgOptions.scales = {\n        x: {\n          grid: {\n            display: false\n          },\n          ticks: {\n            maxRotation: 45,\n            color: stateTickColor,\n            font: stateTickFont\n          }\n        },\n        y: {\n          grid: {\n            color: COLORS.grid\n          }\n        }\n      };\n\n\n      makeChart(\n        \"stateAverage\",\n        \"watercons-chart-state-average\",\n        {\n          type: \"bar\",\n          data: {\n            labels: data.map(function(item){\n              return item.state;\n            }),\n            datasets: [{\n              label: \"Perbezaan daripada Purata\",\n              data: differences,\n              backgroundColor: data.map(function(item,index){\n\n                if(item.state === FIXED_STATE){\n                  return COLORS.orange;\n                }\n\n                return differences[index] >= 0\n                  ? COLORS.green\n                  : COLORS.red;\n\n              }),\n              borderRadius: 7\n            }]\n          },\n          options: avgOptions\n        }\n      );\n\n    }\n\n\n    \/* =====================================================\n       RANKING\n    ====================================================== *\/\n\n    function buildRankingCharts(){\n\n      var s = getSelected();\n\n      var data = stateDataForYear(\n        s.year,\n        s.sector\n      )\n      .sort(function(a,b){\n        return b.value - a.value;\n      });\n\n\n      var limit = Math.min(5,data.length);\n\n      var high = data.slice(0,limit);\n\n      var low = data\n        .slice(-limit)\n        .sort(function(a,b){\n          return a.value - b.value;\n        });\n\n\n      setText(\n        \"watercons-high-sub\",\n        s.year +\n        \" \u00b7 \" +\n        sectorLabel(s.sector) +\n        \" \u00b7 lima negeri tertinggi\"\n      );\n\n      setText(\n        \"watercons-low-sub\",\n        s.year +\n        \" \u00b7 \" +\n        sectorLabel(s.sector) +\n        \" \u00b7 lima negeri terendah\"\n      );\n\n\n      function rankingChart(\n        name,\n        canvasId,\n        dataset,\n        color\n      ){\n\n        var options = baseOptions();\n\n        options.plugins.legend.display = false;\n\n        options.plugins.tooltip = valueTooltip(false);\n\n        options.scales = {\n          x: {\n            grid: {\n              display: false\n            },\n            ticks: {\n              maxRotation: 45,\n              color: stateTickColor,\n              font: stateTickFont\n            }\n          },\n          y: yScale()\n        };\n\n\n        makeChart(\n          name,\n          canvasId,\n          {\n            type: \"bar\",\n            data: {\n              labels: dataset.map(function(item){\n                return item.state;\n              }),\n              datasets: [{\n                label: \"Penggunaan Air\",\n                data: dataset.map(function(item){\n                  return item.value;\n                }),\n                backgroundColor: dataset.map(function(item){\n\n                  return item.state === FIXED_STATE\n                    ? COLORS.orange\n                    : color;\n\n                }),\n                borderRadius: 7\n              }]\n            },\n            options: options\n          }\n        );\n\n      }\n\n\n      rankingChart(\n        \"high\",\n        \"watercons-chart-high\",\n        high,\n        COLORS.green\n      );\n\n\n      rankingChart(\n        \"low\",\n        \"watercons-chart-low\",\n        low,\n        COLORS.red\n      );\n\n    }\n\n\n    \/* =====================================================\n       RELATIVE\n    ====================================================== *\/\n\n    function buildRelativeCharts(){\n\n      var s = getSelected();\n\n      var data = stateDataForYear(\n        s.year,\n        s.sector\n      )\n      .sort(function(a,b){\n        return b.value - a.value;\n      });\n\n\n      var melaka = melakaValue(\n        s.year,\n        s.sector\n      );\n\n      var mean = stateAverage(\n        s.year,\n        s.sector\n      );\n\n\n      setText(\n        \"watercons-relative-sub\",\n        s.year +\n        \" \u00b7 \" +\n        sectorLabel(s.sector) +\n        \" \u00b7 purata negeri \" +\n        fmt(mean,1) +\n        \" MLD\"\n      );\n\n\n      var options = baseOptions();\n\n      options.plugins.legend.display = false;\n\n      options.plugins.tooltip = valueTooltip(false);\n\n      options.scales = {\n        x: xScale(),\n        y: yScale()\n      };\n\n\n      makeChart(\n        \"melakaAverage\",\n        \"watercons-chart-melaka-average\",\n        {\n          type: \"bar\",\n          data: {\n            labels: [\n              \"Melaka\",\n              \"Purata Negeri\"\n            ],\n            datasets: [{\n              label: \"Penggunaan Air\",\n              data: [\n                melaka,\n                mean\n              ],\n              backgroundColor: [\n                COLORS.orange,\n                COLORS.blue\n              ],\n              borderRadius: 8\n            }]\n          },\n          options: options\n        }\n      );\n\n\n      \/* RELATIVE TO HIGHEST *\/\n\n      var highest = data.length\n        ? data[0].value\n        : null;\n\n\n      var relative = data.map(function(item){\n\n        return {\n          state: item.state,\n          value: (\n            valid(highest) &&\n            highest !== 0\n          )\n            ? (item.value \/ highest) * 100\n            : null\n        };\n\n      });\n\n\n      var relativeOptions = baseOptions();\n\n      relativeOptions.plugins.legend.display = false;\n\n      relativeOptions.plugins.tooltip = percentTooltip();\n\n      relativeOptions.scales = {\n        x: {\n          grid: {\n            display: false\n          },\n          ticks: {\n            maxRotation: 45,\n            color: stateTickColor,\n            font: stateTickFont\n          }\n        },\n        y: {\n          min: 0,\n          max: 110,\n          grid: {\n            color: COLORS.grid\n          },\n          ticks: {\n            callback: function(value){\n              return value + \"%\";\n            }\n          }\n        }\n      };\n\n\n      makeChart(\n        \"relative\",\n        \"watercons-chart-relative\",\n        {\n          type: \"bar\",\n          data: {\n            labels: relative.map(function(item){\n              return item.state;\n            }),\n            datasets: [{\n              label: \"Peratus Relatif\",\n              data: relative.map(function(item){\n                return item.value;\n              }),\n              backgroundColor: stateColors(relative),\n              borderRadius: 7\n            }]\n          },\n          options: relativeOptions\n        }\n      );\n\n    }\n\n\n    \/* =====================================================\n       COMPARISON\n    ====================================================== *\/\n\n    function buildComparisonCharts(){\n\n      var s = getSelected();\n\n      var years = availableYears(s.sector);\n\n\n      var melakaValues = years.map(function(year){\n\n        return melakaValue(\n          year,\n          s.sector\n        );\n\n      });\n\n\n      var compareValues = years.map(function(year){\n\n        return stateValue(\n          s.compareState,\n          year,\n          s.sector\n        );\n\n      });\n\n\n      setText(\n        \"watercons-comparison-sub\",\n        \"Melaka berbanding \" +\n        s.compareState +\n        \" \u00b7 \" +\n        sectorLabel(s.sector) +\n        \" \u00b7 sepanjang tempoh data\"\n      );\n\n\n      var options = baseOptions();\n\n      options.interaction = {\n        mode: \"index\",\n        intersect: false\n      };\n\n      options.plugins.tooltip = valueTooltip(false);\n\n      options.scales = {\n        x: xScale(),\n        y: yScale()\n      };\n\n\n      makeChart(\n        \"comparison\",\n        \"watercons-chart-comparison\",\n        {\n          type: \"line\",\n          data: {\n            labels: years,\n            datasets: [\n              lineDataset(\n                \"Melaka\",\n                melakaValues,\n                COLORS.orange,\n                false\n              ),\n              lineDataset(\n                s.compareState,\n                compareValues,\n                COLORS.blue,\n                false\n              )\n            ]\n          },\n          options: options\n        }\n      );\n\n\n      \/* GAP *\/\n\n      var gaps = years.map(function(year){\n\n        var melaka = melakaValue(\n          year,\n          s.sector\n        );\n\n        var comparison = stateValue(\n          s.compareState,\n          year,\n          s.sector\n        );\n\n\n        if(\n          !valid(melaka) ||\n          !valid(comparison)\n        ){\n          return null;\n        }\n\n        return melaka - comparison;\n\n      });\n\n\n      var gapOptions = baseOptions();\n\n      gapOptions.plugins.legend.display = false;\n\n      gapOptions.plugins.tooltip = {\n        backgroundColor: COLORS.dark,\n        callbacks: {\n          label: function(context){\n\n            return (\n              \" Melaka - \" +\n              s.compareState +\n              \": \" +\n              signed(context.parsed.y,1) +\n              \" MLD\"\n            );\n          }\n        }\n      };\n\n      gapOptions.scales = {\n        x: xScale(),\n        y: {\n          grid: {\n            color: COLORS.grid\n          }\n        }\n      };\n\n\n      makeChart(\n        \"gap\",\n        \"watercons-chart-gap\",\n        {\n          type: \"bar\",\n          data: {\n            labels: years,\n            datasets: [{\n              label: \"Perbezaan Penggunaan\",\n              data: gaps,\n              backgroundColor: gaps.map(function(value){\n\n                return valid(value) && value >= 0\n                  ? COLORS.green\n                  : COLORS.red;\n\n              }),\n              borderRadius: 7\n            }]\n          },\n          options: gapOptions\n        }\n      );\n\n    }\n\n\n    \/* =====================================================\n       TAB CONTROL\n    ====================================================== *\/\n\n    var tabs = [\n      \"trend\",\n      \"sector\",\n      \"change\",\n      \"state\",\n      \"ranking\",\n      \"relative\",\n      \"comparison\"\n    ];\n\n\n    function activeTab(){\n\n      var active = root.querySelector(\n        \".tbtn.active\"\n      );\n\n      return active\n        ? active.getAttribute(\"data-tab\")\n        : \"trend\";\n    }\n\n\n    function redraw(){\n\n      if(!allRows.length){\n        return;\n      }\n\n      updateCards();\n\n      var tab = activeTab();\n\n\n      if(tab === \"trend\"){\n        buildTrendCharts();\n      }\n\n      if(tab === \"sector\"){\n        buildSectorCharts();\n      }\n\n      if(tab === \"change\"){\n        buildChangeCharts();\n      }\n\n      if(tab === \"state\"){\n        buildStateCharts();\n      }\n\n      if(tab === \"ranking\"){\n        buildRankingCharts();\n      }\n\n      if(tab === \"relative\"){\n        buildRelativeCharts();\n      }\n\n      if(tab === \"comparison\"){\n        buildComparisonCharts();\n      }\n\n    }\n\n\n    root.querySelectorAll(\".tbtn\").forEach(function(button){\n\n      button.addEventListener(\"click\",function(){\n\n        root.querySelectorAll(\".tbtn\").forEach(function(item){\n\n          item.classList.remove(\"active\");\n\n          item.setAttribute(\n            \"aria-selected\",\n            \"false\"\n          );\n\n        });\n\n\n        button.classList.add(\"active\");\n\n        button.setAttribute(\n          \"aria-selected\",\n          \"true\"\n        );\n\n\n        tabs.forEach(function(tab){\n\n          hide(\"watercons-tab-\" + tab);\n\n        });\n\n\n        show(\n          \"watercons-tab-\" +\n          button.getAttribute(\"data-tab\")\n        );\n\n\n        redraw();\n\n      });\n\n    });\n\n\n    \/* =====================================================\n       FILTER EVENTS\n    ====================================================== *\/\n\n    el(\"watercons-year\").addEventListener(\n      \"change\",\n      redraw\n    );\n\n\n    el(\"watercons-compare-state\").addEventListener(\n      \"change\",\n      redraw\n    );\n\n\n    el(\"watercons-sector\").addEventListener(\n      \"change\",\n      function(){\n\n        updateYearFilter();\n\n        redraw();\n\n      }\n    );\n\n\n    \/* =====================================================\n       API FETCH\n    ====================================================== *\/\n\n    fetch(API_URL)\n\n      .then(function(response){\n\n        if(!response.ok){\n\n          throw new Error(\n            \"HTTP \" + response.status\n          );\n        }\n\n        return response.json();\n\n      })\n\n      .then(function(data){\n\n        var records = Array.isArray(data)\n          ? data\n          : (\n              data && Array.isArray(data.data)\n                ? data.data\n                : []\n            );\n\n\n        allRows = records\n          .map(function(row){\n\n            return {\n              date: row.date,\n              year: yearOf(row),\n              state: String(row.state || \"\").trim(),\n              stateKey: normalizeState(row.state),\n              sector: normalizeSector(row.sector),\n              value: num(row.value)\n            };\n\n          })\n          .filter(function(row){\n\n            return (\n              valid(row.year) &&\n              row.stateKey !== \"\" &&\n              row.sector !== null &&\n              valid(row.value)\n            );\n\n          });\n\n\n        melakaRows = allRows.filter(function(row){\n\n          return (\n            row.stateKey ===\n            normalizeState(FIXED_STATE)\n          );\n\n        });\n\n\n        if(!melakaRows.length){\n\n          throw new Error(\n            \"Tiada rekod penggunaan air Melaka ditemui.\"\n          );\n        }\n\n\n        \/*\n          Prefer both sectors when complete data exists.\n          Otherwise use a sector that has valid records.\n        *\/\n\n        if(!availableYears(\"both\").length){\n\n          if(availableYears(\"domestic\").length){\n\n            el(\"watercons-sector\").value =\n              \"domestic\";\n\n          }else if(availableYears(\"nondomestic\").length){\n\n            el(\"watercons-sector\").value =\n              \"nondomestic\";\n\n          }\n        }\n\n\n        populateFilters();\n\n\n        hide(\"watercons-loading\");\n\n        show(\"watercons-area\");\n\n\n        redraw();\n\n      })\n\n      .catch(function(error){\n\n        var loading = el(\"watercons-loading\");\n\n        loading.innerHTML = \"\";\n\n\n        var heading = document.createElement(\"div\");\n\n        heading.style.color = COLORS.red;\n\n        heading.style.fontSize = \"16px\";\n\n        heading.style.fontWeight = \"800\";\n\n        heading.style.marginBottom = \"8px\";\n\n        heading.textContent =\n          \"Gagal memuatkan data\";\n\n\n        var message = document.createElement(\"p\");\n\n        message.style.fontSize = \"13px\";\n\n        message.style.color = COLORS.muted;\n\n        message.textContent = error.message;\n\n\n        loading.appendChild(heading);\n\n        loading.appendChild(message);\n\n      });\n\n  }\n\n\n  if(document.readyState === \"loading\"){\n\n    document.addEventListener(\n      \"DOMContentLoaded\",\n      init,\n      {once:true}\n    );\n\n  }else{\n\n    init();\n  }\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n#wateraccess-wrap,\n#wateraccess-wrap * {\n  box-sizing: border-box;\n}\n\n#wateraccess-wrap {\n  --wa-blue: #2563eb;\n  --wa-orange: #f97316;\n  --wa-green: #16a34a;\n  --wa-red: #dc2626;\n  --wa-purple: #7c3aed;\n  --wa-cyan: #0891b2;\n  --wa-text: #0f172a;\n  --wa-muted: #64748b;\n  --wa-border: #dbeafe;\n  --wa-bg: #f8fafc;\n  --wa-shadow: 0 10px 28px rgba(15,23,42,.07);\n\n  width: 100%;\n  padding: 26px;\n  border: 1px solid var(--wa-border);\n  border-radius: 22px;\n  background: linear-gradient(180deg,#fff,#f8fafc);\n  font-family: Arial,sans-serif;\n  color: var(--wa-text);\n}\n\n#wateraccess-wrap .wa-container {\n  max-width: 1200px;\n  margin: auto;\n}\n\n#wateraccess-wrap .wa-header,\n#wateraccess-wrap .wa-filters,\n#wateraccess-wrap .wa-card,\n#wateraccess-wrap .wa-chart-card,\n#wateraccess-wrap .wa-status {\n  background: white;\n  border: 1px solid var(--wa-border);\n  box-shadow: var(--wa-shadow);\n}\n\n#wateraccess-wrap .wa-header {\n  padding: 27px;\n  border-radius: 20px;\n  margin-bottom: 20px;\n}\n\n#wateraccess-wrap .wa-eyebrow {\n  color: var(--wa-blue);\n  font-size: 12px;\n  font-weight: 800;\n  letter-spacing: .1em;\n  text-transform: uppercase;\n  margin-bottom: 10px;\n}\n\n#wateraccess-wrap h2 {\n  margin: 0;\n  font-size: clamp(26px,4vw,42px);\n  font-weight: 800;\n  line-height: 1.2;\n}\n\n#wateraccess-wrap .wa-description {\n  color: var(--wa-muted);\n  font-size: 14px;\n  line-height: 1.7;\n  margin: 12px 0 0;\n}\n\n#wateraccess-wrap .wa-filters {\n  display: grid;\n  grid-template-columns: repeat(3,minmax(0,1fr));\n  gap: 15px;\n  padding: 19px;\n  border-radius: 17px;\n  margin-bottom: 20px;\n}\n\n#wateraccess-wrap .wa-filter {\n  min-width: 0;\n}\n\n#wateraccess-wrap .wa-filter label {\n  display: block;\n  color: var(--wa-muted);\n  font-size: 11px;\n  font-weight: 800;\n  text-transform: uppercase;\n  letter-spacing: .06em;\n  margin-bottom: 8px;\n}\n\n#wateraccess-wrap select {\n  width: 100%;\n  min-height: 44px;\n  padding: 10px 13px;\n  border: 1.5px solid #bfdbfe;\n  border-radius: 10px;\n  color: var(--wa-text);\n  background: white;\n  font-size: 14px;\n  font-weight: 600;\n  cursor: pointer;\n}\n\n#wateraccess-wrap select:focus {\n  outline: 3px solid rgba(37,99,235,.12);\n  border-color: var(--wa-blue);\n}\n\n#wateraccess-wrap .wa-stats {\n  display: grid;\n  grid-template-columns: repeat(4,minmax(0,1fr));\n  gap: 14px;\n  margin-bottom: 20px;\n}\n\n#wateraccess-wrap .wa-card {\n  border-radius: 16px;\n  padding: 19px;\n  border-left: 4px solid var(--wa-blue);\n  min-width: 0;\n}\n\n#wateraccess-wrap .wa-card:nth-child(2) {\n  border-left-color: var(--wa-cyan);\n}\n\n#wateraccess-wrap .wa-card:nth-child(3) {\n  border-left-color: var(--wa-purple);\n}\n\n#wateraccess-wrap .wa-card:nth-child(4) {\n  border-left-color: var(--wa-orange);\n}\n\n#wateraccess-wrap .wa-stat-label {\n  font-size: 11px;\n  text-transform: uppercase;\n  color: var(--wa-muted);\n  font-weight: 800;\n  line-height: 1.5;\n  margin-bottom: 8px;\n}\n\n#wateraccess-wrap .wa-stat-value {\n  font-size: clamp(20px,2.5vw,27px);\n  font-weight: 800;\n  overflow-wrap: anywhere;\n}\n\n#wateraccess-wrap .wa-stat-sub {\n  color: var(--wa-muted);\n  font-size: 12px;\n  line-height: 1.5;\n  margin-top: 6px;\n}\n\n#wateraccess-wrap .wa-tabs {\n  display: flex;\n  justify-content: space-between;\n  gap: 7px;\n  padding: 8px;\n  border-radius: 999px;\n  background: #eff6ff;\n  border: 1px solid var(--wa-border);\n  margin-bottom: 19px;\n  overflow-x: auto;\n  scrollbar-width: thin;\n}\n\n#wateraccess-wrap .wa-tab-btn {\n  flex: 1;\n  white-space: nowrap;\n  border: 0;\n  border-radius: 999px;\n  background: transparent;\n  color: var(--wa-muted);\n  padding: 12px 14px;\n  font-size: 13px;\n  font-family: inherit;\n  font-weight: 800;\n  cursor: pointer;\n}\n\n#wateraccess-wrap .wa-tab-btn.active {\n  background: white;\n  color: var(--wa-blue);\n  box-shadow: 0 4px 13px rgba(15,23,42,.09);\n}\n\n#wateraccess-wrap .wa-tab-btn:focus-visible {\n  outline: 2px solid var(--wa-blue);\n}\n\n#wateraccess-wrap .wa-chart-card {\n  padding: 24px;\n  border-radius: 19px;\n  margin-bottom: 18px;\n  min-width: 0;\n}\n\n#wateraccess-wrap .wa-chart-title {\n  font-size: 18px;\n  font-weight: 800;\n  line-height: 1.4;\n  margin-bottom: 6px;\n}\n\n#wateraccess-wrap .wa-chart-sub {\n  font-size: 13px;\n  line-height: 1.6;\n  color: var(--wa-muted);\n  margin-bottom: 18px;\n}\n\n#wateraccess-wrap .wa-two {\n  display: grid;\n  grid-template-columns: repeat(2,minmax(0,1fr));\n  gap: 18px;\n}\n\n#wateraccess-wrap .wa-chart {\n  position: relative;\n  height: 390px;\n  width: 100%;\n}\n\n#wateraccess-wrap .wa-chart-small {\n  position: relative;\n  height: 330px;\n  width: 100%;\n}\n\n#wateraccess-wrap canvas {\n  max-width: 100%;\n}\n\n#wateraccess-wrap .wa-status {\n  border-radius: 18px;\n  padding: 60px 20px;\n  text-align: center;\n  color: var(--wa-muted);\n  line-height: 1.7;\n}\n\n#wateraccess-wrap .wa-spinner {\n  width: 38px;\n  height: 38px;\n  margin: 0 auto 15px;\n  border: 3px solid #dbeafe;\n  border-top-color: var(--wa-blue);\n  border-radius: 50%;\n  animation: wa-spin .8s linear infinite;\n}\n\n@keyframes wa-spin {\n  to { transform: rotate(360deg); }\n}\n\n#wateraccess-wrap .wa-note {\n  padding: 15px 17px;\n  border-radius: 12px;\n  background: #eff6ff;\n  border: 1px solid #bfdbfe;\n  color: #475569;\n  font-size: 12px;\n  line-height: 1.7;\n}\n\n@media(max-width:1024px) {\n  #wateraccess-wrap .wa-stats {\n    grid-template-columns: repeat(2,minmax(0,1fr));\n  }\n\n  #wateraccess-wrap .wa-two {\n    grid-template-columns: 1fr;\n  }\n}\n\n@media(max-width:700px) {\n  #wateraccess-wrap .wa-filters {\n    grid-template-columns: 1fr;\n  }\n}\n\n@media(max-width:600px) {\n  #wateraccess-wrap {\n    padding: 13px;\n  }\n\n  #wateraccess-wrap .wa-header,\n  #wateraccess-wrap .wa-chart-card {\n    padding: 19px;\n  }\n\n  #wateraccess-wrap .wa-stats {\n    grid-template-columns: 1fr;\n  }\n\n  #wateraccess-wrap .wa-tabs {\n    justify-content: flex-start;\n  }\n\n  #wateraccess-wrap .wa-tab-btn {\n    flex: 0 0 auto;\n  }\n\n  #wateraccess-wrap .wa-chart,\n  #wateraccess-wrap .wa-chart-small {\n    height: 310px;\n  }\n}\n<\/style>\n\n\n<div id=\"wateraccess-wrap\">\n<div class=\"wa-container\">\n\n  <!-- HEADER -->\n\n  <div class=\"wa-header\">\n\n    <div class=\"wa-eyebrow\">\n      Statistik Bekalan Air Melaka\n    <\/div>\n\n    <h2>\n      Papan Pemuka Akses kepada Air Paip Terawat\n      Mengikut Negeri dan Strata\n    <\/h2>\n\n    <p class=\"wa-description\">\n      Visualisasi peratus isi rumah di Melaka yang mempunyai\n      akses kepada air paip terawat mengikut strata keseluruhan,\n      bandar dan luar bandar. Terokai trend tahunan, jurang\n      bandar\u2013luar bandar, perubahan akses serta perbandingan\n      Melaka dengan negeri-negeri lain.\n    <\/p>\n\n  <\/div>\n\n\n  <!-- FILTERS -->\n\n  <div class=\"wa-filters\">\n\n    <div class=\"wa-filter\">\n\n      <label for=\"wa-year\">\n        Tahun\n      <\/label>\n\n      <select id=\"wa-year\"><\/select>\n\n    <\/div>\n\n\n    <div class=\"wa-filter\">\n\n      <label for=\"wa-strata\">\n        Strata\n      <\/label>\n\n      <select id=\"wa-strata\">\n\n        <option value=\"overall\">\n          Keseluruhan\n        <\/option>\n\n        <option value=\"urban\">\n          Bandar\n        <\/option>\n\n        <option value=\"rural\">\n          Luar Bandar\n        <\/option>\n\n      <\/select>\n\n    <\/div>\n\n\n    <div class=\"wa-filter\">\n\n      <label for=\"wa-compare\">\n        Negeri Perbandingan\n      <\/label>\n\n      <select id=\"wa-compare\"><\/select>\n\n    <\/div>\n\n  <\/div>\n\n\n  <!-- SUMMARY CARDS -->\n\n  <div class=\"wa-stats\" id=\"wa-stats\" style=\"display:none\">\n\n    <div class=\"wa-card\">\n\n      <div class=\"wa-stat-label\">\n        Tahun Dipilih\n      <\/div>\n\n      <div class=\"wa-stat-value\" id=\"wa-stat-year\">\n        \u2014\n      <\/div>\n\n      <div class=\"wa-stat-sub\" id=\"wa-stat-strata\">\n        \u2014\n      <\/div>\n\n    <\/div>\n\n\n    <div class=\"wa-card\">\n\n      <div class=\"wa-stat-label\">\n        Kadar Akses Melaka\n      <\/div>\n\n      <div class=\"wa-stat-value\" id=\"wa-stat-access\">\n        \u2014\n      <\/div>\n\n      <div class=\"wa-stat-sub\">\n        Isi rumah dengan akses air paip terawat\n      <\/div>\n\n    <\/div>\n\n\n    <div class=\"wa-card\">\n\n      <div class=\"wa-stat-label\">\n        Perubahan Tahunan\n      <\/div>\n\n      <div class=\"wa-stat-value\" id=\"wa-stat-change\">\n        \u2014\n      <\/div>\n\n      <div class=\"wa-stat-sub\" id=\"wa-stat-change-sub\">\n        \u2014\n      <\/div>\n\n    <\/div>\n\n\n    <div class=\"wa-card\">\n\n      <div class=\"wa-stat-label\">\n        Jurang Bandar\u2013Luar Bandar\n      <\/div>\n\n      <div class=\"wa-stat-value\" id=\"wa-stat-gap\">\n        \u2014\n      <\/div>\n\n      <div class=\"wa-stat-sub\">\n        Bandar ditolak luar bandar \u00b7 mata peratusan\n      <\/div>\n\n    <\/div>\n\n  <\/div>\n\n\n  <!-- LOADING -->\n\n  <div class=\"wa-status\" id=\"wa-loading\" role=\"status\">\n\n    <div class=\"wa-spinner\"><\/div>\n\n    <strong>\n      Memuatkan data&#8230;\n    <\/strong>\n\n    <div>\n      Sedang mendapatkan rekod akses air paip terawat Melaka.\n    <\/div>\n\n  <\/div>\n\n\n  <!-- DASHBOARD -->\n\n  <div id=\"wa-dashboard\" style=\"display:none\">\n\n\n    <!-- TAB BUTTONS -->\n\n    <div class=\"wa-tabs\" role=\"tablist\" aria-label=\"Paparan analisis\">\n\n      <button class=\"wa-tab-btn active\"\n        type=\"button\"\n        data-tab=\"trend\"\n        role=\"tab\"\n        aria-selected=\"true\">\n        Trend\n      <\/button>\n\n      <button class=\"wa-tab-btn\"\n        type=\"button\"\n        data-tab=\"strata\"\n        role=\"tab\"\n        aria-selected=\"false\">\n        Strata\n      <\/button>\n\n      <button class=\"wa-tab-btn\"\n        type=\"button\"\n        data-tab=\"gap\"\n        role=\"tab\"\n        aria-selected=\"false\">\n        Jurang\n      <\/button>\n\n      <button class=\"wa-tab-btn\"\n        type=\"button\"\n        data-tab=\"change\"\n        role=\"tab\"\n        aria-selected=\"false\">\n        Perubahan\n      <\/button>\n\n      <button class=\"wa-tab-btn\"\n        type=\"button\"\n        data-tab=\"state\"\n        role=\"tab\"\n        aria-selected=\"false\">\n        Negeri\n      <\/button>\n\n      <button class=\"wa-tab-btn\"\n        type=\"button\"\n        data-tab=\"ranking\"\n        role=\"tab\"\n        aria-selected=\"false\">\n        Kedudukan\n      <\/button>\n\n      <button class=\"wa-tab-btn\"\n        type=\"button\"\n        data-tab=\"comparison\"\n        role=\"tab\"\n        aria-selected=\"false\">\n        Perbandingan\n      <\/button>\n\n    <\/div>\n\n\n    <!-- TREND TAB -->\n\n    <div id=\"wa-tab-trend\">\n\n      <div class=\"wa-two\">\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Trend Akses Air Paip Terawat Melaka\n          <\/div>\n\n          <div class=\"wa-chart-sub\" id=\"wa-trend-sub\">\n            Peratus akses mengikut tahun.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-trend\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Jurang daripada Akses Penuh\n          <\/div>\n\n          <div class=\"wa-chart-sub\">\n            Perbezaan antara kadar akses semasa dengan\n            tahap akses penuh 100%.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-remaining\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Trend Akses Mengikut Strata\n        <\/div>\n\n        <div class=\"wa-chart-sub\">\n          Perbandingan akses keseluruhan, bandar dan luar bandar\n          sepanjang tempoh data yang tersedia.\n          Carta ini sentiasa menunjukkan ketiga-tiga strata.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-strata-trend\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- STRATA TAB -->\n\n    <div id=\"wa-tab-strata\" style=\"display:none\">\n\n      <div class=\"wa-two\">\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Perbandingan Kadar Akses Mengikut Strata\n          <\/div>\n\n          <div class=\"wa-chart-sub\" id=\"wa-strata-sub\">\n            Peratus akses keseluruhan, bandar dan luar bandar.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-strata\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Akses Berbanding Baki kepada 100%\n          <\/div>\n\n          <div class=\"wa-chart-sub\" id=\"wa-progress-sub\">\n            Perbandingan kadar akses dengan baki\n            kepada tahap akses penuh.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-progress\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Trend Akses Bandar dan Luar Bandar\n        <\/div>\n\n        <div class=\"wa-chart-sub\">\n          Perbandingan kedua-dua strata secara langsung\n          untuk mengenal pasti perubahan jurang akses.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-urban-rural\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- GAP TAB -->\n\n    <div id=\"wa-tab-gap\" style=\"display:none\">\n\n      <div class=\"wa-two\">\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Jurang Akses Bandar\u2013Luar Bandar\n          <\/div>\n\n          <div class=\"wa-chart-sub\">\n            Kadar akses bandar ditolak kadar akses luar bandar.\n            Nilai positif menunjukkan akses bandar lebih tinggi.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-gap\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Jurang kepada Akses Penuh Mengikut Strata\n          <\/div>\n\n          <div class=\"wa-chart-sub\" id=\"wa-full-gap-sub\">\n            Baki mata peratusan untuk mencapai kadar akses 100%.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-full-gap\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Trend Perbezaan Akses Mengikut Strata\n        <\/div>\n\n        <div class=\"wa-chart-sub\">\n          Perbezaan antara bandar dan luar bandar\n          sepanjang tempoh data yang tersedia.\n          Tahun tanpa rekod lengkap kedua-dua strata tidak dikira.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-gap-trend\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- CHANGE TAB -->\n\n    <div id=\"wa-tab-change\" style=\"display:none\">\n\n      <div class=\"wa-two\">\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Perubahan Tahunan Kadar Akses\n          <\/div>\n\n          <div class=\"wa-chart-sub\">\n            Perubahan kadar akses berbanding tahun terdahulu\n            yang tersedia, dalam mata peratusan.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-change\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Perubahan Terkumpul daripada Tahun Asas\n          <\/div>\n\n          <div class=\"wa-chart-sub\" id=\"wa-base-sub\">\n            Perbezaan kadar akses daripada tahun asas.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-base\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Perubahan Tahunan Mengikut Strata\n        <\/div>\n\n        <div class=\"wa-chart-sub\">\n          Perubahan kadar akses keseluruhan, bandar dan luar bandar.\n          Setiap strata dibandingkan dengan tahun terdahulu\n          yang mempunyai rekod bagi strata tersebut.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-strata-change\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- STATE TAB -->\n\n    <div id=\"wa-tab-state\" style=\"display:none\">\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Perbandingan Kadar Akses Mengikut Negeri\n        <\/div>\n\n        <div class=\"wa-chart-sub\" id=\"wa-state-sub\">\n          Perbandingan negeri pada tahun dan strata pilihan.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-state\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Jurang Akses Negeri daripada 100%\n        <\/div>\n\n        <div class=\"wa-chart-sub\" id=\"wa-state-gap-sub\">\n          Baki mata peratusan bagi setiap negeri\n          untuk mencapai akses penuh.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-state-gap\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- RANKING TAB -->\n\n    <div id=\"wa-tab-ranking\" style=\"display:none\">\n\n      <div class=\"wa-two\">\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Lima Negeri dengan Kadar Akses Tertinggi\n          <\/div>\n\n          <div class=\"wa-chart-sub\" id=\"wa-high-sub\">\n            Lima negeri tertinggi berdasarkan data tersedia.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-high\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n\n        <div class=\"wa-chart-card\">\n\n          <div class=\"wa-chart-title\">\n            Lima Negeri dengan Kadar Akses Terendah\n          <\/div>\n\n          <div class=\"wa-chart-sub\" id=\"wa-low-sub\">\n            Lima negeri terendah berdasarkan data tersedia.\n          <\/div>\n\n          <div class=\"wa-chart-small\">\n            <canvas id=\"wa-chart-low\"><\/canvas>\n          <\/div>\n\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- COMPARISON TAB -->\n\n    <div id=\"wa-tab-comparison\" style=\"display:none\">\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Trend Melaka berbanding Negeri Pilihan\n        <\/div>\n\n        <div class=\"wa-chart-sub\" id=\"wa-comparison-sub\">\n          Perbandingan trend kadar akses.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-comparison\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Perbezaan Kadar Akses dengan Negeri Pilihan\n        <\/div>\n\n        <div class=\"wa-chart-sub\">\n          Melaka ditolak negeri perbandingan.\n          Nilai positif menunjukkan kadar akses Melaka\n          lebih tinggi, dalam mata peratusan.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-comparison-gap\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n\n      <div class=\"wa-chart-card\">\n\n        <div class=\"wa-chart-title\">\n          Perbandingan Strata Melaka dengan Negeri Pilihan\n        <\/div>\n\n        <div class=\"wa-chart-sub\" id=\"wa-strata-comparison-sub\">\n          Perbandingan keseluruhan, bandar dan luar bandar\n          bagi tahun dipilih.\n        <\/div>\n\n        <div class=\"wa-chart\">\n          <canvas id=\"wa-chart-strata-comparison\"><\/canvas>\n        <\/div>\n\n      <\/div>\n\n    <\/div>\n\n\n    <!-- NOTES -->\n\n    <div class=\"wa-note\">\n\n      <strong>Nota:<\/strong>\n\n      Semua nilai merupakan peratus isi rumah yang mempunyai\n      akses kepada air paip terawat.\n\n      Strata keseluruhan, bandar dan luar bandar ialah\n      statistik berasingan yang diterbitkan dalam dataset.\n\n      Nilai keseluruhan tidak diperoleh dengan menjumlahkan\n      atau mengambil purata mudah nilai bandar dan luar bandar.\n\n      Perubahan dan jurang antara dua kadar peratus\n      dinyatakan dalam mata peratusan.\n\n      Rekod agregat Malaysia dikecualikan daripada\n      carta perbandingan negeri.\n\n      Tahun tanpa data tidak dianggarkan atau diisi\n      dengan nilai sifar.\n\n    <\/div>\n\n  <\/div>\n\n<\/div>\n<\/div>\n\n\n<script>\n(function(){\n\n\"use strict\";\n\n\/* =====================================================\n   INITIALIZATION\n===================================================== *\/\n\nconst root = document.getElementById(\"wateraccess-wrap\");\n\nif(!root || root.dataset.initialized === \"true\"){\n  return;\n}\n\nroot.dataset.initialized = \"true\";\n\nfunction start(){\n\n  if(typeof Chart === \"undefined\"){\n    setTimeout(start,400);\n    return;\n  }\n\n  const API =\n    \"https:\/\/api.data.gov.my\/data-catalogue\/?id=water_access&limit=2000\";\n\n  const MELAKA = \"Melaka\";\n\n  const COLORS = {\n    blue: \"#2563eb\",\n    orange: \"#f97316\",\n    green: \"#16a34a\",\n    red: \"#dc2626\",\n    purple: \"#7c3aed\",\n    cyan: \"#0891b2\",\n    grid: \"#dbeafe\",\n    muted: \"#64748b\",\n    dark: \"#0f172a\"\n  };\n\n  let rows = [];\n  let melakaRows = [];\n  let charts = {};\n\n  Chart.defaults.color = COLORS.muted;\n  Chart.defaults.borderColor = COLORS.grid;\n  Chart.defaults.font.family = \"Arial, sans-serif\";\n\n\n\/* =====================================================\n   DOM HELPERS\n===================================================== *\/\n\nfunction el(id){\n  return root.querySelector(\"#\" + id);\n}\n\nfunction text(id,value){\n\n  const target = el(id);\n\n  if(target){\n    target.textContent = value;\n  }\n\n}\n\nfunction show(id){\n  el(id).style.display = \"\";\n}\n\nfunction hide(id){\n  el(id).style.display = \"none\";\n}\n\n\n\/* =====================================================\n   FORMATTING\n===================================================== *\/\n\nfunction valid(value){\n\n  return (\n    typeof value === \"number\" &&\n    Number.isFinite(value)\n  );\n\n}\n\nfunction number(value){\n\n  if(\n    value === null ||\n    value === undefined ||\n    value === \"\"\n  ){\n    return null;\n  }\n\n  const n = Number(value);\n\n  return Number.isFinite(n) ? n : null;\n\n}\n\nfunction fmt(value,digits=1){\n\n  if(!valid(value)){\n    return \"\u2014\";\n  }\n\n  return value.toLocaleString(\"ms-MY\",{\n    minimumFractionDigits:digits,\n    maximumFractionDigits:digits\n  });\n\n}\n\nfunction pct(value){\n\n  return valid(value)\n    ? fmt(value,1) + \"%\"\n    : \"\u2014\";\n\n}\n\nfunction pp(value){\n\n  if(!valid(value)){\n    return \"\u2014\";\n  }\n\n  return (\n    (value > 0 ? \"+\" : \"\") +\n    fmt(value,1) +\n    \" mata peratusan\"\n  );\n\n}\n\nfunction shortPP(value){\n\n  if(!valid(value)){\n    return \"\u2014\";\n  }\n\n  return (\n    (value > 0 ? \"+\" : \"\") +\n    fmt(value,1)\n  );\n\n}\n\nfunction average(values){\n\n  const clean = values.filter(valid);\n\n  if(!clean.length){\n    return null;\n  }\n\n  return clean.reduce((a,b)=>a+b,0) \/ clean.length;\n\n}\n\n\n\/* =====================================================\n   DATA HELPERS\n===================================================== *\/\n\nfunction key(value){\n\n  return String(value || \"\")\n    .trim()\n    .toLowerCase();\n\n}\n\nfunction yearOf(date){\n\n  const year = parseInt(\n    String(date || \"\").slice(0,4),\n    10\n  );\n\n  return Number.isFinite(year) ? year : null;\n\n}\n\nfunction strataLabel(strata){\n\n  const labels = {\n    overall:\"Keseluruhan\",\n    urban:\"Bandar\",\n    rural:\"Luar Bandar\"\n  };\n\n  return labels[strata] || strata;\n\n}\n\n\/*\n  Each state\/year\/strata should contain one published value.\n\n  Duplicate identical records are not added together.\n  If duplicates differ, their mean is used as a defensive\n  fallback rather than treating percentages as additive.\n*\/\n\nfunction access(state,year,strata){\n\n  const values = rows\n    .filter(row=>\n      row.stateKey === key(state) &&\n      row.year === year &&\n      row.strata === strata\n    )\n    .map(row=>row.proportion)\n    .filter(valid);\n\n  return average(values);\n\n}\n\nfunction melaka(year,strata){\n\n  return access(MELAKA,year,strata);\n\n}\n\nfunction yearsFor(strata){\n\n  return [...new Set(\n    melakaRows.map(row=>row.year)\n  )]\n  .filter(year=>\n    valid(year) &&\n    valid(melaka(year,strata))\n  )\n  .sort((a,b)=>a-b);\n\n}\n\nfunction allYears(){\n\n  return [...new Set(\n    melakaRows.map(row=>row.year)\n  )]\n  .filter(valid)\n  .sort((a,b)=>a-b);\n\n}\n\nfunction previousYear(year,strata){\n\n  const years = yearsFor(strata)\n    .filter(y=>y<year);\n\n  return years.length\n    ? years[years.length-1]\n    : null;\n\n}\n\nfunction states(){\n\n  return [...new Set(\n    rows\n      .filter(row=>row.stateKey!==\"malaysia\")\n      .map(row=>row.state)\n  )]\n  .sort((a,b)=>a.localeCompare(b));\n\n}\n\nfunction stateData(year,strata){\n\n  return states()\n    .map(state=>({\n      state:state,\n      value:access(state,year,strata)\n    }))\n    .filter(item=>valid(item.value));\n\n}\n\nfunction selected(){\n\n  return {\n    year:Number(el(\"wa-year\").value),\n    strata:el(\"wa-strata\").value,\n    compare:el(\"wa-compare\").value\n  };\n\n}\n\n\n\/* =====================================================\n   FILTERS\n===================================================== *\/\n\nfunction fillYears(){\n\n  const select = el(\"wa-year\");\n\n  const oldYear = Number(select.value);\n\n  const strata = el(\"wa-strata\").value;\n\n  const years = yearsFor(strata)\n    .slice()\n    .reverse();\n\n  select.innerHTML = \"\";\n\n  years.forEach(year=>{\n\n    const option = document.createElement(\"option\");\n\n    option.value = year;\n    option.textContent = year;\n\n    select.appendChild(option);\n\n  });\n\n  if(years.includes(oldYear)){\n\n    select.value = String(oldYear);\n\n  }else if(years.length){\n\n    select.value = String(years[0]);\n\n  }\n\n}\n\nfunction fillCompare(){\n\n  const select = el(\"wa-compare\");\n\n  const oldState = select.value;\n\n  const available = states()\n    .filter(state=>key(state)!==\"melaka\");\n\n  select.innerHTML = \"\";\n\n  available.forEach(state=>{\n\n    const option = document.createElement(\"option\");\n\n    option.value = state;\n    option.textContent = state;\n\n    select.appendChild(option);\n\n  });\n\n  if(available.includes(oldState)){\n\n    select.value = oldState;\n\n  }else if(available.includes(\"Negeri Sembilan\")){\n\n    select.value = \"Negeri Sembilan\";\n\n  }else if(available.length){\n\n    select.value = available[0];\n\n  }\n\n}\n\n\n\/* =====================================================\n   CHART HELPERS\n===================================================== *\/\n\nfunction makeChart(name,id,config){\n\n  if(charts[name]){\n    charts[name].destroy();\n  }\n\n  const canvas = el(id);\n\n  if(!canvas){\n    return;\n  }\n\n  charts[name] = new Chart(\n    canvas.getContext(\"2d\"),\n    config\n  );\n\n}\n\nfunction baseOptions(){\n\n  return {\n    responsive:true,\n    maintainAspectRatio:false,\n    interaction:{\n      mode:\"nearest\",\n      intersect:false\n    },\n    plugins:{\n      legend:{\n        position:\"bottom\",\n        labels:{\n          usePointStyle:true,\n          padding:18\n        }\n      },\n      tooltip:{\n        backgroundColor:COLORS.dark\n      }\n    }\n  };\n\n}\n\nfunction percentageTooltip(horizontal=false){\n\n  return {\n    backgroundColor:COLORS.dark,\n    callbacks:{\n      label(context){\n\n        const value = horizontal\n          ? context.parsed.x\n          : context.parsed.y;\n\n        return (\n          \" \" +\n          context.dataset.label +\n          \": \" +\n          pct(value)\n        );\n\n      }\n    }\n  };\n\n}\n\nfunction ppTooltip(horizontal=false){\n\n  return {\n    backgroundColor:COLORS.dark,\n    callbacks:{\n      label(context){\n\n        const value = horizontal\n          ? context.parsed.x\n          : context.parsed.y;\n\n        return (\n          \" \" +\n          context.dataset.label +\n          \": \" +\n          pp(value)\n        );\n\n      }\n    }\n  };\n\n}\n\nfunction xScale(){\n\n  return {\n    grid:{\n      display:false\n    },\n    ticks:{\n      maxRotation:45,\n      autoSkip:true\n    }\n  };\n\n}\n\nfunction percentScale(){\n\n  return {\n    min:0,\n    max:100,\n    grid:{\n      color:COLORS.grid\n    },\n    ticks:{\n      callback(value){\n        return value + \"%\";\n      }\n    }\n  };\n\n}\n\nfunction ppScale(){\n\n  return {\n    grid:{\n      color:COLORS.grid\n    },\n    ticks:{\n      callback(value){\n        return fmt(value,0);\n      }\n    }\n  };\n\n}\n\nfunction line(label,data,color,fill=false){\n\n  return {\n    label,\n    data,\n    borderColor:color,\n    backgroundColor:color+\"20\",\n    fill,\n    tension:.3,\n    borderWidth:2.5,\n    pointRadius:3,\n    pointHoverRadius:6,\n    spanGaps:false\n  };\n\n}\n\nfunction stateColors(data){\n\n  return data.map(item=>\n    key(item.state)===\"melaka\"\n      ? COLORS.orange\n      : \"rgba(37,99,235,.75)\"\n  );\n\n}\n\nfunction stateTickColor(context){\n\n  return context.tick?.label===\"Melaka\"\n    ? COLORS.orange\n    : COLORS.muted;\n\n}\n\nfunction stateTickFont(context){\n\n  return {\n    weight:context.tick?.label===\"Melaka\"\n      ? \"bold\"\n      : \"normal\"\n  };\n\n}\n\n\n\/* =====================================================\n   SUMMARY CARDS\n===================================================== *\/\n\nfunction updateCards(){\n\n  const s = selected();\n\n  const current = melaka(s.year,s.strata);\n\n  const prev = previousYear(s.year,s.strata);\n\n  const previous = prev!==null\n    ? melaka(prev,s.strata)\n    : null;\n\n  const change =\n    valid(current) && valid(previous)\n      ? current-previous\n      : null;\n\n  const urban = melaka(s.year,\"urban\");\n\n  const rural = melaka(s.year,\"rural\");\n\n  const gap =\n    valid(urban) && valid(rural)\n      ? urban-rural\n      : null;\n\n  text(\"wa-stat-year\",s.year);\n\n  text(\"wa-stat-strata\",strataLabel(s.strata));\n\n  text(\"wa-stat-access\",pct(current));\n\n  text(\"wa-stat-change\",pp(change));\n\n  text(\n    \"wa-stat-change-sub\",\n    prev!==null\n      ? \"berbanding tahun \" + prev\n      : \"tiada tahun terdahulu\"\n  );\n\n  text(\"wa-stat-gap\",pp(gap));\n\n  show(\"wa-stats\");\n\n}\n\n\n\/* =====================================================\n   TREND CHARTS\n===================================================== *\/\n\nfunction buildTrend(){\n\n  const s = selected();\n\n  const years = yearsFor(s.strata);\n\n  const values = years.map(year=>\n    melaka(year,s.strata)\n  );\n\n  text(\n    \"wa-trend-sub\",\n    strataLabel(s.strata) +\n    \" \u00b7 semua tahun tersedia \u00b7 peratus (%)\"\n  );\n\n  const options = baseOptions();\n\n  options.plugins.legend.display = false;\n  options.plugins.tooltip = percentageTooltip();\n\n  options.scales = {\n    x:xScale(),\n    y:percentScale()\n  };\n\n  makeChart(\n    \"trend\",\n    \"wa-chart-trend\",\n    {\n      type:\"line\",\n      data:{\n        labels:years,\n        datasets:[\n          line(\n            strataLabel(s.strata),\n            values,\n            COLORS.blue,\n            true\n          )\n        ]\n      },\n      options\n    }\n  );\n\n\n  \/* REMAINING TO FULL ACCESS *\/\n\n  const remaining = values.map(value=>\n    valid(value)\n      ? 100-value\n      : null\n  );\n\n  const remainOptions = baseOptions();\n\n  remainOptions.plugins.legend.display = false;\n  remainOptions.plugins.tooltip = percentageTooltip();\n\n  remainOptions.scales = {\n    x:xScale(),\n    y:percentScale()\n  };\n\n  makeChart(\n    \"remaining\",\n    \"wa-chart-remaining\",\n    {\n      type:\"bar\",\n      data:{\n        labels:years,\n        datasets:[{\n          label:\"Baki kepada 100%\",\n          data:remaining,\n          backgroundColor:COLORS.orange,\n          borderRadius:6\n        }]\n      },\n      options:remainOptions\n    }\n  );\n\n\n  \/* ALL STRATA TREND *\/\n\n  const all = allYears();\n\n  const strataOptions = baseOptions();\n\n  strataOptions.plugins.tooltip = percentageTooltip();\n\n  strataOptions.scales = {\n    x:xScale(),\n    y:percentScale()\n  };\n\n  makeChart(\n    \"strataTrend\",\n    \"wa-chart-strata-trend\",\n    {\n      type:\"line\",\n      data:{\n        labels:all,\n        datasets:[\n          line(\n            \"Keseluruhan\",\n            all.map(y=>melaka(y,\"overall\")),\n            COLORS.blue\n          ),\n          line(\n            \"Bandar\",\n            all.map(y=>melaka(y,\"urban\")),\n            COLORS.green\n          ),\n          line(\n            \"Luar Bandar\",\n            all.map(y=>melaka(y,\"rural\")),\n            COLORS.orange\n          )\n        ]\n      },\n      options:strataOptions\n    }\n  );\n\n}\n\n\n\/* =====================================================\n   STRATA CHARTS\n===================================================== *\/\n\nfunction buildStrata(){\n\n  const s = selected();\n\n  const strata = [\n    \"overall\",\n    \"urban\",\n    \"rural\"\n  ];\n\n  const labels = strata.map(strataLabel);\n\n  const values = strata.map(item=>\n    melaka(s.year,item)\n  );\n\n  text(\n    \"wa-strata-sub\",\n    s.year +\n    \" \u00b7 kadar akses isi rumah Melaka\"\n  );\n\n  const options = baseOptions();\n\n  options.plugins.legend.display = false;\n  options.plugins.tooltip = percentageTooltip();\n\n  options.scales = {\n    x:xScale(),\n    y:percentScale()\n  };\n\n  makeChart(\n    \"strata\",\n    \"wa-chart-strata\",\n    {\n      type:\"bar\",\n      data:{\n        labels,\n        datasets:[{\n          label:\"Kadar Akses\",\n          data:values,\n          backgroundColor:[\n            COLORS.blue,\n            COLORS.green,\n            COLORS.orange\n          ],\n          borderRadius:8\n        }]\n      },\n      options\n    }\n  );\n\n\n  \/* STACKED ACCESS \/ REMAINING *\/\n\n  text(\n    \"wa-progress-sub\",\n    s.year +\n    \" \u00b7 akses dan baki kepada 100%\"\n  );\n\n  const progressOptions = baseOptions();\n\n  progressOptions.indexAxis = \"y\";\n\n  progressOptions.plugins.tooltip =\n    percentageTooltip(true);\n\n  progressOptions.scales = {\n    x:{\n      stacked:true,\n      min:0,\n      max:100,\n      ticks:{\n        callback(value){\n          return value+\"%\";\n        }\n      }\n    },\n    y:{\n      stacked:true,\n      grid:{\n        display:false\n      }\n    }\n  };\n\n  makeChart(\n    \"progress\",\n    \"wa-chart-progress\",\n    {\n      type:\"bar\",\n      data:{\n        labels,\n        datasets:[\n          {\n            label:\"Mempunyai Akses\",\n            data:values,\n            backgroundColor:COLORS.blue,\n            borderRadius:4\n          },\n          {\n            label:\"Baki kepada 100%\",\n            data:values.map(value=>\n              valid(value)\n                ? 100-value\n                : null\n            ),\n            backgroundColor:\"#cbd5e1\",\n            borderRadius:4\n          }\n        ]\n      },\n      options:progressOptions\n    }\n  );\n\n\n  \/* URBAN VS RURAL TREND *\/\n\n  const years = allYears();\n\n  const trendOptions = baseOptions();\n\n  trendOptions.plugins.tooltip =\n    percentageTooltip();\n\n  trendOptions.scales = {\n    x:xScale(),\n    y:percentScale()\n  };\n\n  makeChart(\n    \"urbanRural\",\n    \"wa-chart-urban-rural\",\n    {\n      type:\"line\",\n      data:{\n        labels:years,\n        datasets:[\n          line(\n            \"Bandar\",\n            years.map(y=>melaka(y,\"urban\")),\n            COLORS.green\n          ),\n          line(\n            \"Luar Bandar\",\n            years.map(y=>melaka(y,\"rural\")),\n            COLORS.orange\n          )\n        ]\n      },\n      options:trendOptions\n    }\n  );\n\n}\n\n\n\/* =====================================================\n   GAP CHARTS\n===================================================== *\/\n\nfunction buildGap(){\n\n  const s = selected();\n\n  const years = allYears();\n\n  const gaps = years.map(year=>{\n\n    const urban = melaka(year,\"urban\");\n\n    const rural = melaka(year,\"rural\");\n\n    if(!valid(urban) || !valid(rural)){\n      return null;\n    }\n\n    return urban-rural;\n\n  });\n\n  const gapOptions = baseOptions();\n\n  gapOptions.plugins.legend.display = false;\n  gapOptions.plugins.tooltip = ppTooltip();\n\n  gapOptions.scales = {\n    x:xScale(),\n    y:ppScale()\n  };\n\n  makeChart(\n    \"gap\",\n    \"wa-chart-gap\",\n    {\n      type:\"bar\",\n      data:{\n        labels:years,\n        datasets:[{\n          label:\"Jurang Bandar\u2013Luar Bandar\",\n          data:gaps,\n          backgroundColor:gaps.map(value=>\n            valid(value) && value>=0\n              ? COLORS.orange\n              : COLORS.blue\n          ),\n          borderRadius:6\n        }]\n      },\n      options:gapOptions\n    }\n  );\n\n\n  \/* GAP TO 100% *\/\n\n  const strata = [\n    \"overall\",\n    \"urban\",\n    \"rural\"\n  ];\n\n  const fullGaps = strata.map(item=>{\n\n    const value = melaka(s.year,item);\n\n    return valid(value)\n      ? 100-value\n      : null;\n\n  });\n\n  text(\n    \"wa-full-gap-sub\",\n    s.year +\n    \" \u00b7 baki mata peratusan kepada akses penuh\"\n  );\n\n  const fullOptions = baseOptions();\n\n  fullOptions.plugins.legend.display = false;\n  fullOptions.plugins.tooltip = ppTooltip();\n\n  fullOptions.scales = {\n    x:xScale(),\n    y:ppScale()\n  };\n\n  makeChart(\n    \"fullGap\",\n    \"wa-chart-full-gap\",\n    {\n      type:\"bar\",\n      data:{\n        labels:strata.map(strataLabel),\n        datasets:[{\n          label:\"Baki kepada 100%\",\n          data:fullGaps,\n          backgroundColor:[\n            COLORS.blue,\n            COLORS.green,\n            COLORS.orange\n          ],\n          borderRadius:7\n        }]\n      },\n      options:fullOptions\n    }\n  );\n\n\n  \/* GAP TREND *\/\n\n  const trendOptions = baseOptions();\n\n  trendOptions.plugins.legend.display = false;\n  trendOptions.plugins.tooltip = ppTooltip();\n\n  trendOptions.scales = {\n    x:xScale(),\n    y:ppScale()\n  };\n\n  makeChart(\n    \"gapTrend\",\n    \"wa-chart-gap-trend\",\n    {\n      type:\"line\",\n      data:{\n        labels:years,\n        datasets:[\n          line(\n            \"Jurang Bandar\u2013Luar Bandar\",\n            gaps,\n            COLORS.purple,\n            true\n          )\n        ]\n      },\n      options:trendOptions\n    }\n  );\n\n}\n\n\n\/* =====================================================\n   CHANGE CHARTS\n===================================================== *\/\n\nfunction buildChange(){\n\n  const s = selected();\n\n  const years = yearsFor(s.strata);\n\n  const labels = [];\n  const changes = [];\n\n  for(let i=1;i<years.length;i++){\n\n    const current = melaka(\n      years[i],\n      s.strata\n    );\n\n    const previous = melaka(\n      years[i-1],\n      s.strata\n    );\n\n    labels.push(\n      years[i] + \" vs \" + years[i-1]\n    );\n\n    changes.push(\n      valid(current) &#038;&#038; valid(previous)\n        ? current-previous\n        : null\n    );\n\n  }\n\n  const options = baseOptions();\n\n  options.plugins.legend.display = false;\n  options.plugins.tooltip = ppTooltip();\n\n  options.scales = {\n    x:xScale(),\n    y:ppScale()\n  };\n\n  makeChart(\n    \"change\",\n    \"wa-chart-change\",\n    {\n      type:\"bar\",\n      data:{\n        labels,\n        datasets:[{\n          label:\"Perubahan Tahunan\",\n          data:changes,\n          backgroundColor:changes.map(value=>\n            valid(value) && value>=0\n              ? COLORS.green\n              : COLORS.red\n          ),\n          borderRadius:6\n        }]\n      },\n      options\n    }\n  );\n\n\n  \/* BASE YEAR CHANGE *\/\n\n  const baseYear = years.length\n    ? years[0]\n    : null;\n\n  const baseValue = baseYear!==null\n    ? melaka(baseYear,s.strata)\n    : null;\n\n  const baseChanges = years.map(year=>{\n\n    const value = melaka(year,s.strata);\n\n    return valid(value) && valid(baseValue)\n      ? value-baseValue\n      : null;\n\n  });\n\n  text(\n    \"wa-base-sub\",\n    baseYear!==null\n      ? \"Tahun asas \" + baseYear +\n        \" \u00b7 perubahan dalam mata peratusan\"\n      : \"Tiada tahun asas tersedia\"\n  );\n\n  const baseOptionsChart = baseOptions();\n\n  baseOptionsChart.plugins.legend.display = false;\n  baseOptionsChart.plugins.tooltip = ppTooltip();\n\n  baseOptionsChart.scales = {\n    x:xScale(),\n    y:ppScale()\n  };\n\n  makeChart(\n    \"base\",\n    \"wa-chart-base\",\n    {\n      type:\"line\",\n      data:{\n        labels:years,\n        datasets:[\n          line(\n            \"Perubahan daripada Tahun Asas\",\n            baseChanges,\n            COLORS.purple,\n            true\n          )\n        ]\n      },\n      options:baseOptionsChart\n    }\n  );\n\n\n  \/* CHANGE BY STRATA *\/\n\n  const all = allYears();\n\n  const strata = [\n    \"overall\",\n    \"urban\",\n    \"rural\"\n  ];\n\n  const datasets = strata.map((item,index)=>{\n\n    const colors = [\n      COLORS.blue,\n      COLORS.green,\n      COLORS.orange\n    ];\n\n    return {\n      label:strataLabel(item),\n      data:all.map(year=>{\n\n        const prev = previousYear(year,item);\n\n        if(prev===null){\n          return null;\n        }\n\n        const current = melaka(year,item);\n        const previous = melaka(prev,item);\n\n        return valid(current) && valid(previous)\n          ? current-previous\n          : null;\n\n      }),\n      backgroundColor:colors[index],\n      borderRadius:4\n    };\n\n  });\n\n  const strataOptions = baseOptions();\n\n  strataOptions.plugins.tooltip = ppTooltip();\n\n  strataOptions.scales = {\n    x:xScale(),\n    y:ppScale()\n  };\n\n  makeChart(\n    \"strataChange\",\n    \"wa-chart-strata-change\",\n    {\n      type:\"bar\",\n      data:{\n        labels:all,\n        datasets\n      },\n      options:strataOptions\n    }\n  );\n\n}\n\n\n\/* =====================================================\n   STATE CHARTS\n===================================================== *\/\n\nfunction buildState(){\n\n  const s = selected();\n\n  const data = stateData(\n    s.year,\n    s.strata\n  )\n  .sort((a,b)=>b.value-a.value);\n\n  text(\n    \"wa-state-sub\",\n    s.year +\n    \" \u00b7 \" +\n    strataLabel(s.strata) +\n    \" \u00b7 Melaka ditonjolkan dalam warna oren\"\n  );\n\n  const options = baseOptions();\n\n  options.plugins.legend.display = false;\n  options.plugins.tooltip = percentageTooltip();\n\n  options.scales = {\n    x:{\n      grid:{\n        display:false\n      },\n      ticks:{\n        maxRotation:45,\n        color:stateTickColor,\n        font:stateTickFont\n      }\n    },\n    y:percentScale()\n  };\n\n  makeChart(\n    \"state\",\n    \"wa-chart-state\",\n    {\n      type:\"bar\",\n      data:{\n        labels:data.map(item=>item.state),\n        datasets:[{\n          label:\"Kadar Akses\",\n          data:data.map(item=>item.value),\n          backgroundColor:stateColors(data),\n          borderRadius:6\n        }]\n      },\n      options\n    }\n  );\n\n\n  \/* GAP FROM 100 *\/\n\n  const gapData = data.map(item=>({\n    state:item.state,\n    value:100-item.value\n  }));\n\n  text(\n    \"wa-state-gap-sub\",\n    s.year +\n    \" \u00b7 \" +\n    strataLabel(s.strata) +\n    \" \u00b7 baki mata peratusan kepada 100%\"\n  );\n\n  const gapOptions = baseOptions();\n\n  gapOptions.plugins.legend.display = false;\n  gapOptions.plugins.tooltip = ppTooltip();\n\n  gapOptions.scales = {\n    x:{\n      grid:{\n        display:false\n      },\n      ticks:{\n        maxRotation:45,\n        color:stateTickColor,\n        font:stateTickFont\n      }\n    },\n    y:ppScale()\n  };\n\n  makeChart(\n    \"stateGap\",\n    \"wa-chart-state-gap\",\n    {\n      type:\"bar\",\n      data:{\n        labels:gapData.map(item=>item.state),\n        datasets:[{\n          label:\"Baki kepada 100%\",\n          data:gapData.map(item=>item.value),\n          backgroundColor:stateColors(gapData),\n          borderRadius:6\n        }]\n      },\n      options:gapOptions\n    }\n  );\n\n}\n\n\n\/* =====================================================\n   RANKING\n===================================================== *\/\n\nfunction buildRanking(){\n\n  const s = selected();\n\n  const data = stateData(\n    s.year,\n    s.strata\n  )\n  .sort((a,b)=>b.value-a.value);\n\n  const limit = Math.min(5,data.length);\n\n  const high = data.slice(0,limit);\n\n  const low = data\n    .slice(-limit)\n    .sort((a,b)=>a.value-b.value);\n\n  text(\n    \"wa-high-sub\",\n    s.year +\n    \" \u00b7 \" +\n    strataLabel(s.strata)\n  );\n\n  text(\n    \"wa-low-sub\",\n    s.year +\n    \" \u00b7 \" +\n    strataLabel(s.strata)\n  );\n\n  function rankingChart(name,id,dataset,color){\n\n    const options = baseOptions();\n\n    options.plugins.legend.display = false;\n    options.plugins.tooltip = percentageTooltip();\n\n    options.scales = {\n      x:{\n        grid:{\n          display:false\n        },\n        ticks:{\n          maxRotation:45,\n          color:stateTickColor,\n          font:stateTickFont\n        }\n      },\n      y:percentScale()\n    };\n\n    makeChart(\n      name,\n      id,\n      {\n        type:\"bar\",\n        data:{\n          labels:dataset.map(item=>item.state),\n          datasets:[{\n            label:\"Kadar Akses\",\n            data:dataset.map(item=>item.value),\n            backgroundColor:dataset.map(item=>\n              key(item.state)===\"melaka\"\n                ? COLORS.orange\n                : color\n            ),\n            borderRadius:7\n          }]\n        },\n        options\n      }\n    );\n\n  }\n\n  rankingChart(\n    \"high\",\n    \"wa-chart-high\",\n    high,\n    COLORS.green\n  );\n\n  rankingChart(\n    \"low\",\n    \"wa-chart-low\",\n    low,\n    COLORS.red\n  );\n\n}\n\n\n\/* =====================================================\n   COMPARISON\n===================================================== *\/\n\nfunction buildComparison(){\n\n  const s = selected();\n\n  const years = yearsFor(s.strata);\n\n  const melakaValues = years.map(year=>\n    melaka(year,s.strata)\n  );\n\n  const compareValues = years.map(year=>\n    access(s.compare,year,s.strata)\n  );\n\n  text(\n    \"wa-comparison-sub\",\n    \"Melaka berbanding \" +\n    s.compare +\n    \" \u00b7 \" +\n    strataLabel(s.strata)\n  );\n\n  const options = baseOptions();\n\n  options.interaction = {\n    mode:\"index\",\n    intersect:false\n  };\n\n  options.plugins.tooltip = percentageTooltip();\n\n  options.scales = {\n    x:xScale(),\n    y:percentScale()\n  };\n\n  makeChart(\n    \"comparison\",\n    \"wa-chart-comparison\",\n    {\n      type:\"line\",\n      data:{\n        labels:years,\n        datasets:[\n          line(\n            \"Melaka\",\n            melakaValues,\n            COLORS.orange\n          ),\n          line(\n            s.compare,\n            compareValues,\n            COLORS.blue\n          )\n        ]\n      },\n      options\n    }\n  );\n\n\n  \/* DIFFERENCE *\/\n\n  const gaps = years.map(year=>{\n\n    const m = melaka(year,s.strata);\n\n    const c = access(\n      s.compare,\n      year,\n      s.strata\n    );\n\n    return valid(m) && valid(c)\n      ? m-c\n      : null;\n\n  });\n\n  const gapOptions = baseOptions();\n\n  gapOptions.plugins.legend.display = false;\n  gapOptions.plugins.tooltip = ppTooltip();\n\n  gapOptions.scales = {\n    x:xScale(),\n    y:ppScale()\n  };\n\n  makeChart(\n    \"comparisonGap\",\n    \"wa-chart-comparison-gap\",\n    {\n      type:\"bar\",\n      data:{\n        labels:years,\n        datasets:[{\n          label:\"Melaka - \" + s.compare,\n          data:gaps,\n          backgroundColor:gaps.map(value=>\n            valid(value) && value>=0\n              ? COLORS.green\n              : COLORS.red\n          ),\n          borderRadius:6\n        }]\n      },\n      options:gapOptions\n    }\n  );\n\n\n  \/* STRATA COMPARISON *\/\n\n  const strata = [\n    \"overall\",\n    \"urban\",\n    \"rural\"\n  ];\n\n  text(\n    \"wa-strata-comparison-sub\",\n    s.year +\n    \" \u00b7 Melaka berbanding \" +\n    s.compare +\n    \" mengikut strata\"\n  );\n\n  const strataOptions = baseOptions();\n\n  strataOptions.plugins.tooltip = percentageTooltip();\n\n  strataOptions.scales = {\n    x:xScale(),\n    y:percentScale()\n  };\n\n  makeChart(\n    \"strataComparison\",\n    \"wa-chart-strata-comparison\",\n    {\n      type:\"bar\",\n      data:{\n        labels:strata.map(strataLabel),\n        datasets:[\n          {\n            label:\"Melaka\",\n            data:strata.map(item=>\n              melaka(s.year,item)\n            ),\n            backgroundColor:COLORS.orange,\n            borderRadius:5\n          },\n          {\n            label:s.compare,\n            data:strata.map(item=>\n              access(s.compare,s.year,item)\n            ),\n            backgroundColor:COLORS.blue,\n            borderRadius:5\n          }\n        ]\n      },\n      options:strataOptions\n    }\n  );\n\n}\n\n\n\/* =====================================================\n   TAB SYSTEM\n===================================================== *\/\n\nconst tabs = [\n  \"trend\",\n  \"strata\",\n  \"gap\",\n  \"change\",\n  \"state\",\n  \"ranking\",\n  \"comparison\"\n];\n\nfunction activeTab(){\n\n  const button = root.querySelector(\n    \".wa-tab-btn.active\"\n  );\n\n  return button\n    ? button.dataset.tab\n    : \"trend\";\n\n}\n\nfunction redraw(){\n\n  if(!rows.length){\n    return;\n  }\n\n  updateCards();\n\n  const tab = activeTab();\n\n  if(tab===\"trend\"){\n    buildTrend();\n  }\n\n  if(tab===\"strata\"){\n    buildStrata();\n  }\n\n  if(tab===\"gap\"){\n    buildGap();\n  }\n\n  if(tab===\"change\"){\n    buildChange();\n  }\n\n  if(tab===\"state\"){\n    buildState();\n  }\n\n  if(tab===\"ranking\"){\n    buildRanking();\n  }\n\n  if(tab===\"comparison\"){\n    buildComparison();\n  }\n\n}\n\nroot.querySelectorAll(\".wa-tab-btn\")\n  .forEach(button=>{\n\n    button.addEventListener(\"click\",()=>{\n\n      root.querySelectorAll(\".wa-tab-btn\")\n        .forEach(item=>{\n\n          item.classList.remove(\"active\");\n\n          item.setAttribute(\n            \"aria-selected\",\n            \"false\"\n          );\n\n        });\n\n      button.classList.add(\"active\");\n\n      button.setAttribute(\n        \"aria-selected\",\n        \"true\"\n      );\n\n      tabs.forEach(tab=>{\n        hide(\"wa-tab-\"+tab);\n      });\n\n      show(\"wa-tab-\"+button.dataset.tab);\n\n      redraw();\n\n    });\n\n  });\n\n\n\/* =====================================================\n   FILTER EVENTS\n===================================================== *\/\n\nel(\"wa-year\").addEventListener(\n  \"change\",\n  redraw\n);\n\nel(\"wa-compare\").addEventListener(\n  \"change\",\n  redraw\n);\n\nel(\"wa-strata\").addEventListener(\n  \"change\",\n  ()=>{\n\n    fillYears();\n\n    redraw();\n\n  }\n);\n\n\n\/* =====================================================\n   FETCH DATA\n===================================================== *\/\n\nfetch(API)\n\n.then(response=>{\n\n  if(!response.ok){\n\n    throw new Error(\n      \"HTTP \" + response.status\n    );\n\n  }\n\n  return response.json();\n\n})\n\n.then(data=>{\n\n  const records = Array.isArray(data)\n    ? data\n    : Array.isArray(data?.data)\n      ? data.data\n      : [];\n\n  rows = records\n    .map(row=>({\n\n      date:row.date,\n\n      year:yearOf(row.date),\n\n      state:String(row.state || \"\").trim(),\n\n      stateKey:key(row.state),\n\n      strata:key(row.strata),\n\n      proportion:number(row.proportion)\n\n    }))\n    .filter(row=>\n\n      valid(row.year) &&\n\n      row.stateKey!==\"\" &&\n\n      [\n        \"overall\",\n        \"urban\",\n        \"rural\"\n      ].includes(row.strata) &&\n\n      valid(row.proportion) &&\n\n      row.proportion>=0 &&\n\n      row.proportion<=100\n\n    );\n\n  melakaRows = rows.filter(row=>\n    row.stateKey===\"melaka\"\n  );\n\n  if(!melakaRows.length){\n\n    throw new Error(\n      \"Tiada rekod Melaka ditemui dalam dataset.\"\n    );\n\n  }\n\n  if(!yearsFor(\"overall\").length){\n\n    if(yearsFor(\"urban\").length){\n\n      el(\"wa-strata\").value=\"urban\";\n\n    }else if(yearsFor(\"rural\").length){\n\n      el(\"wa-strata\").value=\"rural\";\n\n    }\n\n  }\n\n  fillYears();\n\n  fillCompare();\n\n  hide(\"wa-loading\");\n\n  show(\"wa-dashboard\");\n\n  redraw();\n\n})\n\n.catch(error=>{\n\n  const loading = el(\"wa-loading\");\n\n  loading.innerHTML=\"\";\n\n  const title = document.createElement(\"strong\");\n\n  title.style.color=COLORS.red;\n\n  title.textContent=\"Gagal memuatkan data\";\n\n  const message=document.createElement(\"p\");\n\n  message.textContent=error.message;\n\n  loading.appendChild(title);\n\n  loading.appendChild(message);\n\n});\n\n}\n\nif(document.readyState===\"loading\"){\n\n  document.addEventListener(\n    \"DOMContentLoaded\",\n    start,\n    {once:true}\n  );\n\n}else{\n\n  start();\n\n}\n\n})();\n<\/script>\n\n\n\n\n\n\n\n\n<!-- ==========================================\n     SUMBER DATA OPENDOSM\n     Simplified Professional Version\n========================================== -->\n\n<style>\n\n#opendosm-source,\n#opendosm-source * {\n    box-sizing: border-box;\n}\n\n#opendosm-source {\n\n    --source-primary: #2563eb;\n    --source-primary-dark: #1d4ed8;\n    --source-light: #eff6ff;\n    --source-border: #dbeafe;\n    --source-text: #0f172a;\n    --source-muted: #64748b;\n\n    width: 100%;\n    max-width: 1200px;\n    margin: 24px auto;\n\n    font-family: Arial, sans-serif;\n    color: var(--source-text);\n\n}\n\n\/* MAIN CARD *\/\n\n#opendosm-source .source-card {\n\n    background: #ffffff;\n\n    border: 1px solid var(--source-border);\n    border-radius: 18px;\n\n    box-shadow: 0 8px 28px rgba(15,23,42,0.05);\n\n    overflow: hidden;\n\n    transition: box-shadow .25s ease;\n\n}\n\n#opendosm-source .source-card:hover {\n\n    box-shadow: 0 12px 32px rgba(15,23,42,0.09);\n\n}\n\n\/* HEADER *\/\n\n#opendosm-source .source-header {\n\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n\n    gap: 20px;\n\n    padding: 22px 25px;\n\n    flex-wrap: wrap;\n\n}\n\n\/* LEFT SECTION *\/\n\n#opendosm-source .source-left {\n\n    display: flex;\n    align-items: center;\n\n    gap: 15px;\n\n    min-width: 0;\n\n}\n\n\/* ICON *\/\n\n#opendosm-source .source-icon {\n\n    width: 52px;\n    height: 52px;\n\n    flex-shrink: 0;\n\n    display: flex;\n    align-items: center;\n    justify-content: center;\n\n    background: var(--source-light);\n\n    border: 1px solid var(--source-border);\n    border-radius: 14px;\n\n    color: var(--source-primary);\n\n}\n\n#opendosm-source .source-icon svg {\n\n    width: 27px;\n    height: 27px;\n\n}\n\n\/* TEXT *\/\n\n#opendosm-source .source-label {\n\n    font-size: 11px;\n    font-weight: 800;\n\n    text-transform: uppercase;\n    letter-spacing: 1px;\n\n    color: var(--source-primary);\n\n    margin-bottom: 5px;\n\n}\n\n#opendosm-source .source-title {\n\n    font-size: 19px;\n    font-weight: 800;\n\n    line-height: 1.4;\n\n    margin: 0;\n\n}\n\n#opendosm-source .source-description {\n\n    font-size: 13px;\n\n    color: var(--source-muted);\n\n    line-height: 1.6;\n\n    margin-top: 5px;\n\n}\n\n\/* ACTION BUTTON *\/\n\n#opendosm-source .source-actions {\n\n    display: flex;\n    align-items: center;\n\n}\n\n#opendosm-source .source-btn {\n\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n\n    gap: 10px;\n\n    min-height: 44px;\n\n    padding: 12px 20px;\n\n    border-radius: 10px;\n\n    font-size: 13px;\n    font-weight: 700;\n\n    text-decoration: none;\n\n    cursor: pointer;\n\n    transition: all .2s ease;\n\n    font-family: inherit;\n\n}\n\n#opendosm-source .source-btn svg {\n\n    width: 16px;\n    height: 16px;\n\n}\n\n#opendosm-source .source-btn-primary {\n\n    background: var(--source-primary);\n\n    color: #ffffff;\n\n    border: 1px solid var(--source-primary);\n\n}\n\n#opendosm-source .source-btn-primary:hover {\n\n    background: var(--source-primary-dark);\n\n    color: #ffffff;\n\n    transform: translateY(-2px);\n\n    box-shadow: 0 5px 14px rgba(37,99,235,0.20);\n\n}\n\n#opendosm-source .source-btn-primary:focus-visible {\n\n    outline: 3px solid #93c5fd;\n    outline-offset: 3px;\n\n}\n\n\/* FOOTER *\/\n\n#opendosm-source .source-footer {\n\n    display: flex;\n\n    align-items: center;\n    justify-content: space-between;\n\n    flex-wrap: wrap;\n\n    gap: 10px;\n\n    padding: 15px 25px;\n\n    border-top: 1px solid var(--source-border);\n\n    background: #f8fafc;\n\n    font-size: 11px;\n\n    color: var(--source-muted);\n\n}\n\n#opendosm-source .source-footer a {\n\n    color: var(--source-primary);\n\n    font-weight: 700;\n\n    text-decoration: none;\n\n}\n\n#opendosm-source .source-footer a:hover {\n\n    text-decoration: underline;\n\n}\n\n\/* RESPONSIVE *\/\n\n@media(max-width:768px) {\n\n    #opendosm-source .source-header {\n\n        padding: 20px;\n\n    }\n\n    #opendosm-source .source-actions {\n\n        width: 100%;\n\n    }\n\n    #opendosm-source .source-btn {\n\n        width: 100%;\n\n    }\n\n    #opendosm-source .source-footer {\n\n        padding: 15px 20px;\n\n    }\n\n}\n\n@media(max-width:480px) {\n\n    #opendosm-source .source-left {\n\n        align-items: flex-start;\n\n    }\n\n    #opendosm-source .source-title {\n\n        font-size: 17px;\n\n    }\n\n    #opendosm-source .source-description {\n\n        font-size: 12px;\n\n    }\n\n    #opendosm-source .source-footer {\n\n        flex-direction: column;\n        align-items: flex-start;\n\n    }\n\n}\n\n<\/style>\n\n\n<!-- ==========================================\n     HTML STRUCTURE\n========================================== -->\n\n<div id=\"opendosm-source\">\n\n    <div class=\"source-card\">\n\n\n        <!-- HEADER -->\n\n        <div class=\"source-header\">\n\n\n            <!-- LEFT: SOURCE INFORMATION -->\n\n            <div class=\"source-left\">\n\n\n                <!-- DATABASE ICON -->\n\n                <div class=\"source-icon\">\n\n                    <svg\n                        xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                        fill=\"none\"\n                        viewBox=\"0 0 24 24\"\n                        stroke=\"currentColor\"\n                        stroke-width=\"1.8\"\n                        aria-hidden=\"true\">\n\n                        <ellipse\n                            cx=\"12\"\n                            cy=\"5\"\n                            rx=\"9\"\n                            ry=\"3\">\n                        <\/ellipse>\n\n                        <path\n                            d=\"M3 5v7c0 1.7 4 3 9 3s9-1.3 9-3V5\">\n                        <\/path>\n\n                        <path\n                            d=\"M3 12v7c0 1.7 4 3 9 3s9-1.3 9-3v-7\">\n                        <\/path>\n\n                    <\/svg>\n\n                <\/div>\n\n\n                <!-- SOURCE TEXT -->\n\n                <div>\n\n                    <div class=\"source-label\">\n                        Sumber Data Rasmi\n                    <\/div>\n\n                    <h3 class=\"source-title\">\n                        OpenDOSM\n                    <\/h3>\n\n                    <div class=\"source-description\">\n                        Portal Data Terbuka Jabatan Perangkaan Malaysia\n                    <\/div>\n\n                <\/div>\n\n\n            <\/div>\n\n\n\n            <!-- RIGHT: VISIT BUTTON -->\n\n            <div class=\"source-actions\">\n\n                <a\n                    class=\"source-btn source-btn-primary\"\n                    href=\"https:\/\/open.dosm.gov.my\/\"\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\">\n\n                    Lawati OpenDOSM\n\n                    <svg\n                        xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n                        fill=\"none\"\n                        viewBox=\"0 0 24 24\"\n                        stroke=\"currentColor\"\n                        stroke-width=\"2\"\n                        aria-hidden=\"true\">\n\n                        <path\n                            stroke-linecap=\"round\"\n                            stroke-linejoin=\"round\"\n                            d=\"M7 17L17 7M8 7h9v9\">\n                        <\/path>\n\n                    <\/svg>\n\n                <\/a>\n\n            <\/div>\n\n\n        <\/div>\n\n\n\n        <!-- FOOTER -->\n\n        <div class=\"source-footer\">\n\n\n            <span>\n                Sumber: Jabatan Perangkaan Malaysia (DOSM)\n            <\/span>\n\n\n            <a\n                href=\"https:\/\/open.dosm.gov.my\/\"\n                target=\"_blank\"\n                rel=\"noopener noreferrer\">\n\n                open.dosm.gov.my \u2197\n\n            <\/a>\n\n\n        <\/div>\n\n\n    <\/div>\n\n<\/div>\n\n\n\n\n\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"pagelayer_contact_templates":[],"_pagelayer_content":"","footnotes":""},"class_list":["post-35","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"http:\/\/itpmelaka.my\/mdic\/wp-json\/wp\/v2\/pages\/35","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/itpmelaka.my\/mdic\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"http:\/\/itpmelaka.my\/mdic\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"http:\/\/itpmelaka.my\/mdic\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/itpmelaka.my\/mdic\/wp-json\/wp\/v2\/comments?post=35"}],"version-history":[{"count":50,"href":"http:\/\/itpmelaka.my\/mdic\/wp-json\/wp\/v2\/pages\/35\/revisions"}],"predecessor-version":[{"id":551,"href":"http:\/\/itpmelaka.my\/mdic\/wp-json\/wp\/v2\/pages\/35\/revisions\/551"}],"wp:attachment":[{"href":"http:\/\/itpmelaka.my\/mdic\/wp-json\/wp\/v2\/media?parent=35"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}