section zoneplaces sur infos générales
This commit is contained in:
parent
5186eb17bb
commit
738f3eeb9d
4 changed files with 455 additions and 5 deletions
|
|
@ -677,6 +677,103 @@ final class AdminController extends AbstractController
|
||||||
}
|
}
|
||||||
unset($points);
|
unset($points);
|
||||||
|
|
||||||
|
// Préparer les données pour le graphique d'activité récente (ZonePlaces)
|
||||||
|
$chartData = [
|
||||||
|
'dates' => [],
|
||||||
|
'deletions' => [],
|
||||||
|
'creations' => [],
|
||||||
|
'completion' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
$zonePlaces = $stats->getZonePlaces();
|
||||||
|
$changesByDate = [];
|
||||||
|
$thirtyDaysAgo = new \DateTime('-30 days');
|
||||||
|
|
||||||
|
foreach ($zonePlaces as $zp) {
|
||||||
|
$theme = $zp->getTheme();
|
||||||
|
|
||||||
|
// Traiter les suppressions
|
||||||
|
$disappearedList = $zp->getDisappearedList() ?? [];
|
||||||
|
if (is_array($disappearedList)) {
|
||||||
|
foreach ($disappearedList as $obj) {
|
||||||
|
if (isset($obj['noticed_deleted_date'])) {
|
||||||
|
$date = substr($obj['noticed_deleted_date'], 0, 10);
|
||||||
|
if (!isset($changesByDate[$date])) {
|
||||||
|
$changesByDate[$date] = ['deletions' => [], 'creations' => []];
|
||||||
|
}
|
||||||
|
if (!isset($changesByDate[$date]['deletions'][$theme])) {
|
||||||
|
$changesByDate[$date]['deletions'][$theme] = [];
|
||||||
|
}
|
||||||
|
$changesByDate[$date]['deletions'][$theme][] = $obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traiter les créations/modifications récentes
|
||||||
|
$currentList = $zp->getCurrentList() ?? [];
|
||||||
|
if (is_array($currentList)) {
|
||||||
|
foreach ($currentList as $obj) {
|
||||||
|
if (isset($obj['timestamp'])) {
|
||||||
|
try {
|
||||||
|
$timestamp = new \DateTime($obj['timestamp']);
|
||||||
|
if ($timestamp >= $thirtyDaysAgo) {
|
||||||
|
$date = $timestamp->format('Y-m-d');
|
||||||
|
if (!isset($changesByDate[$date])) {
|
||||||
|
$changesByDate[$date] = ['deletions' => [], 'creations' => []];
|
||||||
|
}
|
||||||
|
if (!isset($changesByDate[$date]['creations'][$theme])) {
|
||||||
|
$changesByDate[$date]['creations'][$theme] = [];
|
||||||
|
}
|
||||||
|
$changesByDate[$date]['creations'][$theme][] = $obj;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Ignorer les timestamps invalides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer une copie triée par date croissante pour le graphique
|
||||||
|
$changesByDateSorted = $changesByDate;
|
||||||
|
ksort($changesByDateSorted);
|
||||||
|
|
||||||
|
// Compter les créations et suppressions par jour
|
||||||
|
foreach ($changesByDateSorted as $date => $changes) {
|
||||||
|
$chartData['dates'][] = $date;
|
||||||
|
$deletionsCount = 0;
|
||||||
|
$creationsCount = 0;
|
||||||
|
|
||||||
|
if (isset($changes['deletions'])) {
|
||||||
|
foreach ($changes['deletions'] as $theme => $objects) {
|
||||||
|
$deletionsCount += count($objects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($changes['creations'])) {
|
||||||
|
foreach ($changes['creations'] as $theme => $objects) {
|
||||||
|
$creationsCount += count($objects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$chartData['deletions'][] = $deletionsCount;
|
||||||
|
$chartData['creations'][] = $creationsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les données de complétion générale de la ville si disponibles
|
||||||
|
$completionByDate = [];
|
||||||
|
foreach ($followups as $fu) {
|
||||||
|
if ($fu->getName() === 'places_completion') {
|
||||||
|
$date = $fu->getDate()->format('Y-m-d');
|
||||||
|
$completionByDate[$date] = $fu->getMeasure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aligner les données de complétion avec les dates du graphique
|
||||||
|
foreach ($chartData['dates'] as $date) {
|
||||||
|
$chartData['completion'][] = $completionByDate[$date] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('admin/stats.html.twig', [
|
return $this->render('admin/stats.html.twig', [
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
'commerces' => $commerces,
|
'commerces' => $commerces,
|
||||||
|
|
@ -699,6 +796,7 @@ final class AdminController extends AbstractController
|
||||||
'averageUpdateDate' => $averageUpdateDate,
|
'averageUpdateDate' => $averageUpdateDate,
|
||||||
'daysSinceUpdate' => $daysSinceUpdate,
|
'daysSinceUpdate' => $daysSinceUpdate,
|
||||||
'theme_groups' => $theme_groups,
|
'theme_groups' => $theme_groups,
|
||||||
|
'chart_data' => $chartData,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1139,6 +1237,9 @@ final class AdminController extends AbstractController
|
||||||
// Compléter le nom si manquant
|
// Compléter le nom si manquant
|
||||||
if (!$stats->getName()) {
|
if (!$stats->getName()) {
|
||||||
$cityName = $this->motocultrice->get_city_osm_from_zip_code($insee_code);
|
$cityName = $this->motocultrice->get_city_osm_from_zip_code($insee_code);
|
||||||
|
if($insee_code == '91477'){
|
||||||
|
$cityName = 'Palaiseau';
|
||||||
|
}
|
||||||
if ($cityName) {
|
if ($cityName) {
|
||||||
$stats->setName($cityName);
|
$stats->setName($cityName);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1560,19 +1560,69 @@ class PublicController extends AbstractController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trier par date décroissante
|
// Trier par date décroissante pour l'affichage
|
||||||
krsort($changesByDate);
|
krsort($changesByDate);
|
||||||
|
|
||||||
// Trier les utilisateurs uniques par ordre alphabétique
|
// Trier les utilisateurs uniques par ordre alphabétique
|
||||||
$uniqueUsersList = array_keys($uniqueUsers);
|
$uniqueUsersList = array_keys($uniqueUsers);
|
||||||
sort($uniqueUsersList);
|
sort($uniqueUsersList);
|
||||||
|
|
||||||
|
// Préparer les données pour le graphique (groupées par jour, triées par ordre chronologique)
|
||||||
|
$chartData = [
|
||||||
|
'dates' => [],
|
||||||
|
'deletions' => [],
|
||||||
|
'creations' => [],
|
||||||
|
'completion' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
// Créer une copie triée par date croissante pour le graphique
|
||||||
|
$changesByDateSorted = $changesByDate;
|
||||||
|
ksort($changesByDateSorted);
|
||||||
|
|
||||||
|
// Compter les créations et suppressions par jour
|
||||||
|
foreach ($changesByDateSorted as $date => $changes) {
|
||||||
|
$chartData['dates'][] = $date;
|
||||||
|
$deletionsCount = 0;
|
||||||
|
$creationsCount = 0;
|
||||||
|
|
||||||
|
if (isset($changes['deletions'])) {
|
||||||
|
foreach ($changes['deletions'] as $theme => $objects) {
|
||||||
|
$deletionsCount += count($objects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($changes['creations'])) {
|
||||||
|
foreach ($changes['creations'] as $theme => $objects) {
|
||||||
|
$creationsCount += count($objects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$chartData['deletions'][] = $deletionsCount;
|
||||||
|
$chartData['creations'][] = $creationsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les données de complétion générale de la ville si disponibles
|
||||||
|
$followups = $stats->getCityFollowUps();
|
||||||
|
$completionByDate = [];
|
||||||
|
foreach ($followups as $fu) {
|
||||||
|
if ($fu->getName() === 'places_completion') {
|
||||||
|
$date = $fu->getDate()->format('Y-m-d');
|
||||||
|
$completionByDate[$date] = $fu->getMeasure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aligner les données de complétion avec les dates du graphique
|
||||||
|
foreach ($chartData['dates'] as $date) {
|
||||||
|
$chartData['completion'][] = $completionByDate[$date] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('public/zone_places_history.html.twig', [
|
return $this->render('public/zone_places_history.html.twig', [
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
'changesByDate' => $changesByDate,
|
'changesByDate' => $changesByDate,
|
||||||
'followup_labels' => $followupLabels,
|
'followup_labels' => $followupLabels,
|
||||||
'followup_icons' => $followupIcons,
|
'followup_icons' => $followupIcons,
|
||||||
'unique_users' => $uniqueUsersList,
|
'unique_users' => $uniqueUsersList,
|
||||||
|
'chart_data' => $chartData,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,36 @@
|
||||||
|
|
||||||
<div id="graphiques" class="section-anchor">
|
<div id="graphiques" class="section-anchor">
|
||||||
<h2>Graphiques</h2>
|
<h2>Graphiques</h2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{# Graphique d'activité récente #}
|
||||||
|
{% if chart_data is defined and chart_data.dates|length > 0 %}
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="bi bi-graph-up"></i> Activité récente (créations et suppressions)</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="recentActivityChart" style="max-height: 300px;"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="card-footer d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||||
|
<div>
|
||||||
|
<a href="{{ path('app_public_zone_places_history', {'insee_code': stats.zone}) }}" class="btn btn-sm btn-outline-primary">
|
||||||
|
<i class="bi bi-clock-history"></i> Historique des objets
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="{{ path('app_public_atom_city_creations', {'insee_code': stats.zone}) }}" class="btn btn-sm btn-outline-success" target="_blank" title="Flux Atom des créations">
|
||||||
|
<i class="bi bi-rss"></i> <i class="bi bi-plus-circle"></i> Créations
|
||||||
|
</a>
|
||||||
|
<a href="{{ path('app_public_atom_city_deletions', {'insee_code': stats.zone}) }}" class="btn btn-sm btn-outline-danger" target="_blank" title="Flux Atom des suppressions">
|
||||||
|
<i class="bi bi-rss"></i> <i class="bi bi-dash-circle"></i> Suppressions
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<canvas id="repartition_tags" width="600" height="400"
|
<canvas id="repartition_tags" width="600" height="400"
|
||||||
|
|
@ -244,6 +274,9 @@
|
||||||
<a href="https://www.openstreetmap.org/copyright">Données OpenStreetMap</a>
|
<a href="https://www.openstreetmap.org/copyright">Données OpenStreetMap</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% if stats.population %}
|
{% if stats.population %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
|
|
@ -1417,4 +1450,127 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
// Graphique d'activité récente
|
||||||
|
{% if chart_data is defined and chart_data.dates|length > 0 %}
|
||||||
|
{% set hasCompletionData = false %}
|
||||||
|
{% for comp in chart_data.completion %}
|
||||||
|
{% if comp is not null %}
|
||||||
|
{% set hasCompletionData = true %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const chartCanvas = document.getElementById('recentActivityChart');
|
||||||
|
if (chartCanvas) {
|
||||||
|
const chartData = {
|
||||||
|
labels: {{ chart_data.dates|json_encode|raw }},
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Suppressions',
|
||||||
|
data: {{ chart_data.deletions|json_encode|raw }},
|
||||||
|
borderColor: 'rgb(220, 53, 69)',
|
||||||
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Créations',
|
||||||
|
data: {{ chart_data.creations|json_encode|raw }},
|
||||||
|
borderColor: 'rgb(25, 135, 84)',
|
||||||
|
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
}
|
||||||
|
{% if hasCompletionData %}
|
||||||
|
,{
|
||||||
|
label: 'Complétion générale (%)',
|
||||||
|
data: {{ chart_data.completion|json_encode|raw }},
|
||||||
|
borderColor: 'rgb(13, 110, 253)',
|
||||||
|
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: false,
|
||||||
|
yAxisID: 'y1',
|
||||||
|
borderDash: [5, 5]
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(chartCanvas, {
|
||||||
|
type: 'line',
|
||||||
|
data: chartData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
interaction: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
let label = context.dataset.label || '';
|
||||||
|
if (label) {
|
||||||
|
label += ': ';
|
||||||
|
}
|
||||||
|
if (context.parsed.y !== null) {
|
||||||
|
if (context.dataset.label === 'Complétion générale (%)') {
|
||||||
|
label += context.parsed.y.toFixed(1) + '%';
|
||||||
|
} else {
|
||||||
|
label += context.parsed.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label += 'N/A';
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
display: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Date'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
position: 'left',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Nombre d\'objets'
|
||||||
|
},
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
{% if hasCompletionData %}
|
||||||
|
,y1: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
position: 'right',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Complétion (%)'
|
||||||
|
},
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
grid: {
|
||||||
|
drawOnChartArea: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# Graphique des créations et suppressions #}
|
||||||
|
{% if chart_data is defined and chart_data.dates|length > 0 %}
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="bi bi-graph-up"></i> Évolution des créations et suppressions</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="changesChart" style="max-height: 400px;"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% for date, changes in changesByDate %}
|
{% for date, changes in changesByDate %}
|
||||||
<div class="card mb-4 change-date-card" data-date="{{ date }}">
|
<div class="card mb-4 change-date-card" data-date="{{ date }}">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
|
@ -281,6 +293,8 @@
|
||||||
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
let selectedChangeType = 'all';
|
let selectedChangeType = 'all';
|
||||||
|
|
@ -348,24 +362,33 @@
|
||||||
const shouldShowTheme = selectedTheme === 'all' || selectedTheme === theme;
|
const shouldShowTheme = selectedTheme === 'all' || selectedTheme === theme;
|
||||||
|
|
||||||
if (shouldShowTheme) {
|
if (shouldShowTheme) {
|
||||||
themeSection.style.display = '';
|
|
||||||
// Filtrer les lignes du tableau
|
// Filtrer les lignes du tableau
|
||||||
const rows = themeSection.querySelectorAll('tbody tr');
|
const rows = themeSection.querySelectorAll('tbody tr');
|
||||||
|
let hasVisibleRows = false;
|
||||||
|
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
const rowTheme = row.getAttribute('data-theme');
|
const rowTheme = row.getAttribute('data-theme');
|
||||||
const rowChangeType = row.getAttribute('data-change-type');
|
const rowChangeType = row.getAttribute('data-change-type');
|
||||||
const rowUser = row.getAttribute('data-user') || '';
|
const rowUser = row.getAttribute('data-user') || '';
|
||||||
const shouldShowTheme = selectedTheme === 'all' || selectedTheme === rowTheme;
|
const shouldShowRowTheme = selectedTheme === 'all' || selectedTheme === rowTheme;
|
||||||
const shouldShowChangeType = selectedChangeType === 'all' || selectedChangeType === rowChangeType;
|
const shouldShowChangeType = selectedChangeType === 'all' || selectedChangeType === rowChangeType;
|
||||||
const shouldShowUser = selectedUser === 'all' || selectedUser === rowUser;
|
const shouldShowUser = selectedUser === 'all' || selectedUser === rowUser;
|
||||||
|
|
||||||
if (shouldShowTheme && shouldShowChangeType && shouldShowUser) {
|
if (shouldShowRowTheme && shouldShowChangeType && shouldShowUser) {
|
||||||
row.style.display = '';
|
row.style.display = '';
|
||||||
|
hasVisibleRows = true;
|
||||||
} else {
|
} else {
|
||||||
row.style.display = 'none';
|
row.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
hasVisibleTheme = true;
|
|
||||||
|
// Afficher ou masquer la section de thème selon qu'elle a des lignes visibles
|
||||||
|
if (hasVisibleRows) {
|
||||||
|
themeSection.style.display = '';
|
||||||
|
hasVisibleTheme = true;
|
||||||
|
} else {
|
||||||
|
themeSection.style.display = 'none';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
themeSection.style.display = 'none';
|
themeSection.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
@ -391,6 +414,126 @@
|
||||||
// Initialiser les filtres
|
// Initialiser les filtres
|
||||||
applyFilters();
|
applyFilters();
|
||||||
|
|
||||||
|
// Créer le graphique des créations et suppressions
|
||||||
|
{% if chart_data is defined and chart_data.dates|length > 0 %}
|
||||||
|
{% set hasCompletionData = false %}
|
||||||
|
{% for comp in chart_data.completion %}
|
||||||
|
{% if comp is not null %}
|
||||||
|
{% set hasCompletionData = true %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
const chartCanvas = document.getElementById('changesChart');
|
||||||
|
if (chartCanvas) {
|
||||||
|
const chartData = {
|
||||||
|
labels: {{ chart_data.dates|json_encode|raw }},
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Suppressions',
|
||||||
|
data: {{ chart_data.deletions|json_encode|raw }},
|
||||||
|
borderColor: 'rgb(220, 53, 69)',
|
||||||
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Créations',
|
||||||
|
data: {{ chart_data.creations|json_encode|raw }},
|
||||||
|
borderColor: 'rgb(25, 135, 84)',
|
||||||
|
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
}
|
||||||
|
{% if hasCompletionData %}
|
||||||
|
,{
|
||||||
|
label: 'Complétion générale (%)',
|
||||||
|
data: {{ chart_data.completion|json_encode|raw }},
|
||||||
|
borderColor: 'rgb(13, 110, 253)',
|
||||||
|
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: false,
|
||||||
|
yAxisID: 'y1',
|
||||||
|
borderDash: [5, 5]
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(chartCanvas, {
|
||||||
|
type: 'line',
|
||||||
|
data: chartData,
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
interaction: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
let label = context.dataset.label || '';
|
||||||
|
if (label) {
|
||||||
|
label += ': ';
|
||||||
|
}
|
||||||
|
if (context.parsed.y !== null) {
|
||||||
|
if (context.dataset.label === 'Complétion générale (%)') {
|
||||||
|
label += context.parsed.y.toFixed(1) + '%';
|
||||||
|
} else {
|
||||||
|
label += context.parsed.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label += 'N/A';
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
display: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Date'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
position: 'left',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Nombre d\'objets'
|
||||||
|
},
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
{% if hasCompletionData %}
|
||||||
|
,y1: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
position: 'right',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Complétion (%)'
|
||||||
|
},
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
grid: {
|
||||||
|
drawOnChartArea: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
// Convertir les dates en format relatif
|
// Convertir les dates en format relatif
|
||||||
function formatRelativeTime(timestamp) {
|
function formatRelativeTime(timestamp) {
|
||||||
if (!timestamp) return 'N/A';
|
if (!timestamp) return 'N/A';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue