feat: update lake water data and refine WindChart component functionality

This commit is contained in:
David Fencl
2026-06-08 21:10:29 +02:00
parent 48b44cd642
commit cdb653d660
57 changed files with 1091 additions and 165 deletions
+5 -2
View File
@@ -493,8 +493,11 @@
}
.chart-tooltip {
padding: 0.6rem !important;
max-width: 280px !important;
padding: 0.4rem 0.5rem !important;
max-width: 200px !important;
background-color: rgba(15, 23, 42, 0.85) !important;
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.1) !important;
}
.chart-tooltip p,
+45 -13
View File
@@ -31,13 +31,15 @@ interface Props {
windUnit?: 'kmh' | 'ms';
}
const CustomTooltip = ({ active, payload, label, language, isWeather, isRiver }: any) => {
const CustomTooltip = ({ active, payload, label, language, isWeather, isRiver, coordinate, viewBox }: any) => {
if (active && payload && payload.length) {
const dict = t[language as Language].chart;
const isLeft = coordinate && viewBox && coordinate.x > viewBox.width / 2;
const tooltipClass = `chart-tooltip ${isLeft ? 'tooltip-left' : 'tooltip-right'}`;
if (isWeather) {
return (
<div className="chart-tooltip" style={{ backgroundColor: 'var(--bg-card)', padding: '1rem', border: '1px solid var(--border-color)', borderRadius: '0.5rem', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' }}>
<p style={{ margin: '0 0 0.5rem 0', fontWeight: 'bold', color: 'var(--text-main)' }}>{label}</p>
<div className={tooltipClass} style={{ backgroundColor: 'var(--bg-card)', padding: '0.4rem 0.6rem', border: '1px solid var(--border-color)', borderRadius: '0.5rem', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)', fontSize: '0.8rem' }}>
<p style={{ margin: '0 0 0.25rem 0', fontWeight: 'bold', color: 'var(--text-main)', fontSize: '0.85rem' }}>{label}</p>
{payload.map((entry: any, index: number) => {
const isTemp = entry.name === 'temperature' || entry.dataKey === 'temperature';
return (
@@ -50,8 +52,8 @@ const CustomTooltip = ({ active, payload, label, language, isWeather, isRiver }:
);
}
return (
<div className="chart-tooltip" style={{ backgroundColor: 'var(--bg-card)', padding: '1rem', border: '1px solid var(--border-color)', borderRadius: '0.5rem', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' }}>
<p style={{ margin: '0 0 0.5rem 0', fontWeight: 'bold', color: 'var(--text-main)' }}>{label}</p>
<div className={tooltipClass} style={{ backgroundColor: 'var(--bg-card)', padding: '0.4rem 0.6rem', border: '1px solid var(--border-color)', borderRadius: '0.5rem', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)', fontSize: '0.8rem' }}>
<p style={{ margin: '0 0 0.25rem 0', fontWeight: 'bold', color: 'var(--text-main)', fontSize: '0.85rem' }}>{label}</p>
{[...payload].sort((a: any, b: any) => {
const order = ['level', 'inflow', 'outflow', 'temperature', 'precipitation'];
const indexA = order.indexOf(a.dataKey);
@@ -90,18 +92,18 @@ const CustomTooltip = ({ active, payload, label, language, isWeather, isRiver }:
if (!labelStr || entry.value === null || entry.value === undefined) return null;
return (
<div key={index} style={{ margin: 0, color: 'var(--text-main)', display: 'flex', alignItems: 'center', marginBottom: '4px' }}>
<span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '50%', backgroundColor: color, marginRight: '8px' }}></span>
<div key={index} style={{ margin: 0, color: 'var(--text-main)', display: 'flex', alignItems: 'center', marginBottom: '2px' }}>
<span style={{ display: 'inline-block', width: '6px', height: '6px', borderRadius: '50%', backgroundColor: color, marginRight: '6px' }}></span>
{labelStr}: <span style={{ fontWeight: 'bold', marginLeft: '4px' }}>{entry.value.toFixed(entry.dataKey === 'level' ? (isRiver ? 0 : 2) : 1)} {unit}</span>
</div>
);
})}
{payload[0]?.payload?.qn ? (
<div style={{ marginTop: '8px', paddingTop: '8px', borderTop: '1px solid var(--border-color)', fontSize: '0.8rem', color: '#f59e0b', display: 'flex', alignItems: 'center', gap: '4px' }}>
<div style={{ marginTop: '3px', paddingTop: '3px', borderTop: '1px solid var(--border-color)', fontSize: '0.75rem', color: '#f59e0b', display: 'flex', alignItems: 'center', gap: '4px', lineHeight: '1' }}>
{language === 'cs' ? 'Neověřené měření' : 'Unverified measurement'}
</div>
) : (
<div style={{ marginTop: '8px', paddingTop: '8px', borderTop: '1px solid var(--border-color)', fontSize: '0.8rem', color: 'var(--color-green)', display: 'flex', alignItems: 'center', gap: '4px' }}>
<div style={{ marginTop: '3px', paddingTop: '3px', borderTop: '1px solid var(--border-color)', fontSize: '0.75rem', color: 'var(--color-green)', display: 'flex', alignItems: 'center', gap: '4px', lineHeight: '1' }}>
{language === 'cs' ? 'Měření ověřeno' : 'Measurement verified'}
</div>
)}
@@ -129,6 +131,8 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
const [isMobile, setIsMobile] = useState(false);
const [leftCustomDomain, setLeftCustomDomain] = useState<[number, number] | null>(null);
const [rightCustomDomain, setRightCustomDomain] = useState<[number, number] | null>(null);
const [tooltipY, setTooltipY] = useState<number | undefined>(undefined);
const [weatherTooltipY, setWeatherTooltipY] = useState<number | undefined>(undefined);
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth <= 768);
@@ -609,7 +613,18 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
<div style={{ flex: 1, minHeight: '300px', width: '100%', marginTop: '0', position: 'relative' }}>
<ResponsiveContainer width="100%" height="100%">
<ComposedChart data={chartData} margin={isMobile ? { top: 5, right: 5, left: 5, bottom: 0 } : { top: 5, right: 0, left: 10, bottom: 0 }}>
<ComposedChart
data={chartData}
margin={isMobile ? { top: 5, right: 5, left: 5, bottom: 0 } : { top: 5, right: 0, left: 10, bottom: 0 }}
onMouseMove={(state: any) => {
if (state && state.chartY !== undefined) {
const isBottomHalf = state.chartY > 150;
const targetY = isBottomHalf ? 5 : 180;
if (tooltipY !== targetY) setTooltipY(targetY);
}
}}
onMouseLeave={() => setTooltipY(undefined)}
>
<defs>
<linearGradient id="colorLevel" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-cyan)" stopOpacity={0.5}/>
@@ -621,7 +636,10 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
<YAxis yAxisId="right" orientation="right" domain={rightCustomDomain || [0, (dataMax: number) => Math.max(dataMax, 1)]} stroke={(visibleSeries.outflow || visibleSeries.inflow) ? "var(--text-muted)" : "transparent"} tick={{fill: (visibleSeries.outflow || visibleSeries.inflow) ? 'var(--text-muted)' : 'transparent', fontSize: isMobile ? 10 : 12}} tickLine={(visibleSeries.outflow || visibleSeries.inflow) ? { stroke: 'var(--text-muted)' } : { stroke: 'transparent' }} axisLine={(visibleSeries.outflow || visibleSeries.inflow) ? { stroke: 'var(--text-muted)' } : { stroke: 'transparent' }} width={isMobile ? 35 : 60} tickFormatter={(v) => v.toFixed(1)} />
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.05)" vertical={false} />
<Tooltip content={<CustomTooltip language={language} isRiver={isRiver} />} />
<Tooltip
content={<CustomTooltip language={language} isRiver={isRiver} />}
position={tooltipY !== undefined ? { y: tooltipY } : undefined}
/>
{/* Data Series */}
{visibleSeries.level && limits && limits.map((limit, idx) => (
@@ -752,13 +770,27 @@ const LakeDetail = ({ language, lakeId, windUnit = 'kmh' }: Props) => {
<div style={{ flex: 1, minHeight: '200px', width: '100%', marginTop: '0' }}>
<ResponsiveContainer width="100%" height="100%">
<ComposedChart data={chartData} margin={isMobile ? { top: 5, right: 5, left: 5, bottom: 0 } : { top: 5, right: 0, left: 10, bottom: 0 }}>
<ComposedChart
data={chartData}
margin={isMobile ? { top: 5, right: 5, left: 5, bottom: 0 } : { top: 5, right: 0, left: 10, bottom: 0 }}
onMouseMove={(state: any) => {
if (state && state.chartY !== undefined) {
const isBottomHalf = state.chartY > 100;
const targetY = isBottomHalf ? 5 : 110;
if (weatherTooltipY !== targetY) setWeatherTooltipY(targetY);
}
}}
onMouseLeave={() => setWeatherTooltipY(undefined)}
>
<XAxis dataKey="date" stroke="var(--text-muted)" tick={{fill: 'var(--text-muted)', fontSize: isMobile ? 10 : 12}} minTickGap={50} />
<YAxis yAxisId="temp" domain={['auto', 'auto']} stroke={visibleWeatherSeries.temp ? "var(--text-muted)" : "transparent"} tick={{fill: visibleWeatherSeries.temp ? 'var(--text-muted)' : 'transparent', fontSize: isMobile ? 10 : 12}} tickLine={visibleWeatherSeries.temp ? { stroke: 'var(--text-muted)' } : { stroke: 'transparent' }} axisLine={visibleWeatherSeries.temp ? { stroke: 'var(--text-muted)' } : { stroke: 'transparent' }} width={isMobile ? 42 : 60} tickFormatter={(v) => v.toFixed(1)} />
<YAxis yAxisId="precip" orientation="right" domain={[0, 'auto']} stroke={visibleWeatherSeries.precip ? "var(--text-muted)" : "transparent"} tick={{fill: visibleWeatherSeries.precip ? 'var(--text-muted)' : 'transparent', fontSize: isMobile ? 10 : 12}} tickLine={visibleWeatherSeries.precip ? { stroke: 'var(--text-muted)' } : { stroke: 'transparent' }} axisLine={visibleWeatherSeries.precip ? { stroke: 'var(--text-muted)' } : { stroke: 'transparent' }} width={isMobile ? 35 : 60} />
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.05)" vertical={false} />
<Tooltip content={<CustomTooltip language={language} isWeather={true} />} />
<Tooltip
content={<CustomTooltip language={language} isWeather={true} />}
position={weatherTooltipY !== undefined ? { y: weatherTooltipY } : undefined}
/>
<Bar yAxisId="precip" dataKey="precipitation" fill="var(--color-cyan)" fillOpacity={0.6} isAnimationActive={animate} hide={!visibleWeatherSeries.precip} />
<Line yAxisId="temp" type={curveType} dataKey="temperature" stroke="var(--color-red)" strokeWidth={2} dot={true} isAnimationActive={animate} hide={!visibleWeatherSeries.temp} />
+26 -9
View File
@@ -27,28 +27,30 @@ const getCompassDirection = (degrees: number, language: 'cs' | 'en') => {
return directions[index];
};
const CustomWindTooltip = ({ active, payload, label, language, windUnit = 'kmh' }: any) => {
const CustomWindTooltip = ({ active, payload, label, language, windUnit = 'kmh', coordinate, viewBox }: any) => {
if (active && payload && payload.length) {
const isLeft = coordinate && viewBox && coordinate.x > viewBox.width / 2;
const tooltipClass = `chart-tooltip ${isLeft ? 'tooltip-left' : 'tooltip-right'}`;
const data = payload[0].payload;
const date = new Date(label);
const dateStr = date.toLocaleDateString(language === 'cs' ? 'cs-CZ' : 'en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' });
const timeStr = date.toLocaleTimeString(language === 'cs' ? 'cs-CZ' : 'en-GB', { hour: '2-digit', minute: '2-digit' });
return (
<div style={{ backgroundColor: 'rgba(30, 41, 59, 0.95)', border: '1px solid var(--border-color)', borderRadius: '8px', padding: '12px', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.5)', color: 'var(--text-main)', fontSize: '0.9rem', zIndex: 100 }}>
<div style={{ fontWeight: 'bold', marginBottom: '8px', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '4px' }}>
<div className={tooltipClass} style={{ backgroundColor: 'var(--bg-card)', border: '1px solid var(--border-color)', borderRadius: '8px', padding: '8px 10px', boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.5)', color: 'var(--text-main)', fontSize: '0.8rem', zIndex: 100 }}>
<div style={{ fontWeight: 'bold', marginBottom: '6px', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '3px', fontSize: '0.85rem' }}>
{dateStr} {timeStr}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<span style={{ color: 'var(--color-cyan)', fontSize: '1.2rem' }}></span>
<span style={{ color: 'var(--color-cyan)', fontSize: '1rem' }}></span>
<span>{language === 'cs' ? 'Rychlost větru' : 'Wind Speed'}: <strong>{data.speed} {windUnit === 'kmh' ? 'km/h' : 'm/s'}</strong></span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<span style={{ color: 'var(--color-purple)', fontSize: '1.2rem' }}></span>
<span style={{ color: 'var(--color-purple)', fontSize: '1rem' }}></span>
<span>{language === 'cs' ? 'Nárazy větru' : 'Wind Gusts'}: <strong>{data.gusts} {windUnit === 'kmh' ? 'km/h' : 'm/s'}</strong></span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginTop: '4px', color: 'var(--text-muted)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginTop: '2px', color: 'var(--text-muted)' }}>
<FiWind />
<span>{language === 'cs' ? 'Směr' : 'Direction'}: <strong>{data.dirStr} ({data.dir}°)</strong></span>
</div>
@@ -82,6 +84,7 @@ export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'km
const [currentSpeed, setCurrentSpeed] = useState(0);
const [maxGust, setMaxGust] = useState(0);
const [isMobile, setIsMobile] = useState(false);
const [tooltipY, setTooltipY] = useState<number | undefined>(undefined);
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth <= 768);
@@ -224,7 +227,18 @@ export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'km
<div style={{ flex: 1, minHeight: '280px', width: '100%', marginTop: '0' }}>
<ResponsiveContainer width="100%" height="100%">
<ComposedChart data={data} margin={isMobile ? { top: 5, right: 5, left: 5, bottom: 0 } : { top: 5, right: 0, left: -20, bottom: 0 }}>
<ComposedChart
data={data}
margin={isMobile ? { top: 5, right: 5, left: 5, bottom: 0 } : { top: 5, right: 0, left: -20, bottom: 0 }}
onMouseMove={(state: any) => {
if (state && state.chartY !== undefined) {
const isBottomHalf = state.chartY > 140;
const targetY = isBottomHalf ? 5 : 160;
if (tooltipY !== targetY) setTooltipY(targetY);
}
}}
onMouseLeave={() => setTooltipY(undefined)}
>
<defs>
<linearGradient id="colorWind" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-cyan)" stopOpacity={0.4}/>
@@ -248,7 +262,10 @@ export const WindChart = ({ lat, lng, language, timeRange = '7d', windUnit = 'km
tickFormatter={(v) => v.toFixed(1)}
/>
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.05)" vertical={false} />
<Tooltip content={<CustomWindTooltip language={language} windUnit={windUnit} />} />
<Tooltip
content={<CustomWindTooltip language={language} windUnit={windUnit} />}
position={tooltipY !== undefined ? { y: tooltipY } : undefined}
/>
<Area
type="monotone"