Il componente Timeline visualizza la distribuzione temporale delle occorrenze del lemmario AtLiTeG attraverso istogrammi aggregati per quarti di secolo.
Componente principale utilizzato nella dashboard, con funzionalità avanzate:
- Path:
/lemmario-dashboard/components/TimelineEnhanced.tsx - Tipo: Client Component (
'use client') - Dipendenze: React, Framer Motion, Context API
Versione base del componente (attualmente non utilizzata):
- Path:
/lemmario-dashboard/components/Timeline.tsx - Differenze: Meno funzionalità UI (no heatmap, no progress bar)
I dati vengono aggregati in periodi di 25 anni usando la seguente logica:
const getQuartCentury = (year: number): string => {
const century = Math.floor(year / 100);
const quarterInCentury = Math.floor((year % 100) / 25);
const quarters = ['I', 'II', 'III', 'IV'];
return `${century}${quarters[quarterInCentury]}`;
};Mappatura periodi:
| Quarto | Range Anni | Esempio |
|---|---|---|
| I | XX00-XX24 | 1300-1324 |
| II | XX25-XX49 | 1325-1349 |
| III | XX50-XX74 | 1350-1374 |
| IV | XX75-XX99 | 1375-1399 |
Esempi:
- Anno 1308 → Quarto
13I→ Range 1300-1324 - Anno 1450 → Quarto
14II→ Range 1425-1449 - Anno 1598 → Quarto
15III→ Range 1550-1574 - Anno 1820 → Quarto
18I→ Range 1800-1824
const getYearRangeFromQuartCentury = (quartCentury: string): [number, number] => {
// Usa regex per parsing corretto dei numeri romani
const romanMatch = quartCentury.match(/^(\d+)(I{1,3}|IV|V)$/);
if (!romanMatch) {
console.error('[getYearRangeFromQuartCentury] Formato non valido:', quartCentury);
return [0, 0];
}
const century = parseInt(romanMatch[1]); // Estrae solo i numeri (es. "13")
const romanQuarter = romanMatch[2]; // Estrae solo il romano (es. "II")
const quarterIndex = ['I', 'II', 'III', 'IV'].indexOf(romanQuarter);
const start = century * 100 + quarterIndex * 25;
const end = start + 24;
return [start, end];
};Nota importante: Questa funzione usa una regex per separare correttamente la parte numerica dal numero romano, evitando il bug precedente dove slice(0, -1) causava duplicati.
Per ogni quarto di secolo, il componente:
- Raccoglie tutti i lemmi del periodo usando un
Map<string, data> - Somma le frequenze di tutte le attestazioni:
const freq = parseInt(lemma.Frequenza) || 0; data.attestazioni += freq;
- Traccia anni unici, lemmi unici (ma non le località)
- Ordina per periodo cronologico crescente
Caratteristiche chiave:
- ✅ Indipendente dalla località: Tutte le occorrenze vengono sommate indipendentemente dal luogo geografico
- ✅ Un solo istogramma per periodo: Garantito dall'uso di
Mapcon chiavequartCentury - ✅ Somma delle frequenze: Non conta righe, ma somma il campo
Frequenza
Layout:
[←] [████] [███] [█████] [██] [████] [███] [██] [████] [███] [██] [████] [→]
1300 1325 1350 1375 1400 1425 1450 1475 1500 1525 1550 1575
Caratteristiche:
- 12 barre visibili per pagina
- Navigazione con frecce ←/→
- Progress bar per indicare pagina corrente
- Altezza proporzionale alle occorrenze
- Numero occorrenze mostrato al centro della barra
Animazioni (Framer Motion):
- Transizione smooth tra pagine
- Effetto hover: scale + elevazione + ombra
- Effetto click: riduzione scale temporanea
- Effetto shine durante hover/selezione
Layout:
1300s: [▓▓] [▓▓] [▓▓] [░░]
1400s: [▓▓] [▓▓] [░░] [▓▓]
1500s: [▓▓] [▓▓] [▓▓] [▓▓]
Caratteristiche:
- Organizzata per secolo (righe)
- Ogni riga mostra 4 quarti (I, II, III, IV)
- Intensità colore basata su occorrenze
- Click su cella per filtrare periodo
Comportamento:
- Il periodo viene selezionato (stato:
selectedQuart) - Viene chiamato
highlightMultiplecon:lemmaIds: Array di ID univoci dei lemmi del periodoyears: Array degli anni presenti nel periodosource: 'timeline'type: 'select'
- La mappa evidenzia tutti i marker del periodo
- Il pannello dettaglio mostra le forme del periodo
Deseleziona:
- Click nuovamente sullo stesso periodo
- Chiama
clearHighlight()
Comportamento:
- Solo se nessun periodo è selezionato
- Stato temporaneo:
hoveredQuart - Chiamata a
highlightMultiplecontype: 'hover' - Animazioni di ingrandimento e luminosità
Mouse leave:
- Reset hover se nessuna selezione attiva
- Chiama
clearHighlight()
Sintomo: Istogrammi duplicati per lo stesso periodo temporale con altezze diverse.
Causa root: La funzione getYearRangeFromQuartCentury usava slice(0, -1) che non separava correttamente il numero dal romano:
// ❌ CODICE BUGGY (versione precedente)
const century = parseInt(quartCentury.slice(0, -1));
const quarter = quartCentury.slice(-1);
// Problema:
// "13II".slice(0, -1) = "13I" → parseInt("13I") = 13 ✓
// "13III".slice(0, -1) = "13II" → parseInt("13II") = 13 ✓
// "13IV".slice(0, -1) = "13I" → parseInt("13I") = 13 ✓
// Risultato: tutti i quarti mappavano allo stesso range!Impatto:
- Ogni quarto di secolo veniva convertito nello stesso range
- Visualizzazione mostrava 3-4 istogrammi per lo stesso periodo
- Dati aggregati correttamente ma visualizzati male
Utilizzare regex per parsing robusto:
// ✅ CODICE CORRETTO (versione attuale)
const romanMatch = quartCentury.match(/^(\d+)(I{1,3}|IV|V)$/);
const century = parseInt(romanMatch[1]); // Solo numeri
const romanQuarter = romanMatch[2]; // Solo romanoPattern regex: /^(\d+)(I{1,3}|IV|V)$/
- Gruppo 1:
(\d+)- Una o più cifre (es. "13", "14", "18") - Gruppo 2:
(I{1,3}|IV|V)- Numero romano valido:I{1,3}: I, II, IIIIV: IVV: Non usato ma supportato per estensibilità
Validazione:
- Controllo
if (!romanMatch)per input non validi - Controllo
quarterIndex === -1per romani non supportati - Log di errore espliciti per debugging
// Test conversioni
getYearRangeFromQuartCentury("13I") → [1300, 1324] ✓
getYearRangeFromQuartCentury("13II") → [1325, 1349] ✓
getYearRangeFromQuartCentury("13III") → [1350, 1374] ✓
getYearRangeFromQuartCentury("13IV") → [1375, 1399] ✓
getYearRangeFromQuartCentury("18I") → [1800, 1824] ✓-
useMemo per calcoli costosi:
quartCenturies: Aggregazione datitotalOccorrenze: Somma frequenzemaxAttestazioni: Valore massimo per scaling
-
Paginazione:
- Solo 12 elementi renderizzati simultaneamente
- Slice dinamico dell'array
-
Logging condizionale:
- Debug log solo in development
- Warning solo se duplicati rilevati
- Tempo rendering iniziale: ~50-100ms per 6236 record
- Tempo aggregazione: ~30-50ms
- Quarti generati: ~23 periodi (dataset AtLiTeG)
- Memoria: ~2-3MB per stato componente
Il componente emette log dettagliati per debugging:
[TimelineEnhanced] Quarti generati: 23
[TimelineEnhanced] Primi 5 quarti: ['13IV (323 occ)', '13II (267 occ)', ...]
[TimelineEnhanced] ✅ Nessun duplicato trovato
[TimelineEnhanced] Rendering pagina: 0
[TimelineEnhanced] visibleQuarts.length: 12
[TimelineEnhanced] Barre visibili:
[0] 1275-1299 (quart=13IV, attestazioni=323)
[1] 1300-1324 (quart=13I, attestazioni=300)
...Il componente controlla automaticamente la presenza di duplicati:
const uniqueQuarts = new Set(result.map(q => q.quartCentury));
if (uniqueQuarts.size !== result.length) {
console.error('[TimelineEnhanced] ❌ ERRORE: Trovati quarti duplicati!', {
total: result.length,
unique: uniqueQuarts.size,
duplicates: result.filter((q, i, arr) =>
arr.findIndex(x => x.quartCentury === q.quartCentury) !== i
)
});
}- AppContext: Fornisce
lemmi,filteredLemmi - HighlightContext: Gestisce evidenziazione cross-component
- framer-motion: Animazioni fluide
- lucide-react: Icone (ChevronLeft, ChevronRight, Calendar)
interface QuartCenturyData {
quartCentury: string; // Es. "13I", "18III"
hasData: boolean;
years: number[]; // Anni presenti nel quarto
lemmas: string[]; // Lemmi unici
attestazioni: number; // Somma frequenze
}- Zoom dinamico: Permettere aggregazione per secolo o decennio
- Export dati: Scaricare statistiche temporali in CSV
- Tooltip avanzato: Mostrare top 5 lemmi del periodo
- Filtro periodo: Range picker per selezionare intervallo personalizzato
- Confronto periodi: Visualizzare differenze tra due periodi
- ✅ Mantenere regex per parsing numeri romani
- ✅ Validare input con controlli espliciti
- ✅ Aggiornare documentazione per nuove funzionalità
- ✅ Testare con dataset diversi per verificare robustezza
- Component: TimelineEnhanced.tsx
- User Guide: user-guide.md
- Dashboard Docs: lemmario-dashboard.md