Skip to content

Commit 3000900

Browse files
committed
feat: aggiunta funzionalità per caricare e visualizzare la media dei sostenitori giornalieri
1 parent 45b17d9 commit 3000900

2 files changed

Lines changed: 213 additions & 9 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
git config --local user.name "GitHub Action"
7878
7979
# Aggiungi i file se sono cambiati
80-
git add data/source.json data/source.jsonl data/time_line.jsonl data/check_date.txt
80+
git add data/source.json data/source.jsonl data/time_line.jsonl data/media_sostenitori_giornaliera.jsonl data/check_date.txt
8181
8282
# Verifica se ci sono cambiamenti da committare
8383
if git diff --staged --quiet; then

src/pages/numeri.astro

Lines changed: 212 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,37 @@ import { normalizeBaseUrl, createPath } from '../lib/paths';
88
// Carica i dati delle iniziative
99
const initiatives = await fetchInitiatives();
1010
11+
// Carica i dati della media sostenitori giornalieri
12+
async function loadMediaSostenitori() {
13+
try {
14+
const mediaData = await import('../../data/media_sostenitori_giornaliera.jsonl?raw');
15+
const lines = mediaData.default.trim().split('\n');
16+
return lines.map(line => JSON.parse(line));
17+
} catch (error) {
18+
console.warn('Error loading media sostenitori data:', error);
19+
return [];
20+
}
21+
}
22+
23+
const mediaSostenitori = await loadMediaSostenitori();
24+
25+
// Unisci i dati delle iniziative con i dati della media sostenitori
26+
const initiativesWithMedia = initiatives.map(initiative => {
27+
const mediaData = mediaSostenitori.find(m => m.id === initiative.id);
28+
return {
29+
...initiative,
30+
sostenitori_delta_mean: mediaData?.sostenitori_delta_mean || 0
31+
};
32+
});
33+
34+
// Top 10 iniziative per sostenitori_delta_mean
35+
const top10ByMedia = initiativesWithMedia
36+
.filter(init => init.sostenitori_delta_mean > 0)
37+
.sort((a, b) => b.sostenitori_delta_mean - a.sostenitori_delta_mean)
38+
.slice(0, 10);
39+
40+
const maxMediaValue = Math.max(...top10ByMedia.map(init => init.sostenitori_delta_mean));
41+
1142
// Calcola il numero di iniziative per categoria
1243
const categoryCounts = initiatives.reduce((acc, initiative) => {
1344
const categoria = initiative.idDecCatIniziativa?.nome || 'Senza categoria';
@@ -30,8 +61,6 @@ const activeInitiatives = initiatives.filter(initiative =>
3061
3162
// Gestione robusta dei percorsi usando le utility
3263
const baseUrl = import.meta.env.BASE_URL;
33-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
34-
const homePath = normalizedBaseUrl || '/';
3564
---
3665

3766
<Layout title="Numeri - Iniziative Referendum" description="Statistiche e numeri delle iniziative referendarie">
@@ -41,7 +70,7 @@ const homePath = normalizedBaseUrl || '/';
4170
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
4271
<div class="flex justify-between items-center h-16">
4372
<div class="flex items-center">
44-
<a href={homePath} class="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium">
73+
<a href={createPath('/')} class="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium">
4574
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4675
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m0 7h18" />
4776
</svg>
@@ -57,8 +86,13 @@ const homePath = normalizedBaseUrl || '/';
5786
<div class="bg-white border-b border-gray-200">
5887
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
5988
<div class="text-center">
60-
<h1 class="text-3xl font-bold text-gray-900 sm:text-4xl">
61-
Numeri
89+
<h1 id="numeri" class="text-3xl font-bold text-gray-900 sm:text-4xl scroll-mt-20">
90+
<a href="#numeri" class="group hover:text-blue-600 transition-colors duration-200">
91+
Numeri
92+
<svg class="w-6 h-6 ml-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
93+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
94+
</svg>
95+
</a>
6296
</h1>
6397
<p class="mt-2 text-lg text-gray-600">
6498
Un po' di numeri di riepilogo sulle iniziative
@@ -70,15 +104,20 @@ const homePath = normalizedBaseUrl || '/';
70104
<!-- Main content -->
71105
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
72106
<div class="bg-white rounded-lg shadow-sm p-6">
73-
<h2 class="text-2xl font-semibold text-gray-900 mb-6">
74-
Numero di iniziative per categoria
107+
<h2 id="iniziative-per-categoria" class="text-2xl font-semibold text-gray-900 mb-6 scroll-mt-20">
108+
<a href="#iniziative-per-categoria" class="group flex items-center hover:text-blue-600 transition-colors duration-200">
109+
Numero di iniziative per categoria
110+
<svg class="w-5 h-5 ml-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
111+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
112+
</svg>
113+
</a>
75114
</h2>
76115

77116
<!-- Istogramma orizzontale -->
78117
<div class="space-y-4">
79118
{sortedCategories.map(({ nome, count }) => {
80119
const percentage = (count / maxCount) * 100;
81-
const categoryUrl = `${homePath}?categoria=${encodeURIComponent(nome)}`;
120+
const categoryUrl = `${createPath('/')}?categoria=${encodeURIComponent(nome)}`;
82121
return (
83122
<div class="relative">
84123
<!-- Etichetta categoria cliccabile -->
@@ -165,9 +204,88 @@ const homePath = normalizedBaseUrl || '/';
165204
</p>
166205
<p class="text-sm text-gray-600 mt-2">
167206
<strong>Tip:</strong> Clicca sul nome di una categoria per visualizzare tutte le iniziative di quella categoria.
207+
Puoi anche cliccare sui titoli delle sezioni per ottenere un link diretto a quella sezione.
168208
</p>
169209
</div>
170210
</div>
211+
212+
<!-- Grafico Lollipop Top 10 Media Sostenitori -->
213+
{top10ByMedia.length > 0 && (
214+
<div class="bg-white rounded-lg shadow-sm p-6 mt-8">
215+
<h2 id="top-media-sostenitori" class="text-2xl font-semibold text-gray-900 mb-6 scroll-mt-20">
216+
<a href="#top-media-sostenitori" class="group flex items-center hover:text-blue-600 transition-colors duration-200">
217+
Top 10 - Media sostenitori giornalieri
218+
<svg class="w-5 h-5 ml-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
219+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
220+
</svg>
221+
</a>
222+
</h2>
223+
224+
<!-- Grafico Lollipop orizzontale --> <div class="space-y-6">
225+
{top10ByMedia.map((initiative, index) => {
226+
const percentage = (initiative.sostenitori_delta_mean / maxMediaValue) * 100;
227+
const initiativeUrl = createPath(`/initiative/${initiative.id}`);
228+
229+
return (
230+
<div class="relative flex items-center">
231+
<!-- ID cliccabile con tooltip -->
232+
<div class="w-20 flex-shrink-0">
233+
<a
234+
href={initiativeUrl}
235+
class="text-sm font-bold text-blue-600 hover:text-blue-800 transition-colors duration-200 cursor-pointer hover:underline"
236+
title={initiative.titolo}
237+
data-tooltip={initiative.titolo}
238+
>
239+
{initiative.id}
240+
</a>
241+
</div>
242+
243+
<!-- Linea e pallino del lollipop -->
244+
<div class="flex-1 relative">
245+
<!-- Linea di base -->
246+
<div class="w-full h-0.5 bg-gray-200 relative">
247+
<!-- Linea colorata -->
248+
<div
249+
class="h-0.5 bg-gradient-to-r from-emerald-400 to-emerald-600 transition-all duration-1000 ease-out"
250+
style={`width: ${percentage}%`}
251+
data-target-width={percentage}
252+
></div>
253+
254+
<!-- Pallino del lollipop -->
255+
<div
256+
class="absolute top-1/2 transform -translate-y-1/2 w-4 h-4 bg-emerald-500 rounded-full border-2 border-white shadow-md transition-all duration-1000 ease-out"
257+
style={`left: ${percentage}%`}
258+
data-target-left={percentage}
259+
></div>
260+
</div>
261+
262+
<!-- Valore numerico -->
263+
<div
264+
class="absolute top-2 text-sm font-medium text-gray-700 transition-all duration-1000 ease-out"
265+
style={`left: ${percentage}%`}
266+
data-target-left={percentage}
267+
>
268+
{initiative.sostenitori_delta_mean}
269+
</div>
270+
</div>
271+
</div>
272+
);
273+
})}
274+
</div>
275+
276+
<!-- Legenda e note -->
277+
<div class="mt-6 p-4 bg-gray-50 rounded-lg">
278+
<p class="text-sm text-gray-600">
279+
<strong>Nota:</strong> Il grafico mostra le 10 iniziative con la più alta media di sostenitori acquisiti per giorno.
280+
Passa il mouse sull'ID per vedere il titolo completo dell'iniziativa.
281+
</p>
282+
<p class="text-sm text-gray-600 mt-2">
283+
<strong>Tip:</strong> Clicca sull'ID per accedere alla pagina di dettaglio dell'iniziativa.
284+
Clicca sul titolo della sezione per ottenere un link diretto a questo grafico.
285+
</p>
286+
</div>
287+
</div>
288+
)}
171289
</main>
172290

173291
<!-- Footer -->
@@ -189,6 +307,54 @@ const homePath = normalizedBaseUrl || '/';
189307
width: var(--target-width);
190308
}
191309
}
310+
311+
/* Tooltip personalizzato */
312+
[data-tooltip] {
313+
position: relative;
314+
}
315+
316+
[data-tooltip]:hover::after {
317+
content: attr(data-tooltip);
318+
position: absolute;
319+
bottom: 100%;
320+
left: 50%;
321+
transform: translateX(-50%);
322+
background-color: rgba(0, 0, 0, 0.9);
323+
color: white;
324+
padding: 8px 12px;
325+
border-radius: 6px;
326+
font-size: 12px;
327+
white-space: nowrap;
328+
max-width: 300px;
329+
white-space: normal;
330+
word-wrap: break-word;
331+
z-index: 1000;
332+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
333+
}
334+
335+
[data-tooltip]:hover::before {
336+
content: '';
337+
position: absolute;
338+
bottom: 92%;
339+
left: 50%;
340+
transform: translateX(-50%);
341+
border: 5px solid transparent;
342+
border-top-color: rgba(0, 0, 0, 0.9);
343+
z-index: 1000;
344+
}
345+
346+
/* Animazioni per il lollipop */
347+
.lollipop-line {
348+
transition: width 1s ease-out;
349+
}
350+
351+
.lollipop-dot {
352+
transition: left 1s ease-out;
353+
}
354+
355+
.lollipop-value {
356+
transition: left 1s ease-out;
357+
}
192358
</style>
193359

194360
<script>
@@ -204,5 +370,43 @@ const homePath = normalizedBaseUrl || '/';
204370
element.style.width = targetWidth;
205371
}, index * 100);
206372
});
373+
374+
// Animazione per i grafici lollipop
375+
const lollipopLines = document.querySelectorAll('[data-target-width]');
376+
const lollipopDots = document.querySelectorAll('[data-target-left]');
377+
378+
// Inizializza le posizioni a 0
379+
lollipopLines.forEach((line) => {
380+
const element = line as HTMLElement;
381+
element.style.width = '0%';
382+
});
383+
384+
lollipopDots.forEach((dot) => {
385+
const element = dot as HTMLElement;
386+
element.style.left = '0%';
387+
});
388+
389+
// Anima verso le posizioni target
390+
setTimeout(() => {
391+
lollipopLines.forEach((line, index) => {
392+
const element = line as HTMLElement;
393+
const targetWidth = element.getAttribute('data-target-width');
394+
if (targetWidth) {
395+
setTimeout(() => {
396+
element.style.width = `${targetWidth}%`;
397+
}, index * 150);
398+
}
399+
});
400+
401+
lollipopDots.forEach((dot, index) => {
402+
const element = dot as HTMLElement;
403+
const targetLeft = element.getAttribute('data-target-left');
404+
if (targetLeft) {
405+
setTimeout(() => {
406+
element.style.left = `${targetLeft}%`;
407+
}, index * 150);
408+
}
409+
});
410+
}, 500); // Ritardo iniziale per permettere il caricamento
207411
});
208412
</script>

0 commit comments

Comments
 (0)