up wiki compare
This commit is contained in:
parent
d2936d5730
commit
1535cf8ee3
8 changed files with 1036 additions and 79 deletions
|
@ -282,7 +282,35 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div id="alertes_osmose"></div>
|
||||
<div class="card mt-4 mb-4">
|
||||
<div class="card-header">
|
||||
<h4><i class="bi bi-exclamation-triangle"></i> Alertes Osmose</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="alertes_osmose">Chargement des alertes...</div>
|
||||
<div id="alertes_liste" class="mt-3" style="display: none;">
|
||||
<h5>Liste complète des alertes</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Élément</th>
|
||||
<th>Position</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="alertes_table_body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="alertes_distribution" class="mt-4" style="display: none;">
|
||||
<h5>Répartition des alertes par thème</h5>
|
||||
<canvas id="alertesChart" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="themeChart"></canvas>
|
||||
</div>
|
||||
|
@ -885,38 +913,70 @@
|
|||
.then(data => {
|
||||
if (!data.issues || data.issues.length === 0) {
|
||||
console.log('Aucune analyse Osmose trouvée pour ce thème dans cette zone.');
|
||||
document.querySelector('#alertes_osmose').innerHTML = '<div class="alert alert-info">Aucune alerte Osmose trouvée pour ce thème dans cette zone.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const divOsmose = document.querySelector(('#alertes_osmose'))
|
||||
// Stocker les données Osmose globalement pour pouvoir les utiliser ailleurs
|
||||
window.osmoseData = data.issues;
|
||||
|
||||
// Mettre à jour le résumé des alertes
|
||||
const divOsmose = document.querySelector('#alertes_osmose');
|
||||
if(divOsmose){
|
||||
if (data.issues.length === 1) {
|
||||
// Si un seul objet, rendre tout le texte cliquable
|
||||
const issueId = data.issues[0].id;
|
||||
divOsmose.innerHTML = `<a href="https://osmose.openstreetmap.fr/fr/error/${issueId}" target="_blank" style="text-decoration: none; color: inherit;">
|
||||
<span class="counter">${data.issues.length}</span> objet à ajouter selon Osmose
|
||||
</a>`;
|
||||
divOsmose.innerHTML = `<div class="alert alert-warning">
|
||||
<a href="https://osmose.openstreetmap.fr/fr/error/${issueId}" target="_blank" style="text-decoration: none; color: inherit;">
|
||||
<span class="counter">${data.issues.length}</span> objet à ajouter selon Osmose
|
||||
</a>
|
||||
</div>`;
|
||||
} else {
|
||||
// Si plusieurs objets, lister chaque objet avec son numéro
|
||||
let content = `<span class="counter">${data.issues.length}</span> objets à ajouter selon Osmose : `;
|
||||
|
||||
// Limiter à 5 objets affichés pour éviter de surcharger l'interface
|
||||
const displayLimit = 5;
|
||||
const displayCount = Math.min(data.issues.length, displayLimit);
|
||||
|
||||
for (let i = 0; i < displayCount; i++) {
|
||||
const issueId = data.issues[i].id;
|
||||
content += `<a href="http://localhost:8111/import?url=https://osmose.openstreetmap.fr/api/0.3/issue/${issueId}/fix/0" target="_blank" class="badge bg-purple mx-1">${i}</a>`;
|
||||
}
|
||||
|
||||
// Indiquer s'il y a plus d'objets que ceux affichés
|
||||
if (data.issues.length > displayLimit) {
|
||||
content += `<span class="text-muted">(et ${data.issues.length - displayLimit} autres)</span>`;
|
||||
}
|
||||
|
||||
divOsmose.innerHTML = content;
|
||||
// Si plusieurs objets, afficher un résumé
|
||||
divOsmose.innerHTML = `<div class="alert alert-warning">
|
||||
<span class="counter">${data.issues.length}</span> objets à ajouter selon Osmose
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Remplir la table des alertes
|
||||
const alertesTableBody = document.querySelector('#alertes_table_body');
|
||||
if (alertesTableBody) {
|
||||
let tableContent = '';
|
||||
|
||||
data.issues.forEach((issue, index) => {
|
||||
const issueId = issue.id;
|
||||
const element = issue.elems && issue.elems.length > 0 ?
|
||||
`${issue.elems[0].type} ${issue.elems[0].id}` : 'Non spécifié';
|
||||
const position = issue.lat && issue.lon ?
|
||||
`${issue.lat.toFixed(5)}, ${issue.lon.toFixed(5)}` : 'Non spécifié';
|
||||
|
||||
tableContent += `
|
||||
<tr>
|
||||
<td>${issueId}</td>
|
||||
<td>${element}</td>
|
||||
<td>${position}</td>
|
||||
<td>
|
||||
<a href="https://osmose.openstreetmap.fr/fr/error/${issueId}" target="_blank" class="btn btn-sm btn-info" title="Voir sur Osmose">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
<a href="http://localhost:8111/import?url=https://osmose.openstreetmap.fr/api/0.3/issue/${issueId}/fix/0" target="_blank" class="btn btn-sm btn-success" title="Corriger dans JOSM">
|
||||
<i class="bi bi-tools"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
alertesTableBody.innerHTML = tableContent;
|
||||
document.querySelector('#alertes_liste').style.display = 'block';
|
||||
}
|
||||
|
||||
// Créer la distribution des alertes par thème
|
||||
createAlertesDistribution(data.issues);
|
||||
|
||||
// Afficher la section de distribution
|
||||
document.querySelector('#alertes_distribution').style.display = 'block';
|
||||
|
||||
console.log(`[Osmose] ${data.issues.length} analyses trouvées pour le thème ${theme}`);
|
||||
|
||||
|
@ -954,6 +1014,94 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Fonction pour créer le graphique de distribution des alertes par thème
|
||||
function createAlertesDistribution(issues) {
|
||||
if (!issues || issues.length === 0) return;
|
||||
|
||||
// Compter les alertes par item
|
||||
const itemCounts = {};
|
||||
const itemLabels = {
|
||||
8410: 'Borne de recharge (manquante)',
|
||||
8411: 'Borne de recharge (à compléter)',
|
||||
8031: 'École (manquante)',
|
||||
8211: 'Pharmacie (manquante)',
|
||||
7220: 'Cabinet médical (manquant)',
|
||||
8331: 'Hôpital (manquant)',
|
||||
7240: 'Laboratoire (manquant)',
|
||||
8351: 'Laboratoire (à compléter)',
|
||||
8190: 'Police (manquante)',
|
||||
8191: 'Police (à compléter)',
|
||||
8370: 'Défibrillateur (manquant)'
|
||||
};
|
||||
|
||||
issues.forEach(issue => {
|
||||
if (issue.item) {
|
||||
if (!itemCounts[issue.item]) {
|
||||
itemCounts[issue.item] = 0;
|
||||
}
|
||||
itemCounts[issue.item]++;
|
||||
}
|
||||
});
|
||||
|
||||
// Préparer les données pour le graphique
|
||||
const labels = [];
|
||||
const data = [];
|
||||
const backgroundColor = [];
|
||||
|
||||
// Générer des couleurs aléatoires pour chaque thème
|
||||
function getRandomColor() {
|
||||
const letters = '0123456789ABCDEF';
|
||||
let color = '#';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
// Trier les items par nombre d'alertes (décroissant)
|
||||
const sortedItems = Object.keys(itemCounts).sort((a, b) => itemCounts[b] - itemCounts[a]);
|
||||
|
||||
sortedItems.forEach(item => {
|
||||
// Utiliser le label s'il existe, sinon utiliser l'ID de l'item
|
||||
labels.push(itemLabels[item] || `Item ${item}`);
|
||||
data.push(itemCounts[item]);
|
||||
backgroundColor.push(getRandomColor());
|
||||
});
|
||||
|
||||
// Créer le graphique
|
||||
const ctx = document.getElementById('alertesChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
data: data,
|
||||
backgroundColor: backgroundColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right',
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const label = context.label || '';
|
||||
const value = context.raw;
|
||||
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||
const percentage = Math.round((value / total) * 100);
|
||||
return `${label}: ${value} (${percentage}%)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fonction pour charger les détails d'une analyse Osmose
|
||||
function loadOsmoseIssueDetails(issueId) {
|
||||
const detailsUrl = `https://osmose.openstreetmap.fr/api/0.3/issue/${issueId}?langs=auto`;
|
||||
|
|
|
@ -93,41 +93,169 @@
|
|||
<select name="theme" id="theme" class="form-select" onchange="this.form.submit()">
|
||||
<option value="all" {{ theme == 'all' ? 'selected' : '' }}>Tous les thèmes</option>
|
||||
{% for themeKey, themeLabel in themes %}
|
||||
<option value="{{ themeKey }}" {{ theme == themeKey ? 'selected' : '' }}>
|
||||
{{ themeLabel }}
|
||||
</option>
|
||||
{% if themeKey != 'other' or issuesByTheme['other'] > 0 %}
|
||||
<option value="{{ themeKey }}" {{ theme == themeKey ? 'selected' : '' }}>
|
||||
{{ themeLabel }}{% if issuesByTheme[themeKey] > 0 %} ({{ issuesByTheme[themeKey] }}){% endif %}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 text-end">
|
||||
<a href="{{ osmoseApiUrl }}" target="_blank" class="btn btn-primary">
|
||||
<i class="fas fa-external-link-alt"></i> Voir sur Osmose
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3>Liste des problèmes ({{ osmoseIssues|length }})</h3>
|
||||
|
||||
{% if osmoseIssues|length > 0 %}
|
||||
<div class="issue-list">
|
||||
{% for issue in osmoseIssues %}
|
||||
<div class="issue-item level-{{ issue.level }}" data-lat="{{ issue.lat }}" data-lon="{{ issue.lon }}">
|
||||
<h5>{{ issue.title }}</h5>
|
||||
{% if issue.subtitle %}
|
||||
<p>{{ issue.subtitle }}</p>
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="badge bg-secondary">Item: {{ issue.item }}</span>
|
||||
<a href="{{ issue.url }}" target="_blank" class="btn btn-sm btn-primary">Voir sur Osmose</a>
|
||||
<!-- Statistiques des alertes -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">Répartition par thème</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if osmoseIssues|length > 0 %}
|
||||
<div class="row">
|
||||
{% for themeKey, count in issuesByTheme %}
|
||||
{% if count > 0 %}
|
||||
<div class="col-md-6 mb-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>{{ themes[themeKey] }}</span>
|
||||
<span class="badge bg-primary">{{ count }}</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar"
|
||||
style="width: {{ (count / osmoseIssues|length * 100)|round }}%;"
|
||||
aria-valuenow="{{ count }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="{{ osmoseIssues|length }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-center">Aucun problème trouvé</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">Répartition par niveau de sévérité</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if osmoseIssues|length > 0 %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Critique</span>
|
||||
<span class="badge bg-danger">{{ issuesByLevel[1] }}</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-danger" role="progressbar"
|
||||
style="width: {{ (issuesByLevel[1] / osmoseIssues|length * 100)|round }}%;"
|
||||
aria-valuenow="{{ issuesByLevel[1] }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="{{ osmoseIssues|length }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 mb-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Important</span>
|
||||
<span class="badge bg-warning text-dark">{{ issuesByLevel[2] }}</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-warning" role="progressbar"
|
||||
style="width: {{ (issuesByLevel[2] / osmoseIssues|length * 100)|round }}%;"
|
||||
aria-valuenow="{{ issuesByLevel[2] }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="{{ osmoseIssues|length }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 mb-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Avertissement</span>
|
||||
<span class="badge bg-info">{{ issuesByLevel[3] }}</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-info" role="progressbar"
|
||||
style="width: {{ (issuesByLevel[3] / osmoseIssues|length * 100)|round }}%;"
|
||||
aria-valuenow="{{ issuesByLevel[3] }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="{{ osmoseIssues|length }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-center">Aucun problème trouvé</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="no-issues">
|
||||
<p>Aucun problème Osmose trouvé pour cette ville avec le filtre actuel.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<h3 class="mb-0">Liste des problèmes ({{ osmoseIssues|length }})</h3>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-light" id="sort-by-level">Trier par sévérité</button>
|
||||
<button class="btn btn-sm btn-light" id="sort-by-item">Trier par type</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
{% if osmoseIssues|length > 0 %}
|
||||
<div class="issue-list">
|
||||
{% for issue in osmoseIssues %}
|
||||
<div class="issue-item level-{{ issue.level }}" data-lat="{{ issue.lat }}" data-lon="{{ issue.lon }}" data-level="{{ issue.level }}" data-item="{{ issue.item }}">
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<h5>{{ issue.title }}</h5>
|
||||
{% if issue.subtitle %}
|
||||
<p class="text-muted">{{ issue.subtitle }}</p>
|
||||
{% endif %}
|
||||
<div class="d-flex gap-2 mt-2">
|
||||
<span class="badge bg-secondary">Item: {{ issue.item }}</span>
|
||||
{% if issue.level == 1 %}
|
||||
<span class="badge bg-danger">Critique</span>
|
||||
{% elseif issue.level == 2 %}
|
||||
<span class="badge bg-warning text-dark">Important</span>
|
||||
{% elseif issue.level == 3 %}
|
||||
<span class="badge bg-info">Avertissement</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 text-end d-flex flex-column justify-content-between">
|
||||
<a href="{{ issue.url }}" target="_blank" class="btn btn-sm btn-primary mb-2">
|
||||
<i class="fas fa-external-link-alt"></i> Voir sur Osmose
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-secondary locate-on-map">
|
||||
<i class="fas fa-map-marker-alt"></i> Localiser sur la carte
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="no-issues">
|
||||
<p class="text-center">Aucun problème Osmose trouvé pour cette ville avec le filtre actuel.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -311,29 +439,47 @@
|
|||
map.getCanvas().style.cursor = '';
|
||||
});
|
||||
|
||||
// Ajouter un événement de clic sur les éléments de la liste
|
||||
{% for issue in osmoseIssues %}
|
||||
document.querySelector(`.issue-item[data-lat="{{ issue.lat }}"][data-lon="{{ issue.lon }}"]`)?.addEventListener('click', function() {
|
||||
map.flyTo({
|
||||
center: [{{ issue.lon }}, {{ issue.lat }}],
|
||||
zoom: 18
|
||||
});
|
||||
|
||||
// Simuler un clic sur le point pour ouvrir la popup
|
||||
const features = map.queryRenderedFeatures(
|
||||
map.project([{{ issue.lon }}, {{ issue.lat }}]),
|
||||
{ layers: ['unclustered-point'] }
|
||||
);
|
||||
|
||||
if (features.length > 0) {
|
||||
map.fire('click', {
|
||||
lngLat: { lng: {{ issue.lon }}, lat: {{ issue.lat }} },
|
||||
point: map.project([{{ issue.lon }}, {{ issue.lat }}]),
|
||||
features: [features[0]]
|
||||
});
|
||||
}
|
||||
// Fonction pour localiser un problème sur la carte
|
||||
function locateIssueOnMap(lat, lon) {
|
||||
map.flyTo({
|
||||
center: [lon, lat],
|
||||
zoom: 18
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
// Simuler un clic sur le point pour ouvrir la popup
|
||||
const features = map.queryRenderedFeatures(
|
||||
map.project([lon, lat]),
|
||||
{ layers: ['unclustered-point'] }
|
||||
);
|
||||
|
||||
if (features.length > 0) {
|
||||
map.fire('click', {
|
||||
lngLat: { lng: lon, lat: lat },
|
||||
point: map.project([lon, lat]),
|
||||
features: [features[0]]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter un événement de clic sur les boutons "Localiser sur la carte"
|
||||
document.querySelectorAll('.locate-on-map').forEach(function(button) {
|
||||
button.addEventListener('click', function(e) {
|
||||
e.stopPropagation(); // Empêcher la propagation au parent
|
||||
const issueItem = this.closest('.issue-item');
|
||||
const lat = parseFloat(issueItem.dataset.lat);
|
||||
const lon = parseFloat(issueItem.dataset.lon);
|
||||
locateIssueOnMap(lat, lon);
|
||||
});
|
||||
});
|
||||
|
||||
// Ajouter un événement de clic sur les éléments de la liste
|
||||
document.querySelectorAll('.issue-item').forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
const lat = parseFloat(this.dataset.lat);
|
||||
const lon = parseFloat(this.dataset.lon);
|
||||
locateIssueOnMap(lat, lon);
|
||||
});
|
||||
});
|
||||
|
||||
// Ajuster la vue pour montrer tous les marqueurs si nécessaire
|
||||
if (features.length > 0) {
|
||||
|
@ -347,6 +493,37 @@
|
|||
padding: 50
|
||||
});
|
||||
}
|
||||
|
||||
// Fonctions de tri pour les problèmes
|
||||
function sortIssuesByLevel() {
|
||||
const issueList = document.querySelector('.issue-list');
|
||||
const issues = Array.from(issueList.querySelectorAll('.issue-item'));
|
||||
|
||||
issues.sort(function(a, b) {
|
||||
return parseInt(a.dataset.level) - parseInt(b.dataset.level);
|
||||
});
|
||||
|
||||
issues.forEach(function(issue) {
|
||||
issueList.appendChild(issue);
|
||||
});
|
||||
}
|
||||
|
||||
function sortIssuesByItem() {
|
||||
const issueList = document.querySelector('.issue-list');
|
||||
const issues = Array.from(issueList.querySelectorAll('.issue-item'));
|
||||
|
||||
issues.sort(function(a, b) {
|
||||
return parseInt(a.dataset.item) - parseInt(b.dataset.item);
|
||||
});
|
||||
|
||||
issues.forEach(function(issue) {
|
||||
issueList.appendChild(issue);
|
||||
});
|
||||
}
|
||||
|
||||
// Ajouter des événements de clic sur les boutons de tri
|
||||
document.getElementById('sort-by-level').addEventListener('click', sortIssuesByLevel);
|
||||
document.getElementById('sort-by-item').addEventListener('click', sortIssuesByItem);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -171,7 +171,100 @@
|
|||
On compte aussi le nombre de sections et de liens.
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>Graphe de décrépitude</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="decrepitudeChart" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Collect data from the table
|
||||
const labels = [];
|
||||
const scores = [];
|
||||
const colors = [];
|
||||
|
||||
{% for key, languages in wiki_pages %}
|
||||
{% if languages['en'] is defined and languages['fr'] is defined %}
|
||||
labels.push("{{ key }}");
|
||||
{% set score = languages['en'].staleness_score|default(0) %}
|
||||
scores.push({{ score }});
|
||||
|
||||
// Set color based on score
|
||||
{% if score > 50 %}
|
||||
colors.push('rgba(220, 53, 69, 0.7)'); // danger
|
||||
{% elseif score > 20 %}
|
||||
colors.push('rgba(255, 193, 7, 0.7)'); // warning
|
||||
{% else %}
|
||||
colors.push('rgba(25, 135, 84, 0.7)'); // success
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
// Sort data by score (descending)
|
||||
const indices = Array.from(Array(scores.length).keys())
|
||||
.sort((a, b) => scores[b] - scores[a]);
|
||||
|
||||
const sortedLabels = indices.map(i => labels[i]);
|
||||
const sortedScores = indices.map(i => scores[i]);
|
||||
const sortedColors = indices.map(i => colors[i]);
|
||||
|
||||
// Limit to top 20 pages for readability
|
||||
const displayLimit = 20;
|
||||
const displayLabels = sortedLabels.slice(0, displayLimit);
|
||||
const displayScores = sortedScores.slice(0, displayLimit);
|
||||
const displayColors = sortedColors.slice(0, displayLimit);
|
||||
|
||||
// Create the chart
|
||||
const ctx = document.getElementById('decrepitudeChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: displayLabels,
|
||||
datasets: [{
|
||||
label: 'Score de décrépitude',
|
||||
data: displayScores,
|
||||
backgroundColor: displayColors,
|
||||
borderColor: displayColors.map(c => c.replace('0.7', '1')),
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return `Score: ${context.raw}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
max: 100,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Score de décrépitude (0-100)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -257,6 +257,15 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h4>Répartition des années des propositions</h4>
|
||||
<div class="chart-container">
|
||||
<canvas id="yearDistributionChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -578,6 +587,93 @@
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize year distribution chart
|
||||
const yearChartCanvas = document.getElementById('yearDistributionChart');
|
||||
if (yearChartCanvas) {
|
||||
// Get proposals data from the template
|
||||
const proposals = {{ proposals|json_encode|raw }};
|
||||
|
||||
if (proposals && proposals.length > 0) {
|
||||
// Extract years from last_modified dates
|
||||
const yearCounts = {};
|
||||
|
||||
proposals.forEach(proposal => {
|
||||
if (proposal.last_modified) {
|
||||
// Extract year from the date string (format: "DD Month YYYY")
|
||||
const yearMatch = proposal.last_modified.match(/\d{4}$/);
|
||||
if (yearMatch) {
|
||||
const year = yearMatch[0];
|
||||
yearCounts[year] = (yearCounts[year] || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Sort years chronologically
|
||||
const sortedYears = Object.keys(yearCounts).sort();
|
||||
const counts = sortedYears.map(year => yearCounts[year]);
|
||||
|
||||
// Generate a color gradient for the bars
|
||||
const colors = sortedYears.map((year, index) => {
|
||||
// Create a gradient from blue to green
|
||||
const ratio = index / (sortedYears.length - 1 || 1);
|
||||
return `rgba(${Math.round(33 + (20 * ratio))}, ${Math.round(150 + (50 * ratio))}, ${Math.round(243 - (100 * ratio))}, 0.7)`;
|
||||
});
|
||||
|
||||
// Create the chart
|
||||
new Chart(yearChartCanvas, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: sortedYears,
|
||||
datasets: [{
|
||||
label: 'Nombre de propositions',
|
||||
data: counts,
|
||||
backgroundColor: colors,
|
||||
borderColor: colors.map(color => color.replace('0.7', '1')),
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
precision: 0
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Nombre de propositions'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Année'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: function(context) {
|
||||
return `Année ${context[0].label}`;
|
||||
},
|
||||
label: function(context) {
|
||||
const count = context.raw;
|
||||
return count > 1 ? `${count} propositions` : `${count} proposition`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue