diff --git a/assets/app.js b/assets/app.js index 782e2a7..536bb37 100644 --- a/assets/app.js +++ b/assets/app.js @@ -15,10 +15,14 @@ import './utils.js'; import './opening_hours.js'; import './josm.js'; import './edit.js'; +import './table-map-toggles.js'; +import './stats-charts.js'; +import './dashboard-charts.js'; import Chart from 'chart.js/auto'; import ChartDataLabels from 'chartjs-plugin-datalabels'; -import { genererCouleurPastel, setupCitySearch, handleAddCityFormSubmit, enableLabourageForm, getLabourerUrl, adjustListGroupFontSize } from './utils.js'; +import maplibregl from 'maplibre-gl'; +import { genererCouleurPastel, setupCitySearch, handleAddCityFormSubmit, enableLabourageForm, getLabourerUrl, adjustListGroupFontSize, toggleCompletionInfo } from './utils.js'; import Tablesort from 'tablesort'; window.Chart = Chart; @@ -26,6 +30,9 @@ window.genererCouleurPastel = genererCouleurPastel; window.setupCitySearch = setupCitySearch; window.handleAddCityFormSubmit = handleAddCityFormSubmit; window.getLabourerUrl = getLabourerUrl; +window.ChartDataLabels = ChartDataLabels; +window.maplibregl = maplibregl; +window.toggleCompletionInfo = toggleCompletionInfo; Chart.register(ChartDataLabels); @@ -95,13 +102,13 @@ document.addEventListener('DOMContentLoaded', () => { }); // Focus sur le premier champ texte au chargement - const firstTextInput = document.querySelector('input.form-control'); - if (firstTextInput) { - firstTextInput.focus(); - console.log('focus sur le premier champ texte', firstTextInput); - } else { - console.log('pas de champ texte trouvé'); - } + // const firstTextInput = document.querySelector('input.form-control'); + // if (firstTextInput) { + // firstTextInput.focus(); + // console.log('focus sur le premier champ texte', firstTextInput); + // } else { + // console.log('pas de champ texte trouvé'); + // } @@ -179,4 +186,4 @@ document.addEventListener('DOMContentLoaded', () => { enableLabourageForm(); adjustListGroupFontSize('.list-group-item'); }); - + \ No newline at end of file diff --git a/assets/dashboard-charts.js b/assets/dashboard-charts.js new file mode 100644 index 0000000..be21412 --- /dev/null +++ b/assets/dashboard-charts.js @@ -0,0 +1,180 @@ +// Bubble chart du dashboard avec option de taille de bulle proportionnelle ou égale + +let bubbleChart = null; // Déclaré en dehors pour garder la référence +function getBubbleData(proportional) { + // Générer les données puis trier par rayon décroissant + const data = window.statsDataForBubble.map(stat => { + const population = parseInt(stat.population, 10); + const placesCount = parseInt(stat.placesCount, 10); + const ratio = population > 0 ? (placesCount / population) * 1000 : 0; + return { + x: population, + y: ratio, + r: proportional ? Math.sqrt(placesCount) * 2 : 12, + label: stat.name, + completion: stat.completionPercent || 0, + zone: stat.zone + }; + }); + // Trier du plus gros au plus petit rayon + data.sort((a, b) => b.r - a.r); + return data; +} + + +document.addEventListener('DOMContentLoaded', function() { + function waitForChartAndDrawBubble() { + if (!window.Chart || !window.ChartDataLabels) { + setTimeout(waitForChartAndDrawBubble, 50); + return; + } + const chartCanvas = document.getElementById('bubbleChart'); + const toggle = document.getElementById('toggleBubbleSize'); + + + + + function drawBubbleChart(proportional) { + // Détruire toute instance Chart.js existante sur ce canvas (Chart.js v3+) + const existing = window.Chart.getChart(chartCanvas); + if (existing) { + try { existing.destroy(); } catch (e) { console.warn('Erreur destroy Chart:', e); } + } + if (bubbleChart) { + try { bubbleChart.destroy(); } catch (e) { console.warn('Erreur destroy Chart:', e); } + bubbleChart = null; + } + + // Forcer le canvas à occuper toute la largeur/hauteur du conteneur en pixels + if (chartCanvas && chartCanvas.parentElement) { + const parentRect = chartCanvas.parentElement.getBoundingClientRect(); + console.log('parentRect', parentRect) + + chartCanvas.width = (parentRect.width); + chartCanvas.height = (parentRect.height); + chartCanvas.style.width = parentRect.width + 'px'; + chartCanvas.style.height = parentRect.height + 'px'; + } + + + const bubbleChartData = getBubbleData(proportional); + // Calcul de la régression linéaire (moindres carrés) + const validPoints = bubbleChartData.filter(d => d.x > 0 && d.y > 0); + const n = validPoints.length; + let regressionLine = null, slope = 0, intercept = 0; + if (n >= 2) { + let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0; + validPoints.forEach(d => { + sumX += Math.log10(d.x); + sumY += d.y; + sumXY += Math.log10(d.x) * d.y; + sumXX += Math.log10(d.x) * Math.log10(d.x); + }); + const meanX = sumX / n; + const meanY = sumY / n; + slope = (sumXY - n * meanX * meanY) / (sumXX - n * meanX * meanX); + intercept = meanY - slope * meanX; + const xMin = Math.min(...validPoints.map(d => d.x)); + const xMax = Math.max(...validPoints.map(d => d.x)); + regressionLine = [ + { x: xMin, y: slope * Math.log10(xMin) + intercept }, + { x: xMax, y: slope * Math.log10(xMax) + intercept } + ]; + } + window.Chart.register(window.ChartDataLabels); + bubbleChart = new window.Chart(chartCanvas.getContext('2d'), { + type: 'bubble', + data: { + datasets: [ + { + label: 'Villes', + data: bubbleChartData, + backgroundColor: bubbleChartData.map(d => `rgba(94, 255, 121, ${d.completion / 100})`), + borderColor: 'rgb(94, 255, 121)', + datalabels: { + anchor: 'center', + align: 'center', + color: '#000', + display: true, + font: { weight: 'bold' }, + formatter: (value, context) => { + return context.dataset.data[context.dataIndex].label; + } + } + }, + regressionLine ? { + label: 'Régression linéaire', + type: 'line', + data: regressionLine, + borderColor: 'rgba(0, 0, 0, 0.7)', + borderWidth: 2, + pointRadius: 0, + fill: false, + order: 0, + tension: 0, + datalabels: { display: false } + } : null + ].filter(Boolean) + }, + options: { + // responsive: true, + plugins: { + datalabels: { + // Désactivé au niveau global, activé par dataset + display: false + }, + legend: { display: true }, + tooltip: { + callbacks: { + label: (context) => { + const d = context.raw; + if (context.dataset.type === 'line') { + return `Régression: y = ${slope.toFixed(2)} × log10(x) + ${intercept.toFixed(2)}`; + } + return [ + `${d.label}`, + `Population: ${d.x.toLocaleString()}`, + `Lieux / 1000 hab: ${d.y.toFixed(2)}`, + `Total lieux: ${Math.round(Math.pow(d.r / 2, 2))}`, + `Complétion: ${d.completion}%` + ]; + } + } + } + }, + scales: { + x: { + type: 'logarithmic', + title: { display: true, text: 'Population (échelle log)' } + }, + y: { + title: { display: true, text: 'Commerces par habitant' } + } + } + } + }); + // Ajout du clic sur une bulle + chartCanvas.onclick = function(evt) { + const points = bubbleChart.getElementsAtEventForMode(evt, 'nearest', { intersect: true }, true); + if (points.length > 0) { + const firstPoint = points[0]; + const dataIndex = firstPoint.index; + const stat = window.statsDataForBubble[dataIndex]; + if (stat && stat.zone) { + window.location.href = '/admin/stats/' + stat.zone; + } + } + }; + } + + // Initial draw + console.log('[bubble chart] Initialisation avec taille proportionnelle ?', toggle.checked); + drawBubbleChart(toggle && toggle.checked); + // Listener + toggle.addEventListener('change', function() { + console.log('[bubble chart] Toggle changé, taille proportionnelle ?', toggle.checked); + drawBubbleChart(toggle.checked); + }); + } + waitForChartAndDrawBubble(); +}); \ No newline at end of file diff --git a/assets/stats-charts.js b/assets/stats-charts.js new file mode 100644 index 0000000..6034f23 --- /dev/null +++ b/assets/stats-charts.js @@ -0,0 +1,44 @@ +// Affichage du graphique de fréquence des mises à jour par trimestre sur la page stats d'une ville + +document.addEventListener('DOMContentLoaded', function() { + function drawModificationsByQuarterChart() { + if (!window.Chart) { + setTimeout(drawModificationsByQuarterChart, 50); + return; + } + if (typeof window.modificationsByQuarter === 'undefined') return; + const modifData = window.modificationsByQuarter; + const modifLabels = Object.keys(modifData); + const modifCounts = Object.values(modifData); + const modifCanvas = document.getElementById('modificationsByQuarterChart'); + if (modifCanvas && modifLabels.length > 0) { + new window.Chart(modifCanvas.getContext('2d'), { + type: 'bar', + data: { + labels: modifLabels, + datasets: [{ + label: 'Nombre de lieux modifiés', + data: modifCounts, + backgroundColor: 'rgba(54, 162, 235, 0.7)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1 + }] + }, + options: { + responsive: true, + plugins: { + legend: { display: false }, + title: { display: true, text: 'Fréquence des mises à jour par trimestre' } + }, + scales: { + y: { beginAtZero: true, title: { display: true, text: 'Nombre de lieux' } }, + x: { title: { display: true, text: 'Trimestre' } } + } + } + }); + } else if (modifCanvas) { + modifCanvas.parentNode.innerHTML = '
Aucune donnée de modification disponible pour cette ville.
'; + } + } + drawModificationsByQuarterChart(); +}); \ No newline at end of file diff --git a/assets/table-map-toggles.js b/assets/table-map-toggles.js new file mode 100644 index 0000000..1f1c6cf --- /dev/null +++ b/assets/table-map-toggles.js @@ -0,0 +1,23 @@ +// Gestion du tri des tableaux +import Tablesort from 'tablesort'; + +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('.js-sort-table').forEach(table => { + new Tablesort(table); + }); + + // Gestion du toggle gouttes/ronds sur la carte + const toggle = document.getElementById('toggleMarkers'); + if (toggle && window.updateMarkers) { + toggle.addEventListener('change', (e) => { + window.updateMarkers(e.target.value); + }); + } +}); + +// Exposer une fonction pour (ré)appliquer le tri si besoin +export function applyTableSort() { + document.querySelectorAll('.js-sort-table').forEach(table => { + new Tablesort(table); + }); +} \ No newline at end of file diff --git a/assets/utils.js b/assets/utils.js index 83a2029..a6db706 100644 --- a/assets/utils.js +++ b/assets/utils.js @@ -338,9 +338,23 @@ export function calculateCompletion(properties) { }; } +export function toggleCompletionInfo() { + const content = document.getElementById('completionInfoContent'); + const icon = document.getElementById('completionInfoIcon'); + if (content) { + const isVisible = content.style.display === 'block'; + content.style.display = isVisible ? 'none' : 'block'; + if (icon) { + icon.classList.toggle('bi-chevron-down', isVisible); + icon.classList.toggle('bi-chevron-up', !isVisible); + } + } +} + window.check_validity = check_validity; window.colorHeadingTable = colorHeadingTable; window.openInPanoramax = openInPanoramax; window.listChangesets = listChangesets; window.adjustListGroupFontSize = adjustListGroupFontSize; -window.calculateCompletion = calculateCompletion; \ No newline at end of file +window.calculateCompletion = calculateCompletion; +window.toggleCompletionInfo = toggleCompletionInfo; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e2adc6d..39b892c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "chart.js": "^4.5.0", "chartjs-plugin-datalabels": "^2.2.0", "core-js": "^3.38.0", + "maplibre-gl": "^5.6.0", "regenerator-runtime": "^0.13.9", "tablesort": "^5.6.0", "webpack": "^5.74.0", @@ -1704,6 +1705,91 @@ "dev": true, "license": "MIT" }, + "node_modules/@mapbox/geojson-rewind": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", + "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "dev": true, + "license": "ISC", + "dependencies": { + "get-stream": "^6.0.1", + "minimist": "^1.2.6" + }, + "bin": { + "geojson-rewind": "geojson-rewind" + } + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-23.3.0.tgz", + "integrity": "sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, "node_modules/@nuxt/friendly-errors-webpack-plugin": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@nuxt/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-2.6.0.tgz", @@ -1932,6 +2018,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1966,6 +2069,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", + "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, "node_modules/@types/node": { "version": "22.15.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", @@ -1976,6 +2098,23 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -3015,6 +3154,13 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/earcut": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz", + "integrity": "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw==", + "dev": true, + "license": "ISC" + }, "node_modules/electron-to-chromium": { "version": "1.5.158", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.158.tgz", @@ -3278,6 +3424,33 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-vt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", + "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==", + "dev": true, + "license": "MIT" + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -3285,6 +3458,47 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/global-prefix": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", + "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^4.1.3", + "kind-of": "^6.0.3", + "which": "^4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -3358,6 +3572,27 @@ "postcss": "^8.1.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -3457,6 +3692,16 @@ "node": ">=8" } }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -3693,6 +3938,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3706,6 +3958,13 @@ "node": ">=6" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "dev": true, + "license": "ISC" + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -3808,6 +4067,48 @@ "yallist": "^3.0.2" } }, + "node_modules/maplibre-gl": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.6.0.tgz", + "integrity": "sha512-7TuHMozUC4rlIp08bSsxCixFn18P24otrlZU/7UGCO5RufFTJadFzauTrvBHr9FB67MbJ6nvFXEftGd0bUl4Iw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@maplibre/maplibre-gl-style-spec": "^23.3.0", + "@types/geojson": "^7946.0.16", + "@types/geojson-vt": "3.2.5", + "@types/mapbox__point-geometry": "^0.1.4", + "@types/mapbox__vector-tile": "^1.3.4", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "earcut": "^3.0.1", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.3", + "global-prefix": "^4.0.0", + "kdbush": "^4.0.2", + "murmurhash-js": "^1.0.0", + "pbf": "^3.3.0", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0", + "vt-pbf": "^3.1.3" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -3866,6 +4167,16 @@ "webpack": "^5.0.0" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3873,6 +4184,13 @@ "dev": true, "license": "MIT" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -3988,6 +4306,20 @@ "dev": true, "license": "MIT" }, + "node_modules/pbf": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4575,6 +4907,13 @@ "dev": true, "license": "MIT" }, + "node_modules/potpack": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==", + "dev": true, + "license": "ISC" + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -4586,6 +4925,20 @@ "renderkid": "^3.0.0" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "dev": true, + "license": "ISC" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4762,6 +5115,16 @@ "node": ">=8" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/resolve-url-loader": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", @@ -4786,6 +5149,13 @@ "dev": true, "license": "MIT" }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4983,6 +5353,16 @@ "postcss": "^8.4.32" } }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -5234,6 +5614,13 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "dev": true, + "license": "ISC" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -5340,6 +5727,18 @@ "dev": true, "license": "MIT" }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "node_modules/watchpack": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", diff --git a/package.json b/package.json index e902c31..d3f21c8 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "chart.js": "^4.5.0", "chartjs-plugin-datalabels": "^2.2.0", "core-js": "^3.38.0", + "maplibre-gl": "^5.6.0", "regenerator-runtime": "^0.13.9", "tablesort": "^5.6.0", "webpack": "^5.74.0", diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php index 5aa5f9b..77f98ab 100644 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -308,6 +308,7 @@ final class AdminController extends AbstractController 'maptiler_token' => $_ENV['MAPTILER_TOKEN'], 'statsHistory' => $statsHistory, 'CTC_urls' => $urls, + 'overpass' => '' ]); } diff --git a/templates/admin/stats.html.twig b/templates/admin/stats.html.twig index cafcb64..b72fd66 100644 --- a/templates/admin/stats.html.twig +++ b/templates/admin/stats.html.twig @@ -244,14 +244,18 @@ -
+ {#

Requête Overpass

-
-
- -
-
+
+ +
+            {{overpass}}
+    
+
+ +
+ #}
@@ -292,7 +296,7 @@

-

+
@@ -301,17 +305,65 @@ {% block javascripts %} {{ parent() }} - - - + + {% endblock %} diff --git a/templates/public/dashboard.html.twig b/templates/public/dashboard.html.twig index dd6eea2..cef177e 100644 --- a/templates/public/dashboard.html.twig +++ b/templates/public/dashboard.html.twig @@ -47,6 +47,22 @@ position: relative; margin-bottom: 1rem; } + @media (max-width: 768px) { + .bubble-chart-container { + min-height: 300px; + height: 60vw; + max-height: 400px; + overflow-x: auto; + } + } + @media (min-width: 769px) { + .bubble-chart-container { + min-height: 400px; + height: 50vw; + max-height: 600px; + overflow-x: auto; + } + } {% endblock %} @@ -59,16 +75,26 @@
+
+
+
+ + +
+
+
+
- Statistiques des villes (nombre de commerces) + Statistiques des villes (bubble chart)
-
- +
+
-
             
@@ -177,10 +203,11 @@