Skip to content

Commit 3a6129d

Browse files
authored
Merge pull request #205 from Lemoncode/feature/animate-char-legand-bar
feat: add animation to current level bar in history chart and update …
2 parents aa1be90 + bcfb028 commit 3a6129d

1 file changed

Lines changed: 73 additions & 15 deletions

File tree

front/src/pods/embalse/components/chart/history-chart.tsx

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
import { useState, useEffect, useRef } from "react";
13
import * as d3 from "d3";
24
import { ChartModel } from "./chart.vm";
35
import { 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

Comments
 (0)