{"id":2805,"date":"2026-06-16T16:23:05","date_gmt":"2026-06-16T14:23:05","guid":{"rendered":"https:\/\/xpeng.guru\/lineup\/registrations\/"},"modified":"2026-08-07T18:41:06","modified_gmt":"2026-08-07T16:41:06","slug":"registrations","status":"publish","type":"lineup","link":"https:\/\/xpeng.guru\/da\/lineup\/registrations\/","title":{"rendered":"Registreringsstatistik"},"content":{"rendered":"        <div class=\"xg-registrations-dashboard xg-youtube-index\" data-xg-registrations>\r\n        <div class=\"xg-youtube-index__layout\">\r\n            <div class=\"xg-youtube-index__backdrop\" data-filter-close hidden><\/div>\r\n            <aside class=\"xg-youtube-index__sidebar\" id=\"xg-reg-filters\" aria-label=\"Filtrer data\">\r\n                <div class=\"xg-youtube-index__sidebar-head\">\r\n                    <h2 class=\"xg-youtube-index__sidebar-title\">Filtre<\/h2>\r\n                    <button type=\"button\" class=\"xg-youtube-index__sidebar-close\" data-filter-close aria-label=\"Luk filtre\">&times;<\/button>\r\n                <\/div>\r\n                \r\n                <details class=\"xg-youtube-index__section\" data-xg-reg-accordion open>\r\n                    <summary class=\"xg-youtube-index__section-title\">\u00c5r<\/summary>\r\n                    <div class=\"xg-youtube-index__checklist\" id=\"xgRegYearFilters\">\r\n                        <!-- Populated by JS -->\r\n                    <\/div>\r\n                <\/details>\r\n\r\n                <details class=\"xg-youtube-index__section\" data-xg-reg-accordion>\r\n                    <summary class=\"xg-youtube-index__section-title\">Model<\/summary>\r\n                    <div class=\"xg-youtube-index__checklist\" id=\"xgRegModelFilters\">\r\n                        <!-- Populated by JS -->\r\n                    <\/div>\r\n                <\/details>\r\n\r\n                <div class=\"xg-youtube-index__sidebar-foot\">\r\n                    <button type=\"button\" class=\"xg-youtube-index__reset\" id=\"xgRegResetBtn\">Nulstil filtre<\/button>\r\n                    <button type=\"button\" class=\"xg-youtube-index__apply\" data-filter-close>Vis data<\/button>\r\n                <\/div>\r\n            <\/aside>\r\n\r\n            <div class=\"xg-youtube-index__chips-row\">\r\n                <div class=\"xg-youtube-index__chips\" id=\"xgRegChips\" hidden><\/div>\r\n                <button type=\"button\" class=\"xg-youtube-index__reset xg-youtube-index__reset--chips-row\" id=\"xgRegResetChipsBtn\" hidden>Nulstil<\/button>\r\n            <\/div>\r\n            \r\n            <div class=\"xg-youtube-index__toolbar\" style=\"margin-bottom: 1rem;\">\r\n                <button type=\"button\" class=\"xg-youtube-index__open-filters\" data-filter-open>\r\n                    Filtre <span class=\"xg-youtube-index__filter-badge\" id=\"xgRegBadge\" hidden><\/span>\r\n                <\/button>\r\n            <\/div>\r\n\r\n            <div class=\"xg-youtube-index__main\">\r\n                <div class=\"xg-registrations-charts\" style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; margin-bottom: 2rem;\">\r\n                    <div class=\"xg-registrations-chart-container xg-muted-panel\" style=\"padding: 1.5rem; border-radius: 8px; background: var(--theme-palette-color-6); border: 1px solid var(--theme-palette-color-5);\">\r\n                        <h3 style=\"margin-top: 0; font-size: 1.2rem;\">Registreringer over tid<\/h3>\r\n                        <div style=\"position: relative; height: 300px;\">\r\n                            <canvas id=\"xpengTrendChart\"><\/canvas>\r\n                        <\/div>\r\n                    <\/div>\r\n                    <div class=\"xg-registrations-chart-container xg-muted-panel\" style=\"padding: 1.5rem; border-radius: 8px; background: var(--theme-palette-color-6); border: 1px solid var(--theme-palette-color-5);\">\r\n                        <h3 style=\"margin-top: 0; font-size: 1.2rem;\">Fordeling p\u00e5 modeller<\/h3>\r\n                        <div style=\"position: relative; height: 300px;\">\r\n                            <canvas id=\"xpengModelChart\"><\/canvas>\r\n                        <\/div>\r\n                    <\/div>\r\n                <\/div>\r\n\r\n                <div class=\"xg-registrations-table-container xg-muted-panel\" style=\"padding: 1.5rem; border-radius: 8px; background: var(--theme-palette-color-6); border: 1px solid var(--theme-palette-color-5); overflow-x: auto;\">\r\n                    <h3 style=\"margin-top: 0; font-size: 1.2rem;\">Data<\/h3>\r\n                                        <p style=\"margin:-0.15rem 0 0.85rem;font-size:0.875rem;opacity:0.75;\">Seneste hentning: 7. august 2026<\/p>\r\n                                        <div id=\"xgRegTableRoot\"><\/div>\r\n                <\/div>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <script>\r\n    const dataJson = {\"fetched_at\":\"2026-08-07\",\"source_zip\":\"ESStatistikListeModtag-20260731-134146.zip\",\"count_method\":\"unique_vin_first_registration\",\"months\":[\"2021-11\",\"2021-12\",\"2022-01\",\"2022-02\",\"2022-03\",\"2022-04\",\"2022-05\",\"2022-06\",\"2022-07\",\"2022-08\",\"2022-09\",\"2022-10\",\"2022-11\",\"2022-12\",\"2023-01\",\"2023-02\",\"2023-03\",\"2023-04\",\"2023-05\",\"2023-06\",\"2023-07\",\"2023-08\",\"2023-09\",\"2023-10\",\"2023-11\",\"2023-12\",\"2024-01\",\"2024-02\",\"2024-03\",\"2024-04\",\"2024-05\",\"2024-06\",\"2024-07\",\"2024-08\",\"2024-09\",\"2024-10\",\"2024-11\",\"2024-12\",\"2025-01\",\"2025-02\",\"2025-03\",\"2025-04\",\"2025-05\",\"2025-06\",\"2025-07\",\"2025-08\",\"2025-09\",\"2025-10\",\"2025-11\",\"2025-12\",\"2026-01\",\"2026-02\",\"2026-03\",\"2026-04\",\"2026-05\",\"2026-06\",\"2026-07\"],\"models\":{\"G6\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,0,23,136,225,329,383,262,139,144,228,288,248,141,75,26,148,248,299,501,212,306,277,459,468,506,464],\"G9\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2,3,29,107,190,279,91,104,143,203,131,145,160,111,78,84,74,88,39,47,64,54,53,66,28,32,24,91,76,141,43,47,70,63,101,122,71],\"P7+\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,0,67],\"P7\":[2,0,1,1,0,0,0,0,0,0,0,4,0,1,0,0,0,0,0,10,5,28,11,6,5,12,5,21,30,17,3,2,19,9,9,16,27,9,0,0,5,18,13,7,4,2,0,2,0,27,0,0,0,0,0,0,0],\"P5\":[1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}};\r\n    const regI18n = {\"unavailable\":\"Registreringsdata er ikke tilg\\u00e6ngelige i \\u00f8jeblikket.\",\"filter_aria\":\"Filtrer data\",\"filters\":\"Filtre\",\"close_filters\":\"Luk filtre\",\"year\":\"\\u00c5r\",\"model\":\"Model\",\"reset_filters\":\"Nulstil filtre\",\"show_data\":\"Vis data\",\"reset\":\"Nulstil\",\"chart_trend\":\"Registreringer over tid\",\"chart_models\":\"Fordeling p\\u00e5 modeller\",\"data_table\":\"Data\",\"month\":\"M\\u00e5ned\",\"total\":\"I alt\",\"no_data\":\"Ingen data matcher filtrene.\",\"fetched\":\"Seneste hentning: %s\",\"fetched_source\":\"Registreringsstatistik seneste hentet: %s\"};\r\n    const regChartModelOrder = [\"G6\",\"G9\",\"P7+\",\"P7\",\"P5\"];\r\n    const regChartConfig = {\"display_order\":[\"G6\",\"G9\",\"L03\",\"P7+\",\"P7\",\"G9L\",\"P5\"],\"chart_colors\":{\"G6\":\"#ff6900\",\"G9\":\"#00ba5d\",\"L03\":\"#ff944d\",\"P7+\":\"#cc5400\",\"G9L\":\"#00994c\",\"P7\":\"#808080\",\"P5\":\"#595959\",\"X9\":\"#707070\",\"GX\":\"#5c5c5c\"},\"auto_colors\":[\"#00994c\",\"#6ee7a3\",\"#007a3d\",\"#ff944d\",\"#cc5400\",\"#994000\",\"#b3b3b3\",\"#707070\"]};\r\n\/\/ CSS variables helper\r\nconst getCSSVar = (name) => {\r\n    return window.getComputedStyle(document.documentElement).getPropertyValue(name).trim();\r\n};\r\n\r\nconst gridColor = () => getCSSVar('--theme-palette-color-5');\r\nconst textColor = () => getCSSVar('--theme-palette-color-3');\r\nconst titleColor = () => getCSSVar('--theme-palette-color-4');\r\n\r\nconst chartPanelIsDark = () => {\r\n    const panel = (getCSSVar('--theme-palette-color-6') || '#f3f3f3').replace('#', '');\r\n    if (panel.length !== 6) return false;\r\n    const lum = (parseInt(panel.slice(0, 2), 16) + parseInt(panel.slice(2, 4), 16) + parseInt(panel.slice(4, 6), 16)) \/ 3;\r\n    return lum < 128;\r\n};\r\n\r\n\/\/ Brand green\/orange + grey legacy; auto_colors for models not yet in chart_colors config.\r\nconst chartLineDashes = [[], [12, 6], [4, 4], [12, 4, 3, 4]];\r\n\r\nconst resolveChartColor = (token) => {\r\n    if (!token) return '#808080';\r\n    if (token.startsWith('--')) return getCSSVar(token) || token;\r\n    return token;\r\n};\r\n\r\nconst chartPanelFill = () => getCSSVar('--theme-palette-color-6') || (chartPanelIsDark() ? '#121212' : '#f3f3f3');\r\n\r\nconst chartModelStyle = (model, slot) => {\r\n    const fixed = (regChartConfig && regChartConfig.chart_colors) || {};\r\n    const auto = (regChartConfig && regChartConfig.auto_colors) || [\r\n        '#00994c', '#6ee7a3', '#ff944d', '#cc5400', '#b3b3b3', '#707070',\r\n    ];\r\n\r\n    if (Object.prototype.hasOwnProperty.call(fixed, model)) {\r\n        return { color: resolveChartColor(fixed[model]), borderDash: [] };\r\n    }\r\n\r\n    let autoIdx = 0;\r\n    for (let i = 0; i < slot; i++) {\r\n        const m = regChartModelOrder[i];\r\n        if (m && !Object.prototype.hasOwnProperty.call(fixed, m)) autoIdx++;\r\n    }\r\n\r\n    const color = resolveChartColor(auto[autoIdx % auto.length]);\r\n    const dashTier = Math.floor(autoIdx \/ auto.length);\r\n    return {\r\n        color,\r\n        borderDash: chartLineDashes[dashTier % chartLineDashes.length],\r\n    };\r\n};\r\n\r\ndocument.addEventListener('DOMContentLoaded', function() {\r\n    if (typeof Chart === 'undefined') {\r\n        console.error('Chart.js not loaded.');\r\n        return;\r\n    }\r\n\r\n    const container = document.querySelector('[data-xg-registrations]');\r\n    if (!container) return;\r\n\r\n    \/\/ dataJson is provided by PHP inline\r\n    \/\/ We assume dataJson = { months: [...], models: { G9: [...], P7: [...] } }\r\n    \r\n    \/\/ 1. Extract Unique Years & Models\r\n    const allMonths = dataJson.months || [];\r\n    const allModels = (regChartModelOrder && regChartModelOrder.length)\r\n        ? regChartModelOrder.filter((m) => dataJson.models && Object.prototype.hasOwnProperty.call(dataJson.models, m))\r\n        : Object.keys(dataJson.models || {});\r\n\r\n    const chartModelSlot = {};\r\n    allModels.forEach((m, i) => { chartModelSlot[m] = i; });\r\n    \r\n    const uniqueYears = [...new Set(allMonths.map(m => m.substring(0, 4)))].reverse(); \/\/ newest first\r\n    \r\n    \/\/ Current year for default filtering\r\n    const currentYear = new Date().getFullYear().toString();\r\n    \r\n    let selectedYears = new Set([currentYear]); \/\/ Default to current year\r\n    if (!uniqueYears.includes(currentYear) && uniqueYears.length > 0) {\r\n        selectedYears = new Set([uniqueYears[0]]); \/\/ fallback to latest available year\r\n    }\r\n    \r\n    let selectedModels = new Set(allModels); \/\/ Default to all models\r\n    \r\n    \/\/ UI Elements\r\n    const yearFiltersDiv = document.getElementById('xgRegYearFilters');\r\n    const modelFiltersDiv = document.getElementById('xgRegModelFilters');\r\n    const resetBtn = document.getElementById('xgRegResetBtn');\r\n    const badge = document.getElementById('xgRegBadge');\r\n    \r\n    \/\/ Render Year Checkboxes\r\n    uniqueYears.forEach(year => {\r\n        const label = document.createElement('label');\r\n        label.className = 'xg-youtube-index__check';\r\n        const cb = document.createElement('input');\r\n        cb.type = 'checkbox';\r\n        cb.value = year;\r\n        cb.checked = selectedYears.has(year);\r\n        cb.addEventListener('change', (e) => {\r\n            if (e.target.checked) selectedYears.add(year);\r\n            else selectedYears.delete(year);\r\n            updateDashboard();\r\n        });\r\n        const span = document.createElement('span');\r\n        span.textContent = year;\r\n        label.appendChild(cb);\r\n        label.appendChild(span);\r\n        yearFiltersDiv.appendChild(label);\r\n    });\r\n\r\n    \/\/ Render Model Checkboxes\r\n    allModels.forEach(model => {\r\n        const label = document.createElement('label');\r\n        label.className = 'xg-youtube-index__check';\r\n        const cb = document.createElement('input');\r\n        cb.type = 'checkbox';\r\n        cb.value = model;\r\n        cb.checked = selectedModels.has(model);\r\n        cb.addEventListener('change', (e) => {\r\n            if (e.target.checked) selectedModels.add(model);\r\n            else selectedModels.delete(model);\r\n            updateDashboard();\r\n        });\r\n        const span = document.createElement('span');\r\n        span.textContent = model;\r\n        label.appendChild(cb);\r\n        label.appendChild(span);\r\n        modelFiltersDiv.appendChild(label);\r\n    });\r\n    \r\n    resetBtn.addEventListener('click', () => {\r\n        selectedYears = new Set([currentYear]);\r\n        if (!uniqueYears.includes(currentYear) && uniqueYears.length > 0) {\r\n            selectedYears = new Set([uniqueYears[0]]);\r\n        }\r\n        selectedModels = new Set(allModels);\r\n        \r\n        yearFiltersDiv.querySelectorAll('input').forEach(cb => cb.checked = selectedYears.has(cb.value));\r\n        modelFiltersDiv.querySelectorAll('input').forEach(cb => cb.checked = selectedModels.has(cb.value));\r\n        updateDashboard();\r\n    });\r\n\r\n    \/\/ Exclusive Year \/ Model accordions - full lists, no inner scroll.\r\n    const regAccordions = container.querySelectorAll('[data-xg-reg-accordion]');\r\n    regAccordions.forEach(function (section) {\r\n        section.addEventListener('toggle', function () {\r\n            if (section.open) {\r\n                regAccordions.forEach(function (other) {\r\n                    if (other !== section) other.open = false;\r\n                });\r\n                return;\r\n            }\r\n            if (!Array.from(regAccordions).some(function (s) { return s.open; }) && regAccordions[0]) {\r\n                regAccordions[0].open = true;\r\n            }\r\n        });\r\n    });\r\n\r\n    \/\/ Sidebar toggles (mobile)\r\n    const backdrop = container.querySelector('.xg-youtube-index__backdrop');\r\n    const sidebar = container.querySelector('.xg-youtube-index__sidebar');\r\n    \r\n    container.querySelectorAll('[data-filter-open]').forEach(btn => {\r\n        btn.addEventListener('click', () => {\r\n            sidebar.classList.add('is-open');\r\n            backdrop.hidden = false;\r\n            document.body.style.overflow = 'hidden';\r\n        });\r\n    });\r\n    \r\n    container.querySelectorAll('[data-filter-close]').forEach(btn => {\r\n        btn.addEventListener('click', () => {\r\n            sidebar.classList.remove('is-open');\r\n            backdrop.hidden = true;\r\n            document.body.style.overflow = '';\r\n        });\r\n    });\r\n\r\n    let trendChartInstance = null;\r\n    let modelChartInstance = null;\r\n\r\n    function updateDashboard() {\r\n        \/\/ Update badge\r\n        const filterCount = (uniqueYears.length !== selectedYears.size ? 1 : 0) + (allModels.length !== selectedModels.size ? 1 : 0);\r\n        if (filterCount > 0) {\r\n            badge.textContent = filterCount;\r\n            badge.hidden = false;\r\n        } else {\r\n            badge.hidden = true;\r\n        }\r\n\r\n        \/\/ Update Chips\r\n        const chipsContainer = document.getElementById('xgRegChips');\r\n        const resetChipsBtn = document.getElementById('xgRegResetChipsBtn');\r\n        if (chipsContainer && resetChipsBtn) {\r\n            chipsContainer.innerHTML = '';\r\n            let hasActiveFilters = false;\r\n\r\n            if (selectedYears.size !== uniqueYears.length) {\r\n                hasActiveFilters = true;\r\n                selectedYears.forEach(year => {\r\n                    const btn = document.createElement('button');\r\n                    btn.className = 'xg-youtube-index__chip';\r\n                    btn.innerHTML = `${year} &times;`;\r\n                    btn.onclick = () => {\r\n                        selectedYears.delete(year);\r\n                        if (selectedYears.size === 0) selectedYears.add(uniqueYears[0] || new Date().getFullYear().toString());\r\n                        const cb = document.querySelector(`#xgRegYearFilters input[value=\"${year}\"]`);\r\n                        if(cb) cb.checked = false;\r\n                        updateDashboard();\r\n                    };\r\n                    chipsContainer.appendChild(btn);\r\n                });\r\n            }\r\n\r\n            if (selectedModels.size !== allModels.length) {\r\n                hasActiveFilters = true;\r\n                selectedModels.forEach(model => {\r\n                    const btn = document.createElement('button');\r\n                    btn.className = 'xg-youtube-index__chip';\r\n                    btn.innerHTML = `${model} &times;`;\r\n                    btn.onclick = () => {\r\n                        selectedModels.delete(model);\r\n                        if (selectedModels.size === 0) selectedModels = new Set(allModels);\r\n                        const cb = document.querySelector(`#xgRegModelFilters input[value=\"${model}\"]`);\r\n                        if(cb) cb.checked = false;\r\n                        updateDashboard();\r\n                    };\r\n                    chipsContainer.appendChild(btn);\r\n                });\r\n            }\r\n\r\n            if (hasActiveFilters) {\r\n                chipsContainer.hidden = false;\r\n                resetChipsBtn.hidden = false;\r\n            } else {\r\n                chipsContainer.hidden = true;\r\n                resetChipsBtn.hidden = true;\r\n            }\r\n            \r\n            if (!resetChipsBtn.hasListener) {\r\n                resetChipsBtn.addEventListener('click', () => {\r\n                    resetBtn.click();\r\n                });\r\n                resetChipsBtn.hasListener = true;\r\n            }\r\n        }\r\n\r\n        \/\/ Filter Data\r\n        const filteredMonthIndices = [];\r\n        const filteredMonths = [];\r\n        \r\n        allMonths.forEach((m, idx) => {\r\n            const y = m.substring(0, 4);\r\n            \/\/ If selectedYears is empty, it means show none, but usually users don't uncheck all.\r\n            \/\/ If they do, we show empty.\r\n            if (selectedYears.has(y)) {\r\n                filteredMonthIndices.push(idx);\r\n                filteredMonths.push(m);\r\n            }\r\n        });\r\n        \r\n        const datasetsLine = [];\r\n        const datasetsDoughnut = [{ data: [], backgroundColor: [], borderColor: [], borderWidth: 2 }];\r\n        const doughnutLabels = [];\r\n        \r\n        let colorIndex = 0;\r\n        \r\n        const filteredModelsData = {}; \/\/ For the table\r\n        \r\n        allModels.forEach(model => {\r\n            if (!selectedModels.has(model)) return;\r\n            \r\n            const rawCounts = dataJson.models[model] || [];\r\n            const filteredCounts = filteredMonthIndices.map(idx => rawCounts[idx] || 0);\r\n            const total = filteredCounts.reduce((a, b) => a + b, 0);\r\n            \/\/ Skip models with zero total for the selected period (line, doughnut, table).\r\n            if (total <= 0) {\r\n                return;\r\n            }\r\n            filteredModelsData[model] = filteredCounts;\r\n            \/\/ Chart.js skips nulls - no dots for 0 (pre-launch or zero months).\r\n            const chartCounts = filteredCounts.map(v => (v === 0 ? null : v));\r\n            \r\n            const slot = Object.prototype.hasOwnProperty.call(chartModelSlot, model) ? chartModelSlot[model] : colorIndex;\r\n            const style = chartModelStyle(model, slot);\r\n            colorIndex++;\r\n            \r\n            datasetsLine.push({\r\n                label: model,\r\n                data: chartCounts,\r\n                borderColor: style.color,\r\n                backgroundColor: style.color,\r\n                borderDash: style.borderDash,\r\n                tension: 0.3,\r\n                borderWidth: 3,\r\n                pointRadius: 4,\r\n                pointBackgroundColor: style.color,\r\n                pointStyle: style.borderDash.length ? 'rectRot' : 'circle',\r\n            });\r\n            \r\n            doughnutLabels.push(model);\r\n            datasetsDoughnut[0].data.push(total);\r\n            datasetsDoughnut[0].backgroundColor.push(style.color);\r\n        });\r\n        \r\n        datasetsDoughnut[0].borderColor = chartPanelFill();\r\n        datasetsDoughnut[0].borderWidth = 2;\r\n        \r\n        \/\/ Update Charts\r\n        const commonOptions = {\r\n            responsive: true,\r\n            maintainAspectRatio: false,\r\n            color: textColor(),\r\n            plugins: {\r\n                legend: { labels: { color: textColor() } },\r\n                tooltip: {\r\n                    titleColor: '#fff',\r\n                    bodyColor: '#fff',\r\n                    backgroundColor: 'rgba(0, 0, 0, 0.8)',\r\n                    padding: 10,\r\n                    cornerRadius: 4,\r\n                    displayColors: true\r\n                }\r\n            }\r\n        };\r\n\r\n        if (trendChartInstance) trendChartInstance.destroy();\r\n        trendChartInstance = new Chart(document.getElementById('xpengTrendChart').getContext('2d'), {\r\n            type: 'line',\r\n            data: { labels: filteredMonths, datasets: datasetsLine },\r\n            options: Object.assign({}, commonOptions, {\r\n                scales: {\r\n                    x: { grid: { color: gridColor(), drawBorder: false }, ticks: { color: textColor() } },\r\n                    y: { grid: { color: gridColor(), drawBorder: false }, ticks: { color: textColor() } }\r\n                }\r\n            })\r\n        });\r\n\r\n        if (modelChartInstance) modelChartInstance.destroy();\r\n        modelChartInstance = new Chart(document.getElementById('xpengModelChart').getContext('2d'), {\r\n            type: 'doughnut',\r\n            data: { labels: doughnutLabels, datasets: datasetsDoughnut },\r\n            options: Object.assign({}, commonOptions, { cutout: '60%' })\r\n        });\r\n        \r\n        \/\/ Re-build Table\r\n        buildTable(filteredMonths, filteredModelsData);\r\n    }\r\n    \r\n    function buildTable(months, modelsData) {\r\n        const root = document.getElementById('xgRegTableRoot');\r\n        root.innerHTML = '';\r\n        \r\n        if (months.length === 0) {\r\n            root.innerHTML = '<p>' + regI18n.no_data + '<\/p>';\r\n            return;\r\n        }\r\n        \r\n        const table = document.createElement('table');\r\n        table.className = 'xg-registrations-table';\r\n        table.style.width = '100%';\r\n        table.style.textAlign = 'left';\r\n        table.style.borderCollapse = 'collapse';\r\n        \r\n        \/\/ Thead\r\n        const thead = document.createElement('thead');\r\n        const htr = document.createElement('tr');\r\n        htr.style.borderBottom = '2px solid var(--theme-palette-color-5)';\r\n        \r\n        let th = document.createElement('th');\r\n        th.style.padding = '0.5rem';\r\n        th.textContent = regI18n.month;\r\n        htr.appendChild(th);\r\n        \r\n        \/\/ Hide model columns with zero total for the selected period (e.g. P5\/P7).\r\n        const activeModels = Object.keys(modelsData).filter(function (model) {\r\n            var sum = 0;\r\n            for (var i = 0; i < months.length; i++) {\r\n                sum += modelsData[model][i] || 0;\r\n            }\r\n            return sum > 0;\r\n        });\r\n        activeModels.forEach(model => {\r\n            let mth = document.createElement('th');\r\n            mth.style.padding = '0.5rem';\r\n            mth.textContent = model;\r\n            htr.appendChild(mth);\r\n        });\r\n        \r\n        let thTotal = document.createElement('th');\r\n        thTotal.style.padding = '0.5rem';\r\n        thTotal.textContent = regI18n.total;\r\n        htr.appendChild(thTotal);\r\n        \r\n        thead.appendChild(htr);\r\n        table.appendChild(thead);\r\n        \r\n        \/\/ Tbody (Reverse order!)\r\n        const tbody = document.createElement('tbody');\r\n        const totalsPerModel = {};\r\n        activeModels.forEach(m => totalsPerModel[m] = 0);\r\n        let absoluteTotal = 0;\r\n        \r\n        \/\/ Reverse loop to show newest months first!\r\n        for (let i = months.length - 1; i >= 0; i--) {\r\n            const tr = document.createElement('tr');\r\n            tr.style.borderBottom = '1px solid var(--theme-palette-color-5)';\r\n            \r\n            let tdM = document.createElement('td');\r\n            tdM.style.padding = '0.5rem';\r\n            tdM.style.fontWeight = '500';\r\n            tdM.textContent = months[i];\r\n            tr.appendChild(tdM);\r\n            \r\n            let rowTotal = 0;\r\n            activeModels.forEach(model => {\r\n                const count = modelsData[model][i] || 0;\r\n                rowTotal += count;\r\n                totalsPerModel[model] += count;\r\n                absoluteTotal += count;\r\n                \r\n                let tdC = document.createElement('td');\r\n                tdC.style.padding = '0.5rem';\r\n                tdC.textContent = count;\r\n                tr.appendChild(tdC);\r\n            });\r\n            \r\n            let tdR = document.createElement('td');\r\n            tdR.style.padding = '0.5rem';\r\n            tdR.style.fontWeight = 'bold';\r\n            tdR.textContent = rowTotal;\r\n            tr.appendChild(tdR);\r\n            \r\n            tbody.appendChild(tr);\r\n        }\r\n        table.appendChild(tbody);\r\n        \r\n        \/\/ Tfoot\r\n        const tfoot = document.createElement('tfoot');\r\n        const ftr = document.createElement('tr');\r\n        \r\n        let ftdL = document.createElement('td');\r\n        ftdL.style.padding = '0.5rem';\r\n        ftdL.style.fontWeight = 'bold';\r\n        ftdL.textContent = regI18n.total;\r\n        ftr.appendChild(ftdL);\r\n        \r\n        activeModels.forEach(model => {\r\n            let ftd = document.createElement('td');\r\n            ftd.style.padding = '0.5rem';\r\n            ftd.style.fontWeight = 'bold';\r\n            ftd.textContent = totalsPerModel[model];\r\n            ftr.appendChild(ftd);\r\n        });\r\n        \r\n        let ftdT = document.createElement('td');\r\n        ftdT.style.padding = '0.5rem';\r\n        ftdT.style.fontWeight = 'bold';\r\n        ftdT.textContent = absoluteTotal;\r\n        ftr.appendChild(ftdT);\r\n        \r\n        tfoot.appendChild(ftr);\r\n        table.appendChild(tfoot);\r\n        \r\n        root.appendChild(table);\r\n    }\r\n\r\n    \/\/ Initial render\r\n    updateDashboard();\r\n\r\n    \/\/ Dark Mode observer\r\n    const observer = new MutationObserver(function() {\r\n        setTimeout(() => {\r\n            const newTextColor = textColor();\r\n            const newGridColor = gridColor();\r\n            \r\n            [trendChartInstance, modelChartInstance].forEach(chart => {\r\n                if (!chart) return;\r\n                chart.options.color = newTextColor;\r\n                if (chart.options.plugins && chart.options.plugins.legend) {\r\n                    chart.options.plugins.legend.labels.color = newTextColor;\r\n                }\r\n                if (chart.options.scales) {\r\n                    if (chart.options.scales.x) {\r\n                        chart.options.scales.x.grid.color = newGridColor;\r\n                        chart.options.scales.x.ticks.color = newTextColor;\r\n                    }\r\n                    if (chart.options.scales.y) {\r\n                        chart.options.scales.y.grid.color = newGridColor;\r\n                        chart.options.scales.y.ticks.color = newTextColor;\r\n                    }\r\n                }\r\n                chart.update();\r\n            });\r\n        }, 50);\r\n    });\r\n\r\n    observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-color-scheme', 'class'] });\r\n    observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });\r\n});\r\n\r\n    <\/script>\r\n    \n\n\n\n<div class=\"wp-block-group xg-callout-note is-layout-flow wp-block-group-is-layout-flow\">\n\n<h4 class=\"wp-block-heading\">\ud83d\udccc Datakilde<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Tallene er baseret p\u00e5 Motorstyrelsens \u00e5bne data fra Motorregistret (DMR). Vi opdaterer m\u00e5nedligt; sm\u00e5 afvigelser ift. andre rapporter kan forekomme afh\u00e6ngigt af udtr\u00e6ksdato.<\/p>\n\n\n<p style=\"margin:1em 0 0;\">Registreringsstatistik seneste hentet: 7. august 2026<\/p>\n\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Dyk ned i danske registreringstal - fordelt p\u00e5 modeller og perioder.<\/p>\n","protected":false},"author":1,"featured_media":2812,"parent":0,"menu_order":20,"template":"","class_list":["post-2805","lineup","type-lineup","status-publish","has-post-thumbnail","hentry"],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":7}},"acf":[],"_links":{"self":[{"href":"https:\/\/xpeng.guru\/da\/wp-json\/wp\/v2\/lineup\/2805","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/xpeng.guru\/da\/wp-json\/wp\/v2\/lineup"}],"about":[{"href":"https:\/\/xpeng.guru\/da\/wp-json\/wp\/v2\/types\/lineup"}],"author":[{"embeddable":true,"href":"https:\/\/xpeng.guru\/da\/wp-json\/wp\/v2\/users\/1"}],"version-history":[{"count":6,"href":"https:\/\/xpeng.guru\/da\/wp-json\/wp\/v2\/lineup\/2805\/revisions"}],"predecessor-version":[{"id":5231,"href":"https:\/\/xpeng.guru\/da\/wp-json\/wp\/v2\/lineup\/2805\/revisions\/5231"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/xpeng.guru\/da\/wp-json\/wp\/v2\/media\/2812"}],"wp:attachment":[{"href":"https:\/\/xpeng.guru\/da\/wp-json\/wp\/v2\/media?parent=2805"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}