Skip to content

Commit ccc49f3

Browse files
committed
Show timelines for subordinate stations
1 parent 3b5185b commit ccc49f3

5 files changed

Lines changed: 72 additions & 61 deletions

File tree

package-lock.json

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
"concurrently": "^9.2.1"
1717
},
1818
"dependencies": {
19-
"@neaps/api": "^0.3.3",
20-
"@neaps/tide-database": "^0.6.20260218",
21-
"@neaps/tide-predictor": "^0.7.0",
22-
"neaps": "^0.5.1"
19+
"@neaps/api": "^0.4.0",
20+
"@neaps/tide-database": "^0.6.20260220",
21+
"@neaps/tide-predictor": "^0.8.0",
22+
"neaps": "^0.6.0"
2323
}
2424
}

website/src/components/tides/TideGraph.tsx

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ ChartJS.register(
2828
TimeScale,
2929
);
3030

31-
interface TideDataPoint {
32-
time: Date;
33-
level: number;
34-
label?: string;
35-
}
36-
3731
interface Props {
3832
station: Station;
3933
extremesData: ExtremesResponse | null;
@@ -42,31 +36,35 @@ interface Props {
4236

4337
export function TideGraph({ station, extremesData, timelineData }: Props) {
4438
const units = timelineData?.units || extremesData?.units || "meters";
45-
const isReference = station.type === "reference";
4639

47-
const data: TideDataPoint[] = useMemo(() => {
48-
if (isReference && timelineData) {
49-
return timelineData.timeline.map((p) => ({
50-
time: new Date(p.time),
51-
level: p.level,
52-
}));
53-
}
54-
if (!isReference && extremesData) {
55-
return extremesData.extremes.map((p) => ({
56-
time: new Date(p.time),
57-
level: p.level,
58-
label: p.label,
59-
}));
60-
}
61-
return [];
62-
}, [isReference, timelineData, extremesData]);
40+
const timelinePoints = useMemo(() => {
41+
if (!timelineData) return [];
42+
return timelineData.timeline.map((p) => ({
43+
time: new Date(p.time),
44+
level: p.level,
45+
}));
46+
}, [timelineData]);
47+
48+
const extremePoints = useMemo(() => {
49+
if (!extremesData) return [];
50+
return extremesData.extremes.map((p) => ({
51+
time: new Date(p.time),
52+
level: p.level,
53+
label: p.label,
54+
}));
55+
}, [extremesData]);
6356

6457
const datum =
65-
(isReference ? timelineData?.datum : extremesData?.datum) ||
66-
station.chart_datum;
58+
timelineData?.datum || extremesData?.datum || station.chart_datum;
6759

68-
const minLevel = data.length > 0 ? Math.min(...data.map((d) => d.level)) : 0;
69-
const maxLevel = data.length > 0 ? Math.max(...data.map((d) => d.level)) : 2;
60+
const minLevel =
61+
timelinePoints.length > 0
62+
? Math.min(...timelinePoints.map((d) => d.level))
63+
: 0;
64+
const maxLevel =
65+
timelinePoints.length > 0
66+
? Math.max(...timelinePoints.map((d) => d.level))
67+
: 2;
7068
const padding = (maxLevel - minLevel) * 0.1 || 0.5;
7169

7270
const pointDateStyle = Intl.DateTimeFormat(undefined, {
@@ -84,9 +82,21 @@ export function TideGraph({ station, extremesData, timelineData }: Props) {
8482
const currentTime = new Date();
8583
const chartData = {
8684
datasets: [
85+
{
86+
label: "High/Low",
87+
data: extremePoints.map((d) => ({ x: d.time, y: d.level })),
88+
borderColor: "transparent",
89+
backgroundColor: "#0284c7",
90+
borderWidth: 0,
91+
fill: false,
92+
pointRadius: 5,
93+
pointHoverRadius: 7,
94+
pointStyle: "circle" as const,
95+
showLine: false,
96+
},
8797
{
8898
label: "Water Level",
89-
data: data.map((d) => ({ x: d.time, y: d.level })),
99+
data: timelinePoints.map((d) => ({ x: d.time, y: d.level })),
90100
borderColor: "#0ea5e9",
91101
backgroundColor: "rgba(14, 165, 233, 0.1)",
92102
borderWidth: 2,
@@ -124,13 +134,19 @@ export function TideGraph({ station, extremesData, timelineData }: Props) {
124134
},
125135
tooltip: {
126136
displayColors: false,
137+
filter: (item: any) => item.dataset.label !== "Current Time",
127138
callbacks: {
128139
title: (context: any) => {
129140
if (context.length === 0) return "";
130141
return pointDateStyle.format(new Date(context[0].parsed.x));
131142
},
132143
label: (context: any) => {
133-
return `${(context.parsed.y ?? 0).toFixed(2)} ${unitsLabel}`;
144+
const value = `${(context.parsed.y ?? 0).toFixed(2)} ${unitsLabel}`;
145+
if (context.dataset.label === "High/Low") {
146+
const extreme = extremePoints[context.dataIndex];
147+
return extreme?.label ? `${extreme.label}: ${value}` : value;
148+
}
149+
return value;
134150
},
135151
},
136152
},
@@ -165,7 +181,7 @@ export function TideGraph({ station, extremesData, timelineData }: Props) {
165181
return (
166182
<div className="space-y-4">
167183
<div className="">
168-
{data.length > 0 ? (
184+
{timelinePoints.length > 0 ? (
169185
<div>
170186
<Line data={chartData} options={chartOptions} />
171187
</div>

website/src/components/tides/TideStation.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ interface Props {
1212
}
1313

1414
export function TideStation({ station }: Props) {
15-
const isReference = station.type === "reference";
16-
1715
// Compute a single date range that covers both components:
1816
// - Today needs extremes from -6.5h to +18.5h
1917
// - TideGraph needs data from now to +3 days
@@ -40,7 +38,6 @@ export function TideStation({ station }: Props) {
4038
const { data: timelineData } = useNeapsAPI<TimelineResponse>(
4139
`/tides/stations/${station.id}/timeline`,
4240
{ start: startDate, end: endDate, units: preferredUnits },
43-
{ skip: !isReference },
4441
);
4542

4643
if (loading) {

website/src/components/tides/Today.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ export default function Today({
4747

4848
const end = useMemo(() => start.add({ hours: 25 }), [start]);
4949

50-
const isReference = station.type === "reference";
51-
5250
// Filter extremes to the ~25h window for today's display
5351
const extremes = useMemo(() => {
5452
if (!extremesData?.extremes) return [];
@@ -68,7 +66,7 @@ export default function Today({
6866

6967
// Find the closest timeline point to now for current water level
7068
const nowLevel = useMemo(() => {
71-
if (!isReference || !timelineData?.timeline?.length) return undefined;
69+
if (!timelineData?.timeline?.length) return undefined;
7270
const nowMs = now.epochMilliseconds;
7371
let closest = timelineData.timeline[0];
7472
let closestDiff = Math.abs(new Date(closest.time).valueOf() - nowMs);
@@ -80,7 +78,7 @@ export default function Today({
8078
}
8179
}
8280
return closest.level;
83-
}, [isReference, timelineData, now]);
81+
}, [timelineData, now]);
8482

8583
return (
8684
<div className="space-y-4 tabular-nums">

0 commit comments

Comments
 (0)