add history in articles measures

This commit is contained in:
Tykayn 2025-09-08 10:20:51 +02:00 committed by tykayn
parent 1ed74c2e2f
commit 381f378db4
9 changed files with 1678 additions and 195 deletions

View file

@ -56,6 +56,11 @@
<i class="bi bi-graph-up"></i> Scores de décrépitude
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki_rankings' %}active{% endif %}" href="{{ path('app_admin_wiki_rankings') }}">
<i class="bi bi-bar-chart-line"></i> Évolution des classements
</a>
</li>
</ul>
</div>
</div>

View file

@ -105,9 +105,32 @@
<div class="container mt-4">
{% include 'admin/_wiki_navigation.html.twig' %}
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
{% if prev_page is defined and prev_page is not null %}
<a href="{{ path('app_admin_wiki_compare', {'key': prev_page.key}) }}" class="btn btn-outline-primary">
<i class="bi bi-arrow-left"></i> Page précédente: {{ prev_page.key }}
</a>
{% else %}
<button class="btn btn-outline-secondary" disabled>
<i class="bi bi-arrow-left"></i> Pas de page précédente
</button>
{% endif %}
</div>
<div>
{% if next_page is defined and next_page is not null %}
<a href="{{ path('app_admin_wiki_compare', {'key': next_page.key}) }}" class="btn btn-outline-primary">
Page suivante: {{ next_page.key }} <i class="bi bi-arrow-right"></i>
</a>
{% else %}
<button class="btn btn-outline-secondary" disabled>
Pas de page suivante <i class="bi bi-arrow-right"></i>
</button>
{% endif %}
</div>
</div>
<h1>Comparaison Wiki OpenStreetMap - {{ key }}
{% if en_page.is_specific_page is defined and en_page.is_specific_page %}
<a href="{{ fr_page.url|default('https://wiki.openstreetmap.org/wiki/FR:' ~ key) }}">fr</a>
<a href="{{ en_page.url }}">en</a>
@ -117,7 +140,6 @@
{% endif %}
</h1>
<p class="lead">
{% if en_page.is_specific_page is defined and en_page.is_specific_page %}
Comparaison détaillée des pages wiki en français et en anglais pour "{{ key }}".
@ -159,6 +181,63 @@
</div>
</div>
{% if history_data is defined and history_data is not empty %}
<div class="card mb-4">
<div class="card-header bg-success text-white">
<h2>Évolution du classement</h2>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
<canvas id="rankingEvolutionChart" width="800" height="300"></canvas>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Date</th>
<th>Score de décrépitude</th>
<th>Différence de mots</th>
<th>Différence de sections</th>
<th>Différence de liens</th>
<th>Différence d'images</th>
</tr>
</thead>
<tbody>
{% for entry in history_data %}
<tr>
<td>{{ entry.date }}</td>
<td>
<div class="progress" style="height: 20px;">
{% set score = entry.metrics.staleness_score %}
{% set score_class = score > 70 ? 'bg-danger' : (score > 40 ? 'bg-warning' : 'bg-success') %}
<div class="progress-bar {{ score_class }}" role="progressbar"
style="width: {{ score }}%;"
aria-valuenow="{{ score }}"
aria-valuemin="0"
aria-valuemax="100">
{{ score }}
</div>
</div>
</td>
<td>{{ entry.metrics.word_diff }}</td>
<td>{{ entry.metrics.section_diff }}</td>
<td>{{ entry.metrics.link_diff }}</td>
<td>{{ entry.metrics.media_diff }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{# suggestions de grammalecte #}
{% if fr_page is defined and fr_page is not null %}
{% if detailed_comparison is defined and detailed_comparison is not null and detailed_comparison.section_comparison is defined and detailed_comparison.section_comparison is not null %}
@ -225,6 +304,59 @@
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-success text-white">
<h3>Sections communes</h3>
<span class="badge bg-light text-dark">
{% if detailed_comparison['section_comparison']['common'] is defined %}
{{ detailed_comparison['section_comparison']['common']|length }} sections communes
{% else %}
0 sections communes
{% endif %}
</span>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th class="bg-primary text-white">Section anglaise</th>
<th class="bg-info text-white">Section française</th>
</tr>
</thead>
<tbody>
{% if detailed_comparison['section_comparison']['common'] is defined and detailed_comparison['section_comparison']['common'] is iterable %}
{% for section in detailed_comparison['section_comparison']['common'] %}
<tr>
<td class="title-level-{{ section.en.level|default(1) }}">
<span class="badge bg-secondary">h{{ section.en.level|default(1) }}</span>
{% if section.en.title is defined and section.en.title is not empty %}
<span class="section-title">{{ section.en.title }}</span>
{% endif %}
</td>
<td class="title-level-{{ section.fr.level|default(1) }}">
<span class="badge bg-secondary">h{{ section.fr.level|default(1) }}</span>
{% if section.fr.title is defined and section.fr.title is not empty %}
<span class="section-title">{{ section.fr.title }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="2" class="text-center">Aucune section commune trouvée</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="text-center mt-3">
{% if detailed_comparison is defined and detailed_comparison is not null and detailed_comparison.section_comparison is defined and detailed_comparison.section_comparison is not null %}
<button class="btn btn-outline-secondary copy-btn" data-content="different-sections">
@ -519,142 +651,311 @@
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-success text-white">
<h3>Images communes</h3>
<span class="badge bg-light text-dark">
{% if detailed_comparison.media_comparison.common is defined %}
{{ detailed_comparison.media_comparison.common|length }} images communes
{% else %}
0 images communes
{% endif %}
</span>
</div>
<div class="card-body">
<div class="row">
{% if detailed_comparison.media_comparison.common is defined and detailed_comparison.media_comparison.common is iterable %}
{% for media in detailed_comparison.media_comparison.common %}
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-header bg-primary text-white">
<h5>Version anglaise</h5>
</div>
<div class="card-body">
<img src="{{ media.en.src }}" class="img-fluid"
alt="{{ media.en.alt }}"
style="max-height: 150px; object-fit: contain;">
<p class="card-text small mt-2">{{ media.en.alt }}</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-header bg-info text-white">
<h5>Version française</h5>
</div>
<div class="card-body">
<img src="{{ media.fr.src }}" class="img-fluid"
alt="{{ media.fr.alt }}"
style="max-height: 150px; object-fit: contain;">
<p class="card-text small mt-2">{{ media.fr.alt }}</p>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="col-12">
<div class="alert alert-info">
Aucune image commune trouvée entre les versions anglaise et française.
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{# {% if detailed_comparison and detailed_comparison.link_comparison %} #}
{# <div class="card mb-4"> #}
{# <div class="card-header"> #}
{# <h2>Comparaison des liens</h2> #}
{# </div> #}
{# <div class="card-body"> #}
{# <div class="row"> #}
{# <div class="col-md-6"> #}
{# <div class="card h-100"> #}
{# <div class="card-header bg-primary text-white"> #}
{# <h3>Liens en anglais</h3> #}
{# <span class="badge bg-light text-dark">{{ en_page.link_count }} liens</span> #}
{# </div> #}
{# <div class="card-body"> #}
{% if detailed_comparison and detailed_comparison.link_comparison %}
<div class="card mb-4">
<div class="card-header">
<h2>Comparaison des liens</h2>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-primary text-white">
<h3>Liens uniquement en anglais</h3>
<span class="badge bg-light text-dark">
{% if detailed_comparison.link_comparison.en_only is defined %}
{{ detailed_comparison.link_comparison.en_only|length }} liens
{% else %}
0 liens
{% endif %}
</span>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Texte du lien</th>
<th>URL</th>
</tr>
</thead>
<tbody>
{% if detailed_comparison.link_comparison.en_only is defined and detailed_comparison.link_comparison.en_only is iterable %}
{% for link in detailed_comparison.link_comparison.en_only %}
<tr>
<td>{{ link.text }}</td>
<td>
<a href="{{ link.href }}" target="_blank" class="small">
{{ link.href|slice(0, 30) }}{% if link.href|length > 30 %}...{% endif %}
</a>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="2" class="text-center">Aucun lien uniquement en anglais</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-info text-white">
<h3>Liens uniquement en français</h3>
<span class="badge bg-light text-dark">
{% if detailed_comparison.link_comparison.fr_only is defined %}
{{ detailed_comparison.link_comparison.fr_only|length }} liens
{% else %}
0 liens
{% endif %}
</span>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Texte du lien</th>
<th>URL</th>
</tr>
</thead>
<tbody>
{% if detailed_comparison.link_comparison.fr_only is defined and detailed_comparison.link_comparison.fr_only is iterable %}
{% for link in detailed_comparison.link_comparison.fr_only %}
<tr>
<td>{{ link.text }}</td>
<td>
<a href="{{ link.href }}" target="_blank" class="small">
{{ link.href|slice(0, 30) }}{% if link.href|length > 30 %}...{% endif %}
</a>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="2" class="text-center">Aucun lien uniquement en français</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-success text-white">
<h3>Liens communs</h3>
<span class="badge bg-light text-dark">
{% if detailed_comparison.link_comparison.common is defined %}
{{ detailed_comparison.link_comparison.common|length }} liens communs
{% else %}
0 liens communs
{% endif %}
</span>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th class="bg-primary text-white">Texte EN</th>
<th class="bg-primary text-white">URL EN</th>
<th class="bg-info text-white">Texte FR</th>
<th class="bg-info text-white">URL FR</th>
</tr>
</thead>
<tbody>
{% if detailed_comparison.link_comparison.common is defined and detailed_comparison.link_comparison.common is iterable %}
{% for link in detailed_comparison.link_comparison.common %}
<tr>
<td>{{ link.en.text }}</td>
<td>
<a href="{{ link.en.href }}" target="_blank" class="small">
{{ link.en.href|slice(0, 30) }}{% if link.en.href|length > 30 %}...{% endif %}
</a>
</td>
<td>{{ link.fr.text }}</td>
<td>
<a href="{{ link.fr.href }}" target="_blank" class="small">
{{ link.fr.href|slice(0, 30) }}{% if link.fr.href|length > 30 %}...{% endif %}
</a>
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="4" class="text-center">Aucun lien commun trouvé</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{# <h4>Comparaison des liens côte à côte</h4> #}
{# <div class="table-responsive"> #}
{# <table class="table table-sm"> #}
{# <thead> #}
{# <tr> #}
{# <th class="bg-primary text-white">Texte EN</th> #}
{# <th class="bg-primary text-white">URL EN</th> #}
{# <th class="bg-info text-white">Texte FR</th> #}
{# <th class="bg-info text-white">URL FR</th> #}
{# </tr> #}
{# </thead> #}
{# <tbody> #}
{# {% set en_links = detailed_comparison.link_comparison.en_only %} #}
{# {% set fr_links = detailed_comparison.link_comparison.fr_only %} #}
{# {% set max_links = max(en_links|length, fr_links|length) %} #}
{# {% for i in 0..(max_links - 1) %} #}
{# <tr> #}
{# {% if i < en_links|length %} #}
{# <td class="bg-light">{{ en_links[i].text }}</td> #}
{# <td class="bg-light"><a href="{{ en_links[i].href }}" #}
{# target="_blank" #}
{# class="small">{{ en_links[i].href|slice(0, 30) }} #}
{# ...</a></td> #}
{# {% else %} #}
{# <td class="bg-light"></td> #}
{# <td class="bg-light"></td> #}
{# {% endif %} #}
{# {% if i < fr_links|length %} #}
{# <td>{{ fr_links[i].text }}</td> #}
{# <td><a href="{{ fr_links[i].href }}" target="_blank" #}
{# class="small">{{ fr_links[i].href|slice(0, 30) }} #}
{# ...</a></td> #}
{# {% else %} #}
{# <td></td> #}
{# <td></td> #}
{# {% endif %} #}
{# </tr> #}
{# {% endfor %} #}
{# </tbody> #}
{# </table> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# {% endif %} #}
{# {% if detailed_comparison and detailed_comparison.category_comparison %} #}
{# <div class="card mb-4"> #}
{# <div class="card-header"> #}
{# <h2>Comparaison des catégories</h2> #}
{# </div> #}
{# <div class="card-body"> #}
{# <div class="row"> #}
{# <div class="col-md-6"> #}
{# <div class="card h-100"> #}
{# <div class="card-header bg-primary text-white"> #}
{# <h3>Catégories en anglais</h3> #}
{# <span class="badge bg-light text-dark"> #}
{# {{ (detailed_comparison.category_comparison.en_only|length + detailed_comparison.category_comparison.common|length) }} catégories #}
{# </span> #}
{# </div> #}
{# <div class="card-body"> #}
{# <h4>Catégories communes #}
{# ({{ detailed_comparison.category_comparison.common|length }})</h4> #}
{# <ul class="list-group mb-3"> #}
{# {% for category in detailed_comparison.category_comparison.common %} #}
{# <li class="list-group-item">{{ category }}</li> #}
{# {% endfor %} #}
{# </ul> #}
{# <h4>Catégories uniquement en anglais #}
{# ({{ detailed_comparison.category_comparison.en_only|length }})</h4> #}
{# <ul class="list-group"> #}
{# {% for category in detailed_comparison.category_comparison.en_only %} #}
{# <li class="list-group-item list-group-item-warning">{{ category }}</li> #}
{# {% endfor %} #}
{# </ul> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# <div class="col-md-6"> #}
{# <div class="card h-100"> #}
{# <div class="card-header bg-info text-white"> #}
{# <h3>Catégories en français</h3> #}
{# <span class="badge bg-light text-dark"> #}
{# {{ (detailed_comparison.category_comparison.fr_only|length + detailed_comparison.category_comparison.common|length) }} catégories #}
{# </span> #}
{# </div> #}
{# <div class="card-body"> #}
{# <h4>Catégories communes #}
{# ({{ detailed_comparison.category_comparison.common|length }})</h4> #}
{# <ul class="list-group mb-3"> #}
{# {% for category in detailed_comparison.category_comparison.common %} #}
{# <li class="list-group-item">{{ category }}</li> #}
{# {% endfor %} #}
{# </ul> #}
{# <h4>Catégories uniquement en français #}
{# ({{ detailed_comparison.category_comparison.fr_only|length }})</h4> #}
{# <ul class="list-group"> #}
{# {% for category in detailed_comparison.category_comparison.fr_only %} #}
{# <li class="list-group-item list-group-item-info">{{ category }}</li> #}
{# {% endfor %} #}
{# </ul> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# {% endif %} #}
{% if detailed_comparison and detailed_comparison.category_comparison %}
<div class="card mb-4">
<div class="card-header">
<h2>Comparaison des catégories</h2>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-primary text-white">
<h3>Catégories uniquement en anglais</h3>
<span class="badge bg-light text-dark">
{% if detailed_comparison.category_comparison.en_only is defined %}
{{ detailed_comparison.category_comparison.en_only|length }} catégories
{% else %}
0 catégories
{% endif %}
</span>
</div>
<div class="card-body">
<ul class="list-group">
{% if detailed_comparison.category_comparison.en_only is defined and detailed_comparison.category_comparison.en_only is iterable and detailed_comparison.category_comparison.en_only|length > 0 %}
{% for category in detailed_comparison.category_comparison.en_only %}
<li class="list-group-item list-group-item-warning">{{ category }}</li>
{% endfor %}
{% else %}
<li class="list-group-item text-center">Aucune catégorie uniquement en anglais</li>
{% endif %}
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-info text-white">
<h3>Catégories uniquement en français</h3>
<span class="badge bg-light text-dark">
{% if detailed_comparison.category_comparison.fr_only is defined %}
{{ detailed_comparison.category_comparison.fr_only|length }} catégories
{% else %}
0 catégories
{% endif %}
</span>
</div>
<div class="card-body">
<ul class="list-group">
{% if detailed_comparison.category_comparison.fr_only is defined and detailed_comparison.category_comparison.fr_only is iterable and detailed_comparison.category_comparison.fr_only|length > 0 %}
{% for category in detailed_comparison.category_comparison.fr_only %}
<li class="list-group-item list-group-item-info">{{ category }}</li>
{% endfor %}
{% else %}
<li class="list-group-item text-center">Aucune catégorie uniquement en français</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-success text-white">
<h3>Catégories communes</h3>
<span class="badge bg-light text-dark">
{% if detailed_comparison.category_comparison.common is defined %}
{{ detailed_comparison.category_comparison.common|length }} catégories communes
{% else %}
0 catégories communes
{% endif %}
</span>
</div>
<div class="card-body">
<ul class="list-group">
{% if detailed_comparison.category_comparison.common is defined and detailed_comparison.category_comparison.common is iterable and detailed_comparison.category_comparison.common|length > 0 %}
{% for category in detailed_comparison.category_comparison.common %}
<li class="list-group-item">{{ category }}</li>
{% endfor %}
{% else %}
<li class="list-group-item text-center">Aucune catégorie commune trouvée</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{# {% else %} #}
{# <div class="card mb-4"> #}
{# <div class="card-header bg-warning text-dark"> #}
@ -796,6 +1097,41 @@
</div>
</div>
{% if staleness_distribution is defined and staleness_distribution is not null %}
<div class="card mb-4">
<div class="card-header">
<h2>Répartition des scores de décrépitude</h2>
</div>
<div class="card-body">
<p>Ce graphique montre la répartition des scores de décrépitude pour toutes les pages wiki et où se situe la page courante :</p>
<div class="mb-4">
<canvas id="distributionChart" width="400" height="200"></canvas>
</div>
<div class="alert alert-info">
<p><strong>Statistiques de décrépitude :</strong></p>
<div class="row">
<div class="col-md-6">
<ul>
<li><strong>Nombre total de pages :</strong> {{ staleness_distribution.totalPages }}</li>
<li><strong>Score minimum :</strong> {{ staleness_distribution.min|round(2) }}</li>
<li><strong>Score maximum :</strong> {{ staleness_distribution.max|round(2) }}</li>
</ul>
</div>
<div class="col-md-6">
<ul>
<li><strong>Score moyen :</strong> {{ staleness_distribution.avg|round(2) }}</li>
<li><strong>Score médian :</strong> {{ staleness_distribution.median|round(2) }}</li>
<li><strong>Score de cette page :</strong> {{ staleness_distribution.currentPageScore|round(2) }}</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if history_data is defined and history_data|length > 0 %}
<div class="card mb-4">
<div class="card-header">
@ -1078,6 +1414,162 @@
});
});
// Create ranking evolution chart if the element exists
const rankingEvolutionChartElement = document.getElementById('rankingEvolutionChart');
const historyData = {{ history_data|json_encode|raw }};
if (rankingEvolutionChartElement && historyData && historyData.length > 0) {
// Format the data for the chart
const dates = historyData.map(entry => entry.date);
const stalenessScores = historyData.map(entry => entry.metrics.staleness_score || 0);
const wordDiffs = historyData.map(entry => entry.metrics.word_diff || 0);
const sectionDiffs = historyData.map(entry => entry.metrics.section_diff || 0);
const linkDiffs = historyData.map(entry => entry.metrics.link_diff || 0);
// Create the chart
new Chart(rankingEvolutionChartElement, {
type: 'line',
data: {
labels: dates,
datasets: [
{
label: 'Score de décrépitude',
data: stalenessScores,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: false,
tension: 0.1
},
{
label: 'Différence de mots / 10',
data: wordDiffs.map(val => val / 10),
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
fill: false,
tension: 0.1
},
{
label: 'Différence de sections',
data: sectionDiffs,
borderColor: 'rgba(255, 206, 86, 1)',
backgroundColor: 'rgba(255, 206, 86, 0.2)',
fill: false,
tension: 0.1
},
{
label: 'Différence de liens',
data: linkDiffs,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: false,
tension: 0.1
}
]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Évolution des métriques au fil du temps'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Create distribution histogram if the element exists
const distributionChartElement = document.getElementById('distributionChart');
if (distributionChartElement) {
// Get staleness distribution data from the template
const distributionData = {{ staleness_distribution|json_encode|raw }};
if (distributionData) {
// Prepare data for the histogram
const labels = distributionData.binLabels;
const counts = distributionData.bins;
const currentPageBin = distributionData.currentPageBin;
// Create colors array with the current page bin highlighted
const colors = counts.map((_, index) =>
index === currentPageBin ? 'rgba(255, 99, 132, 0.8)' : 'rgba(54, 162, 235, 0.6)'
);
// Create the histogram
const ctx = distributionChartElement.getContext('2d');
const distributionChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Nombre de pages',
data: counts,
backgroundColor: colors,
borderColor: colors.map(color => color.replace('0.6', '1').replace('0.8', '1')),
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Nombre de pages'
}
},
x: {
title: {
display: true,
text: 'Score de décrépitude'
}
}
},
plugins: {
title: {
display: true,
text: 'Répartition des scores de décrépitude (la page courante est en rouge)'
},
tooltip: {
callbacks: {
title: function(tooltipItems) {
const item = tooltipItems[0];
const binRange = item.label;
return `Score: ${binRange}`;
},
label: function(context) {
let label = 'Nombre de pages: ' + context.raw;
if (context.dataIndex === currentPageBin) {
label += ' (inclut la page courante)';
}
return label;
},
afterLabel: function(context) {
if (context.dataIndex === currentPageBin) {
return `Score de cette page: ${distributionData.currentPageScore.toFixed(2)}`;
}
return '';
}
}
},
legend: {
display: false
}
}
}
});
}
}
// Create history chart if the element exists
const historyChartElement = document.getElementById('historyChart');
if (historyChartElement) {

View file

@ -84,50 +84,72 @@ python3 wiki_compare.py</code></pre>
{# {{ page.reason }}#}
</td>
<td class="text-center">
{% if page.word_diff > 0 %}
<span class="badge bg-danger">{{ page.word_diff }}</span>
{% elseif page.word_diff < 0 %}
<span class="badge bg-success">{{ page.word_diff }}</span>
{% if page.word_diff is defined %}
{% if page.word_diff > 0 %}
<span class="badge bg-danger">{{ page.word_diff }}</span>
{% elseif page.word_diff < 0 %}
<span class="badge bg-success">{{ page.word_diff }}</span>
{% else %}
<span class="badge bg-secondary">0</span>
{% endif %}
{% else %}
<span class="badge bg-secondary">0</span>
<span class="badge bg-secondary">N/A</span>
{% endif %}
</td>
<td class="text-center">
{% if page.section_diff > 0 %}
<span class="badge bg-danger">{{ page.section_diff }}</span>
{% elseif page.section_diff < 0 %}
<span class="badge bg-success">{{ page.section_diff }}</span>
{% if page.section_diff is defined %}
{% if page.section_diff > 0 %}
<span class="badge bg-danger">{{ page.section_diff }}</span>
{% elseif page.section_diff < 0 %}
<span class="badge bg-success">{{ page.section_diff }}</span>
{% else %}
<span class="badge bg-secondary">0</span>
{% endif %}
{% else %}
<span class="badge bg-secondary">0</span>
<span class="badge bg-secondary">N/A</span>
{% endif %}
</td>
<td class="text-center">
{% if page.link_diff > 0 %}
<span class="badge bg-danger">{{ page.link_diff }}</span>
{% elseif page.link_diff < 0 %}
<span class="badge bg-success">{{ page.link_diff }}</span>
{% if page.link_diff is defined %}
{% if page.link_diff > 0 %}
<span class="badge bg-danger">{{ page.link_diff }}</span>
{% elseif page.link_diff < 0 %}
<span class="badge bg-success">{{ page.link_diff }}</span>
{% else %}
<span class="badge bg-secondary">0</span>
{% endif %}
{% else %}
<span class="badge bg-secondary">0</span>
<span class="badge bg-secondary">N/A</span>
{% endif %}
</td>
<td class="text-center">
<div class="progress" style="height: 20px;">
{% set score_class = page.staleness_score > 70 ? 'bg-danger' : (page.staleness_score > 40 ? 'bg-warning' : 'bg-success') %}
<div class="progress-bar {{ score_class }}" role="progressbar"
style="width: {{ page.staleness_score }}%;"
aria-valuenow="{{ page.staleness_score }}"
aria-valuemin="0"
aria-valuemax="100">
{{ page.staleness_score }}
{% if page.staleness_score is defined %}
<div class="progress" style="height: 20px;">
{% set score_class = page.staleness_score > 70 ? 'bg-danger' : (page.staleness_score > 40 ? 'bg-warning' : 'bg-success') %}
<div class="progress-bar {{ score_class }}" role="progressbar"
style="width: {{ page.staleness_score }}%;"
aria-valuenow="{{ page.staleness_score }}"
aria-valuemin="0"
aria-valuemax="100">
{{ page.staleness_score }}
</div>
</div>
</div>
{% else %}
<span class="badge bg-secondary">N/A</span>
{% endif %}
</td>
<td class="text-center">
<div class="btn-group" role="group">
<a href="{{ page.en_page.url }}" target="_blank"
class="btn btn-sm btn-outline-primary" title="Version anglaise">
<i class="bi bi-translate"></i> EN
</a>
{% if page.url is defined and page.url %}
<a href="{{ page.url }}" target="_blank"
class="btn btn-sm btn-outline-primary" title="Version anglaise">
<i class="bi bi-translate"></i> EN
</a>
{% else %}
<button class="btn btn-sm btn-outline-secondary" disabled>
<i class="bi bi-translate"></i> EN (URL manquante)
</button>
{% endif %}
{% if page.fr_page is defined and page.fr_page %}
{% if page.fr_page.url is defined %}
<a href="{{ page.fr_page.url }}" target="_blank"
@ -207,9 +229,9 @@ python3 wiki_compare.py</code></pre>
<tr>
<td>
<div class="d-flex align-items-center">
{% if page.en_page.description_img_url is defined and page.en_page.description_img_url %}
{% if page.description_img_url is defined and page.description_img_url %}
<div class="me-3">
<img src="{{ page.en_page.description_img_url }}"
<img src="{{ page.description_img_url }}"
alt="{% if page.key is defined %}{{ page.key }}{% elseif page.title is defined %}{{ page.title }}{% else %}Image{% endif %}"
style="max-width: 80px; max-height: 60px; object-fit: contain;">
</div>
@ -223,23 +245,33 @@ python3 wiki_compare.py</code></pre>
{# {{ page.reason }}#}
</td>
<td>
<div class="progress" style="height: 20px;">
{% set score_class = page.staleness_score > 70 ? 'bg-danger' : (page.staleness_score > 40 ? 'bg-warning' : 'bg-success') %}
<div class="progress-bar {{ score_class }}" role="progressbar"
style="width: {{ page.staleness_score }}%;"
aria-valuenow="{{ page.staleness_score }}"
aria-valuemin="0"
aria-valuemax="100">
{{ page.staleness_score }}
{% if page.staleness_score is defined %}
<div class="progress" style="height: 20px;">
{% set score_class = page.staleness_score > 70 ? 'bg-danger' : (page.staleness_score > 40 ? 'bg-warning' : 'bg-success') %}
<div class="progress-bar {{ score_class }}" role="progressbar"
style="width: {{ page.staleness_score }}%;"
aria-valuenow="{{ page.staleness_score }}"
aria-valuemin="0"
aria-valuemax="100">
{{ page.staleness_score }}
</div>
</div>
</div>
{% else %}
<span class="badge bg-secondary">N/A</span>
{% endif %}
</td>
<td class="text-center">
<div class="btn-group" role="group">
<a href="{{ page.en_page.url }}" target="_blank"
class="btn btn-sm btn-outline-primary" title="Version anglaise">
<i class="bi bi-translate"></i> EN
</a>
{% if page.url is defined and page.url %}
<a href="{{ page.url }}" target="_blank"
class="btn btn-sm btn-outline-primary" title="Version anglaise">
<i class="bi bi-translate"></i> EN
</a>
{% else %}
<button class="btn btn-sm btn-outline-secondary" disabled>
<i class="bi bi-translate"></i> EN (URL manquante)
</button>
{% endif %}
{% if page.fr_page is defined and page.fr_page %}
{% if page.fr_page.url is defined %}
<a href="{{ page.fr_page.url }}" target="_blank"
@ -320,21 +352,26 @@ python3 wiki_compare.py</code></pre>
{% else %}
labels.push("Page sans clé");
{% endif %}
scores.push({{ page.staleness_score }});
{% if page.staleness_score is defined %}
scores.push({{ page.staleness_score }});
// Set color based on score
{% if page.staleness_score > 80 %}
colors.push('rgba(220, 53, 69, 0.7)'); // danger (red)
{% elseif page.staleness_score > 60 %}
colors.push('rgba(232, 113, 55, 0.7)'); // dark orange
{% elseif page.staleness_score > 40 %}
colors.push('rgba(255, 153, 0, 0.7)'); // orange
{% elseif page.staleness_score > 20 %}
colors.push('rgba(255, 193, 7, 0.7)'); // warning (yellow)
{% elseif page.staleness_score > 10 %}
colors.push('rgba(140, 195, 38, 0.7)'); // light green
// Set color based on score
{% if page.staleness_score > 80 %}
colors.push('rgba(220, 53, 69, 0.7)'); // danger (red)
{% elseif page.staleness_score > 60 %}
colors.push('rgba(232, 113, 55, 0.7)'); // dark orange
{% elseif page.staleness_score > 40 %}
colors.push('rgba(255, 153, 0, 0.7)'); // orange
{% elseif page.staleness_score > 20 %}
colors.push('rgba(255, 193, 7, 0.7)'); // warning (yellow)
{% elseif page.staleness_score > 10 %}
colors.push('rgba(140, 195, 38, 0.7)'); // light green
{% else %}
colors.push('rgba(25, 135, 84, 0.7)'); // success (green)
{% endif %}
{% else %}
colors.push('rgba(25, 135, 84, 0.7)'); // success (green)
scores.push(0);
colors.push('rgba(108, 117, 125, 0.7)'); // secondary (gray)
{% endif %}
{% endfor %}

View file

@ -0,0 +1,566 @@
{% extends 'base.html.twig' %}
{% block title %}Évolution des classements Wiki OSM{% endblock %}
{% block body %}
<div class="container mt-4">
{% include 'admin/_wiki_navigation.html.twig' %}
<div class="d-flex justify-content-between align-items-center mb-3">
<h1>Évolution des classements Wiki OSM</h1>
{% if last_updated %}
<div class="text-muted">
Dernière mise à jour: {{ last_updated|date('d/m/Y H:i') }}
</div>
{% endif %}
</div>
{% if not json_exists %}
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i> Aucune donnée de classement n'est disponible. Veuillez exécuter le script de scraping pour générer les données.
</div>
{% else %}
<!-- Global Metrics Section -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h2>Métriques globales</h2>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<canvas id="globalMetricsChart" width="400" height="200"></canvas>
</div>
<div class="col-md-6">
<canvas id="stalenessDistributionChart" width="400" height="200"></canvas>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<h3>Évolution des métriques globales</h3>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Date</th>
<th>Pages totales</th>
<th>Score moyen</th>
<th>Sections (moy.)</th>
<th>Mots (moy.)</th>
<th>Liens (moy.)</th>
<th>Images (moy.)</th>
<th>Catégories (moy.)</th>
</tr>
</thead>
<tbody>
{% for timestamp in timestamps %}
{% if global_metrics[timestamp] is defined %}
<tr>
<td>{{ timestamp|date('d/m/Y') }}</td>
<td>{{ global_metrics[timestamp].total_pages }}</td>
<td>
<div class="progress" style="height: 20px;">
{% set score = global_metrics[timestamp].avg_staleness %}
{% set score_class = score > 70 ? 'bg-danger' : (score > 40 ? 'bg-warning' : 'bg-success') %}
<div class="progress-bar {{ score_class }}" role="progressbar"
style="width: {{ score }}%;"
aria-valuenow="{{ score }}"
aria-valuemin="0"
aria-valuemax="100">
{{ score }}
</div>
</div>
</td>
<td>{{ global_metrics[timestamp].avg_sections }}</td>
<td>{{ global_metrics[timestamp].avg_words }}</td>
<td>{{ global_metrics[timestamp].avg_links }}</td>
<td>{{ global_metrics[timestamp].avg_images }}</td>
<td>{{ global_metrics[timestamp].avg_categories }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Page Rankings Section -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h2>Classement des pages</h2>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text">Filtrer</span>
<input type="text" id="pageFilter" class="form-control" placeholder="Rechercher une page...">
</div>
</div>
<div class="col-md-3">
<select id="metricSelector" class="form-select">
<option value="staleness_score">Score de décrépitude</option>
<option value="word_diff">Différence de mots</option>
<option value="section_diff">Différence de sections</option>
<option value="link_diff">Différence de liens</option>
<option value="media_diff">Différence d'images</option>
</select>
</div>
<div class="col-md-3">
<select id="sortOrder" class="form-select">
<option value="desc">Décroissant</option>
<option value="asc">Croissant</option>
</select>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover" id="pagesTable">
<thead>
<tr>
<th>Page</th>
<th>Score actuel</th>
<th>Évolution</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for key, page in pages %}
<tr data-page-key="{{ key }}">
<td>{{ page.title }}</td>
<td>
{% set latest_timestamp = timestamps|last %}
{% if page.metrics[latest_timestamp] is defined %}
{% set latest_score = page.metrics[latest_timestamp].staleness_score %}
<div class="progress" style="height: 20px;">
{% set score_class = latest_score > 70 ? 'bg-danger' : (latest_score > 40 ? 'bg-warning' : 'bg-success') %}
<div class="progress-bar {{ score_class }}" role="progressbar"
style="width: {{ latest_score }}%;"
aria-valuenow="{{ latest_score }}"
aria-valuemin="0"
aria-valuemax="100">
{{ latest_score }}
</div>
</div>
{% else %}
<span class="badge bg-secondary">N/A</span>
{% endif %}
</td>
<td>
<canvas class="trend-chart" data-page-key="{{ key }}" width="200" height="50"></canvas>
</td>
<td>
<a href="{{ path('app_admin_wiki_compare', {'key': key}) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-arrows-angle-expand"></i> Comparer
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Page Detail Modal -->
<div class="modal fade" id="pageDetailModal" tabindex="-1" aria-labelledby="pageDetailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="pageDetailModalLabel">Détails de la page</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h3 id="modalPageTitle"></h3>
<div class="row">
<div class="col-md-12">
<canvas id="pageDetailChart" width="700" height="300"></canvas>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<h4>Historique des métriques</h4>
<div class="table-responsive">
<table class="table table-sm" id="pageMetricsTable">
<thead>
<tr>
<th>Date</th>
<th>Score</th>
<th>Mots</th>
<th>Sections</th>
<th>Liens</th>
<th>Images</th>
</tr>
</thead>
<tbody>
<!-- Filled dynamically by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
<a href="#" id="comparePageBtn" class="btn btn-primary">Comparer</a>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
{% if json_exists %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Data from controller
const timestamps = {{ timestamps|json_encode|raw }};
const pages = {{ pages|json_encode|raw }};
const globalMetrics = {{ global_metrics|json_encode|raw }};
// Format dates for display
const formatDates = timestamps.map(ts => {
const date = new Date(ts);
return date.toLocaleDateString('fr-FR');
});
// Global metrics chart
const globalMetricsCtx = document.getElementById('globalMetricsChart').getContext('2d');
const globalMetricsData = {
labels: formatDates,
datasets: [
{
label: 'Score moyen',
data: timestamps.map(ts => globalMetrics[ts]?.avg_staleness || 0),
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: false,
tension: 0.1
},
{
label: 'Sections (moy.)',
data: timestamps.map(ts => globalMetrics[ts]?.avg_sections || 0),
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
fill: false,
tension: 0.1
},
{
label: 'Mots (moy. / 10)',
data: timestamps.map(ts => (globalMetrics[ts]?.avg_words || 0) / 10),
borderColor: 'rgba(255, 206, 86, 1)',
backgroundColor: 'rgba(255, 206, 86, 0.2)',
fill: false,
tension: 0.1
}
]
};
new Chart(globalMetricsCtx, {
type: 'line',
data: globalMetricsData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Évolution des métriques globales'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
// Staleness distribution chart
if (timestamps.length > 0) {
const latestTimestamp = timestamps[timestamps.length - 1];
const latestDistribution = globalMetrics[latestTimestamp]?.staleness_distribution || {};
const stalenessDistCtx = document.getElementById('stalenessDistributionChart').getContext('2d');
const stalenessDistData = {
labels: Object.keys(latestDistribution),
datasets: [{
label: 'Nombre de pages',
data: Object.values(latestDistribution),
backgroundColor: [
'rgba(25, 135, 84, 0.7)',
'rgba(140, 195, 38, 0.7)',
'rgba(255, 193, 7, 0.7)',
'rgba(255, 153, 0, 0.7)',
'rgba(232, 113, 55, 0.7)',
'rgba(220, 53, 69, 0.7)'
],
borderColor: [
'rgba(25, 135, 84, 1)',
'rgba(140, 195, 38, 1)',
'rgba(255, 193, 7, 1)',
'rgba(255, 153, 0, 1)',
'rgba(232, 113, 55, 1)',
'rgba(220, 53, 69, 1)'
],
borderWidth: 1
}]
};
new Chart(stalenessDistCtx, {
type: 'bar',
data: stalenessDistData,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Distribution des scores de décrépitude'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Create small trend charts for each page
const trendCharts = document.querySelectorAll('.trend-chart');
trendCharts.forEach(canvas => {
const pageKey = canvas.dataset.pageKey;
const pageData = pages[pageKey];
if (pageData && pageData.metrics) {
const ctx = canvas.getContext('2d');
const metricValues = timestamps.map(ts => {
return pageData.metrics[ts]?.staleness_score || null;
});
// Filter out null values
const validData = metricValues.filter(val => val !== null);
// Calculate trend (increasing or decreasing)
let trendColor = 'rgba(75, 192, 192, 1)'; // Default: neutral
if (validData.length >= 2) {
const firstValid = validData[0];
const lastValid = validData[validData.length - 1];
if (lastValid > firstValid) {
trendColor = 'rgba(255, 99, 132, 1)'; // Red: getting worse
} else if (lastValid < firstValid) {
trendColor = 'rgba(75, 192, 192, 1)'; // Green: getting better
}
}
new Chart(ctx, {
type: 'line',
data: {
labels: formatDates,
datasets: [{
data: metricValues,
borderColor: trendColor,
backgroundColor: trendColor.replace('1)', '0.2)'),
tension: 0.1,
pointRadius: 0,
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
enabled: true
}
},
scales: {
x: {
display: false
},
y: {
display: false,
min: 0,
max: 100
}
}
}
});
}
});
// Page filtering
const pageFilter = document.getElementById('pageFilter');
const pagesTable = document.getElementById('pagesTable');
pageFilter.addEventListener('input', function() {
const filterText = this.value.toLowerCase();
const rows = pagesTable.querySelectorAll('tbody tr');
rows.forEach(row => {
const pageKey = row.dataset.pageKey;
const pageTitle = pages[pageKey]?.title.toLowerCase() || '';
if (pageTitle.includes(filterText) || pageKey.toLowerCase().includes(filterText)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
});
// Metric selector and sorting
const metricSelector = document.getElementById('metricSelector');
const sortOrder = document.getElementById('sortOrder');
function sortPages() {
const metric = metricSelector.value;
const order = sortOrder.value;
const tbody = pagesTable.querySelector('tbody');
const rows = Array.from(tbody.querySelectorAll('tr'));
// Get the latest timestamp
const latestTs = timestamps[timestamps.length - 1];
rows.sort((a, b) => {
const keyA = a.dataset.pageKey;
const keyB = b.dataset.pageKey;
const valueA = pages[keyA]?.metrics[latestTs]?.[metric] || 0;
const valueB = pages[keyB]?.metrics[latestTs]?.[metric] || 0;
return order === 'asc' ? valueA - valueB : valueB - valueA;
});
// Clear and re-append rows
rows.forEach(row => tbody.appendChild(row));
}
metricSelector.addEventListener('change', sortPages);
sortOrder.addEventListener('change', sortPages);
// Initial sort
sortPages();
// Page detail modal
const pageDetailModal = new bootstrap.Modal(document.getElementById('pageDetailModal'));
const modalPageTitle = document.getElementById('modalPageTitle');
const comparePageBtn = document.getElementById('comparePageBtn');
const pageMetricsTable = document.getElementById('pageMetricsTable');
let pageDetailChart = null;
// Add click event to table rows
const tableRows = pagesTable.querySelectorAll('tbody tr');
tableRows.forEach(row => {
row.addEventListener('click', function() {
const pageKey = this.dataset.pageKey;
const pageData = pages[pageKey];
if (pageData) {
// Set modal title and compare button link
modalPageTitle.textContent = pageData.title;
comparePageBtn.href = `/wiki/compare/${pageKey}`;
// Fill metrics table
const tbody = pageMetricsTable.querySelector('tbody');
tbody.innerHTML = '';
timestamps.forEach(ts => {
if (pageData.metrics[ts]) {
const metrics = pageData.metrics[ts];
const row = document.createElement('tr');
// Format date
const date = new Date(ts);
const formattedDate = date.toLocaleDateString('fr-FR');
row.innerHTML = `
<td>${formattedDate}</td>
<td>${metrics.staleness_score || 0}</td>
<td>${metrics.word_diff || 0}</td>
<td>${metrics.section_diff || 0}</td>
<td>${metrics.link_diff || 0}</td>
<td>${metrics.media_diff || 0}</td>
`;
tbody.appendChild(row);
}
});
// Create detailed chart
const ctx = document.getElementById('pageDetailChart').getContext('2d');
// Destroy previous chart if exists
if (pageDetailChart) {
pageDetailChart.destroy();
}
pageDetailChart = new Chart(ctx, {
type: 'line',
data: {
labels: formatDates,
datasets: [
{
label: 'Score de décrépitude',
data: timestamps.map(ts => pageData.metrics[ts]?.staleness_score || null),
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: false
},
{
label: 'Différence de mots / 10',
data: timestamps.map(ts => (pageData.metrics[ts]?.word_diff || 0) / 10),
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
fill: false
},
{
label: 'Différence de sections',
data: timestamps.map(ts => pageData.metrics[ts]?.section_diff || null),
borderColor: 'rgba(255, 206, 86, 1)',
backgroundColor: 'rgba(255, 206, 86, 0.2)',
fill: false
},
{
label: 'Différence de liens',
data: timestamps.map(ts => pageData.metrics[ts]?.link_diff || null),
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
fill: false
}
]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: `Évolution des métriques pour ${pageData.title}`
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
// Show modal
pageDetailModal.show();
}
});
});
});
</script>
{% endif %}
{% endblock %}