1+ "use client" ;
2+ import { useState , useEffect , useRef } from "react" ;
13import * as d3 from "d3" ;
24import { ChartModel } from "./chart.vm" ;
35import { sizeChart as s } from "./chart.constants" ;
@@ -10,6 +12,48 @@ export const HistoryChart: React.FC<ChartModel> = ({
1012 dataOneYearAgo,
1113 dataTenYearsAgo,
1214} ) => {
15+ const [ animationKey , setAnimationKey ] = useState ( 0 ) ;
16+ const [ animProgress , setAnimProgress ] = useState ( 0 ) ;
17+ const [ labelVisible , setLabelVisible ] = useState ( false ) ;
18+ const rafRef = useRef < number | null > ( null ) ;
19+
20+ const startAnimation = ( ) => {
21+ if ( rafRef . current !== null ) cancelAnimationFrame ( rafRef . current ) ;
22+ setAnimProgress ( 0 ) ;
23+ setLabelVisible ( false ) ;
24+ setAnimationKey ( ( k ) => k + 1 ) ;
25+ } ;
26+
27+ useEffect ( ( ) => {
28+ startAnimation ( ) ;
29+ const mq = window . matchMedia ( "(min-width: 768px)" ) ;
30+ const handler = ( e : MediaQueryListEvent ) => {
31+ if ( e . matches ) startAnimation ( ) ;
32+ } ;
33+ mq . addEventListener ( "change" , handler ) ;
34+ return ( ) => mq . removeEventListener ( "change" , handler ) ;
35+ } , [ ] ) ;
36+
37+ useEffect ( ( ) => {
38+ if ( animationKey === 0 ) return ;
39+ const duration = 1200 ;
40+ const start = performance . now ( ) ;
41+ const tick = ( now : number ) => {
42+ const t = Math . min ( ( now - start ) / duration , 1 ) ;
43+ const eased = 1 - Math . pow ( 1 - t , 3 ) ;
44+ setAnimProgress ( eased ) ;
45+ if ( t < 1 ) {
46+ rafRef . current = requestAnimationFrame ( tick ) ;
47+ } else {
48+ setLabelVisible ( true ) ;
49+ }
50+ } ;
51+ rafRef . current = requestAnimationFrame ( tick ) ;
52+ return ( ) => {
53+ if ( rafRef . current !== null ) cancelAnimationFrame ( rafRef . current ) ;
54+ } ;
55+ } , [ animationKey ] ) ;
56+
1357 let percentageActual =
1458 ( reservoirData . currentVolume * 100 ) / reservoirData . totalCapacity ;
1559 if ( percentageActual > 100 ) {
@@ -40,32 +84,44 @@ export const HistoryChart: React.FC<ChartModel> = ({
4084 // Etiqueta: encima de la barra si el nivel es muy bajo (<10%), dentro si no
4185 const labelY = isOutside ? barY - 8 : barY + 20 ;
4286
87+ // Animación de la barra oscura (nivel actual) que crece de abajo hacia arriba.
88+ // Si la animación no ha arrancado aún (móvil antes de tocar), mostrar estado final estático.
89+ const progress = animationKey === 0 ? 1 : animProgress ;
90+ const animBarHeight = progress * barHeight ;
91+ const animBarY = y ( 0 ) - animBarHeight ;
92+ const showLabel = animationKey === 0 ? true : labelVisible ;
93+
4394 return (
4495 < section
4596 className = "card bg-base-100 mx-auto w-full items-center rounded-2xl md:gap-4 md:p-4 md:shadow-lg"
4697 aria-labelledby = "gauge-title"
98+ onClick = { ( ) => {
99+ if ( ! window . matchMedia ( "(min-width: 768px)" ) . matches ) {
100+ startAnimation ( ) ;
101+ }
102+ } }
47103 >
48104 < h2 id = "gauge-title" className = "text-center" >
49105 { titleChart }
50106 </ h2 >
51107
52108 < svg width = { s . width } height = { s . height } >
53- { /* Indicador de capacidad total (100%) */ }
109+ { /* Indicador de capacidad total (100%) - fijo, ocupa todo el alto */ }
54110 < rect
55111 x = { barX }
56112 y = { y ( 100 ) }
57113 width = { barWidth }
58- height = { y ( barHeight / 2 ) }
114+ height = { y ( 0 ) - y ( 100 ) }
59115 rx = { s . radius }
60116 fill = "var(--color-total-water)"
61117 />
62118
63- { /* Nivel actual */ }
119+ { /* Nivel actual - animado creciendo de abajo hacia arriba */ }
64120 < BarRoundedTop
65121 x = { barX }
66- y = { barY }
122+ y = { animBarY }
67123 width = { barWidth }
68- height = { barHeight }
124+ height = { animBarHeight }
69125 fill = "var(--color-primary)"
70126 />
71127
@@ -95,16 +151,18 @@ export const HistoryChart: React.FC<ChartModel> = ({
95151 />
96152 ) }
97153 { /* Etiqueta con el nivel actual en Hm³ */ }
98- < text
99- x = { barX + barWidth / 2 }
100- y = { labelY }
101- textAnchor = "middle"
102- fontSize = "16px"
103- fill = "var(--color-brand-100)"
104- fontWeight = "900"
105- >
106- { reservoirData . currentVolume } Hm³
107- </ text >
154+ { showLabel && (
155+ < text
156+ x = { barX + barWidth / 2 }
157+ y = { labelY }
158+ textAnchor = "middle"
159+ fontSize = "16px"
160+ fill = "var(--color-brand-100)"
161+ fontWeight = "900"
162+ >
163+ { reservoirData . currentVolume } Hm³
164+ </ text >
165+ ) }
108166
109167 { /* Eje X */ }
110168 < line
0 commit comments