From bcfb028f1650c8352604329a44d7db9041c4be0c Mon Sep 17 00:00:00 2001 From: manudous Date: Mon, 27 Apr 2026 13:02:08 +0200 Subject: [PATCH] feat: add animation to current level bar in history chart and update label visibility Co-authored-by: Copilot --- .../components/chart/history-chart.tsx | 88 +++++++++++++++---- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/front/src/pods/embalse/components/chart/history-chart.tsx b/front/src/pods/embalse/components/chart/history-chart.tsx index 7c2176b..639ef93 100644 --- a/front/src/pods/embalse/components/chart/history-chart.tsx +++ b/front/src/pods/embalse/components/chart/history-chart.tsx @@ -1,3 +1,5 @@ +"use client"; +import { useState, useEffect, useRef } from "react"; import * as d3 from "d3"; import { ChartModel } from "./chart.vm"; import { sizeChart as s } from "./chart.constants"; @@ -10,6 +12,48 @@ export const HistoryChart: React.FC = ({ dataOneYearAgo, dataTenYearsAgo, }) => { + const [animationKey, setAnimationKey] = useState(0); + const [animProgress, setAnimProgress] = useState(0); + const [labelVisible, setLabelVisible] = useState(false); + const rafRef = useRef(null); + + const startAnimation = () => { + if (rafRef.current !== null) cancelAnimationFrame(rafRef.current); + setAnimProgress(0); + setLabelVisible(false); + setAnimationKey((k) => k + 1); + }; + + useEffect(() => { + startAnimation(); + const mq = window.matchMedia("(min-width: 768px)"); + const handler = (e: MediaQueryListEvent) => { + if (e.matches) startAnimation(); + }; + mq.addEventListener("change", handler); + return () => mq.removeEventListener("change", handler); + }, []); + + useEffect(() => { + if (animationKey === 0) return; + const duration = 1200; + const start = performance.now(); + const tick = (now: number) => { + const t = Math.min((now - start) / duration, 1); + const eased = 1 - Math.pow(1 - t, 3); + setAnimProgress(eased); + if (t < 1) { + rafRef.current = requestAnimationFrame(tick); + } else { + setLabelVisible(true); + } + }; + rafRef.current = requestAnimationFrame(tick); + return () => { + if (rafRef.current !== null) cancelAnimationFrame(rafRef.current); + }; + }, [animationKey]); + let percentageActual = (reservoirData.currentVolume * 100) / reservoirData.totalCapacity; if (percentageActual > 100) { @@ -40,32 +84,44 @@ export const HistoryChart: React.FC = ({ // Etiqueta: encima de la barra si el nivel es muy bajo (<10%), dentro si no const labelY = isOutside ? barY - 8 : barY + 20; + // Animación de la barra oscura (nivel actual) que crece de abajo hacia arriba. + // Si la animación no ha arrancado aún (móvil antes de tocar), mostrar estado final estático. + const progress = animationKey === 0 ? 1 : animProgress; + const animBarHeight = progress * barHeight; + const animBarY = y(0) - animBarHeight; + const showLabel = animationKey === 0 ? true : labelVisible; + return (
{ + if (!window.matchMedia("(min-width: 768px)").matches) { + startAnimation(); + } + }} >

{titleChart}

- {/* Indicador de capacidad total (100%) */} + {/* Indicador de capacidad total (100%) - fijo, ocupa todo el alto */} - {/* Nivel actual */} + {/* Nivel actual - animado creciendo de abajo hacia arriba */} @@ -95,16 +151,18 @@ export const HistoryChart: React.FC = ({ /> )} {/* Etiqueta con el nivel actual en Hm³ */} - - {reservoirData.currentVolume} Hm³ - + {showLabel && ( + + {reservoirData.currentVolume} Hm³ + + )} {/* Eje X */}