169 lines
6.4 KiB
TypeScript
169 lines
6.4 KiB
TypeScript
"use client";
|
|
|
|
import { Bar, CartesianGrid, ComposedChart, Legend, Line, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
|
import { Card } from "@/components/ui/card";
|
|
import { ChartTooltip } from "@/components/charts/chart-tooltip";
|
|
import { CHART_COLORS } from "@/lib/chart-theme";
|
|
import { useI18n } from "@/lib/i18n";
|
|
import { formatForecastRainfall, formatForecastTemperature } from "@/lib/forecast-utils";
|
|
import { useWeatherStore } from "@/lib/store";
|
|
import { convertTemperature, formatTemperatureValue, getTemperatureUnitLabel } from "@/lib/weather-utils";
|
|
import type { HourlyForecast } from "@/types/forecast";
|
|
|
|
const INITIAL_CHART_DIMENSION = { width: 1, height: 1 };
|
|
|
|
function formatHour(value: string) {
|
|
return value.slice(11, 16);
|
|
}
|
|
|
|
export function DayForecastCharts({ hours }: { hours: HourlyForecast[] }) {
|
|
const { language, t } = useI18n();
|
|
const temperatureUnit = useWeatherStore((state) => state.temperatureUnit);
|
|
const rows = hours.map((hour) => ({
|
|
time: formatHour(hour.time),
|
|
temperature: hour.temperature === null ? null : convertTemperature(hour.temperature, temperatureUnit),
|
|
feelsLike: hour.feelsLike === null ? null : convertTemperature(hour.feelsLike, temperatureUnit),
|
|
precipitation: hour.precipitation,
|
|
precipitationProbability: hour.precipitationProbability,
|
|
}));
|
|
const temperatureUnitLabel = getTemperatureUnitLabel(temperatureUnit);
|
|
|
|
return (
|
|
<div className="grid gap-3 lg:grid-cols-2">
|
|
<Card className="p-4 sm:p-5">
|
|
<h3 className="text-sm font-semibold">{t("forecast.temperatureChart")}</h3>
|
|
<p className="mt-1 text-xs leading-5 text-muted">{t("forecast.temperatureChartDescription")}</p>
|
|
<div className="mt-4 h-56 min-w-0">
|
|
<ResponsiveContainer
|
|
width="100%"
|
|
height="100%"
|
|
minWidth={0}
|
|
minHeight={0}
|
|
initialDimension={INITIAL_CHART_DIMENSION}
|
|
>
|
|
<ComposedChart data={rows} margin={{ left: -20, right: 8, top: 8 }}>
|
|
<CartesianGrid stroke={CHART_COLORS.grid} strokeDasharray="4 4" vertical={false} />
|
|
<XAxis
|
|
dataKey="time"
|
|
axisLine={false}
|
|
tickLine={false}
|
|
interval={3}
|
|
tick={{ fill: "currentColor", fontSize: 11 }}
|
|
/>
|
|
<YAxis
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fill: "currentColor", fontSize: 11 }}
|
|
unit={temperatureUnitLabel}
|
|
/>
|
|
<Tooltip
|
|
cursor={{ stroke: CHART_COLORS.grid, strokeDasharray: "4 4" }}
|
|
content={
|
|
<ChartTooltip
|
|
valueFormatter={(entry) =>
|
|
typeof entry.value === "number"
|
|
? formatTemperatureValue(entry.value, language, temperatureUnit)
|
|
: formatForecastTemperature(null, language, temperatureUnit)
|
|
}
|
|
/>
|
|
}
|
|
/>
|
|
<Legend wrapperStyle={{ fontSize: "0.72rem" }} />
|
|
<Line
|
|
type="monotone"
|
|
dataKey="temperature"
|
|
name={t("forecast.temperature")}
|
|
stroke={CHART_COLORS.temperature}
|
|
strokeWidth={3}
|
|
dot={false}
|
|
connectNulls
|
|
/>
|
|
<Line
|
|
type="monotone"
|
|
dataKey="feelsLike"
|
|
name={t("forecast.apparentTemperature")}
|
|
stroke={CHART_COLORS.feelsLike}
|
|
strokeWidth={2}
|
|
strokeDasharray="5 4"
|
|
dot={false}
|
|
connectNulls
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</Card>
|
|
|
|
<Card className="p-4 sm:p-5">
|
|
<h3 className="text-sm font-semibold">{t("forecast.rainfallChart")}</h3>
|
|
<p className="mt-1 text-xs leading-5 text-muted">{t("forecast.rainfallChartDescription")}</p>
|
|
<div className="mt-4 h-56 min-w-0">
|
|
<ResponsiveContainer
|
|
width="100%"
|
|
height="100%"
|
|
minWidth={0}
|
|
minHeight={0}
|
|
initialDimension={INITIAL_CHART_DIMENSION}
|
|
>
|
|
<ComposedChart data={rows} margin={{ left: -20, right: -10, top: 8 }}>
|
|
<CartesianGrid stroke={CHART_COLORS.grid} strokeDasharray="4 4" vertical={false} />
|
|
<XAxis
|
|
dataKey="time"
|
|
axisLine={false}
|
|
tickLine={false}
|
|
interval={3}
|
|
tick={{ fill: "currentColor", fontSize: 11 }}
|
|
/>
|
|
<YAxis
|
|
yAxisId="rainfall"
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fill: "currentColor", fontSize: 11 }}
|
|
unit=" mm"
|
|
/>
|
|
<YAxis
|
|
yAxisId="probability"
|
|
orientation="right"
|
|
domain={[0, 100]}
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fill: "currentColor", fontSize: 11 }}
|
|
unit="%"
|
|
/>
|
|
<Tooltip
|
|
cursor={{ fill: "hsl(var(--border) / 0.18)" }}
|
|
content={
|
|
<ChartTooltip
|
|
valueFormatter={(entry) =>
|
|
entry.dataKey === "precipitation"
|
|
? formatForecastRainfall(typeof entry.value === "number" ? entry.value : null, language)
|
|
: `${typeof entry.value === "number" ? entry.value : "—"}%`
|
|
}
|
|
/>
|
|
}
|
|
/>
|
|
<Legend wrapperStyle={{ fontSize: "0.72rem" }} />
|
|
<Bar
|
|
yAxisId="rainfall"
|
|
dataKey="precipitation"
|
|
name={t("forecast.precipitation")}
|
|
fill={CHART_COLORS.rainfall}
|
|
radius={[5, 5, 0, 0]}
|
|
/>
|
|
<Line
|
|
yAxisId="probability"
|
|
type="monotone"
|
|
dataKey="precipitationProbability"
|
|
name={t("forecast.precipitationProbability")}
|
|
stroke={CHART_COLORS.probability}
|
|
strokeWidth={2}
|
|
dot={false}
|
|
connectNulls
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|