feat: update lake water data and refine WindChart component functionality
This commit is contained in:
+5
-2
@@ -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,
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user