@@ -8,6 +8,37 @@ import { normalizeBaseUrl, createPath } from '../lib/paths';
88// Carica i dati delle iniziative
99const 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
1243const 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
3263const 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