From c282159ab5afa995e3c42c5c67612a94daf2b494 Mon Sep 17 00:00:00 2001 From: James Bithell Date: Sun, 9 Jun 2024 18:56:57 +0100 Subject: [PATCH 01/12] Create chart.tsx --- src/pages/chart.tsx | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/pages/chart.tsx diff --git a/src/pages/chart.tsx b/src/pages/chart.tsx new file mode 100644 index 0000000..e115ceb --- /dev/null +++ b/src/pages/chart.tsx @@ -0,0 +1,51 @@ +import { LineChart } from "@mantine/charts"; +import { Container } from "@mantine/core"; +import type { HeadFC, PageProps } from "gatsby"; +import { DateTime } from "luxon"; +import * as React from "react"; +import TidalData from "../../data/tides.json"; +import { SEO } from "../components/SEO"; +import Layout from "../components/navigation/Layout"; + +const Page: React.FC = () => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const nextWeek = new Date(today); + nextWeek.setDate(today.getDate() + 2); + const tides = TidalData.schedule.filter(element => { + let date = new Date(element.date); + return date >= today && date <= nextWeek + }); + + return ( + + + day.groups.map(tide => ({ + date: new Date(DateTime.fromSQL(day.date + " " + tide.time).toISO()).getTime() / 1000, + Height: Number(tide.height) + }))).flat()} + dataKey="date" + xAxisLabel="Date" + yAxisLabel="Amount" + yAxisProps={{ domain: [0, 6] }} + xAxisProps={{ tickFormatter: (date: number) => DateTime.fromMillis(date * 1000).toLocaleString(DateTime.TIME_SIMPLE), domain: [new Date().getTime(), nextWeek.getTime()] }} + unit="m" + connectNulls={false} + series={[ + { name: 'Height', color: 'indigo.6' }, + ]} + curveType="step" + gridAxis="y" + /> + + + ) +} + +export default Page + +export const Head: HeadFC = () => ( + +) \ No newline at end of file From 283f13caa9ba630d4d6b30ff06e4964bf73b24ac Mon Sep 17 00:00:00 2001 From: James Bithell Date: Mon, 10 Jun 2024 21:10:47 +0100 Subject: [PATCH 02/12] Update chart.tsx --- src/pages/chart.tsx | 84 +++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/src/pages/chart.tsx b/src/pages/chart.tsx index e115ceb..06f2da9 100644 --- a/src/pages/chart.tsx +++ b/src/pages/chart.tsx @@ -6,46 +6,86 @@ import * as React from "react"; import TidalData from "../../data/tides.json"; import { SEO } from "../components/SEO"; import Layout from "../components/navigation/Layout"; +import { TidesJson_ScheduleObject } from "../types"; const Page: React.FC = () => { - const today = new Date(); - today.setHours(0, 0, 0, 0); - const nextWeek = new Date(today); - nextWeek.setDate(today.getDate() + 2); - const tides = TidalData.schedule.filter(element => { - let date = new Date(element.date); - return date >= today && date <= nextWeek - }); + const startTimestamp = new Date(); + startTimestamp.setHours(0, 0, 0, 0); + const endTimestamp = new Date(startTimestamp); + endTimestamp.setDate(endTimestamp.getDate() + 2); + let startIndex = TidalData.schedule.findIndex( + (date: TidesJson_ScheduleObject) => { + return new Date(date.date) >= startTimestamp; + } + ); + let endIndex = TidalData.schedule.findIndex( + (date: TidesJson_ScheduleObject) => { + return new Date(date.date) > endTimestamp; + } + ); + // Adjust indices to include the days immediately before and after the range + startIndex = startIndex > 0 ? startIndex - 1 : startIndex; + endIndex = endIndex < TidalData.schedule.length ? endIndex + 1 : endIndex; + + // Slice the array to get the desired elements + const highTides = TidalData.schedule + .slice(startIndex, endIndex) + .flatMap((date: TidesJson_ScheduleObject) => + date.groups.map((tide) => ({ + timestamp: new Date(date.date + " " + tide.time).getTime() / 1000, + height: Number(tide.height), + })) + ); + + const highAndLowTides = []; + for (let i = 0; i < highTides.length; i++) { + highAndLowTides.push(highTides[i]); + if (i < highTides.length - 1) { + highAndLowTides.push({ + timestamp: + highTides[i].timestamp + + (highTides[i + 1].timestamp - highTides[i].timestamp) / 2, + height: 0, + }); + } + } + console.log(highAndLowTides); return ( day.groups.map(tide => ({ - date: new Date(DateTime.fromSQL(day.date + " " + tide.time).toISO()).getTime() / 1000, - Height: Number(tide.height) - }))).flat()} + data={highAndLowTides + .map((tide) => ({ + date: tide.timestamp, + Height: Number(tide.height), + })) + .flat()} dataKey="date" xAxisLabel="Date" yAxisLabel="Amount" yAxisProps={{ domain: [0, 6] }} - xAxisProps={{ tickFormatter: (date: number) => DateTime.fromMillis(date * 1000).toLocaleString(DateTime.TIME_SIMPLE), domain: [new Date().getTime(), nextWeek.getTime()] }} + xAxisProps={ + { + //tickFormatter: (date: number) => + // DateTime.fromMillis(date * 1000).toLocaleString( + // DateTime.TIME_SIMPLE + // ), + //domain: [new Date().getTime(), nextWeek.getTime()], + } + } unit="m" connectNulls={false} - series={[ - { name: 'Height', color: 'indigo.6' }, - ]} + series={[{ name: "Height", color: "indigo.6" }]} curveType="step" gridAxis="y" /> - ) -} + ); +}; -export default Page +export default Page; -export const Head: HeadFC = () => ( - -) \ No newline at end of file +export const Head: HeadFC = () => ; From 1726a919f627364669ce0954fa0aecba43a15ccd Mon Sep 17 00:00:00 2001 From: James Bithell Date: Mon, 15 Jul 2024 18:28:10 +0100 Subject: [PATCH 03/12] Create graphDataGenerator.ts --- .../tideGraph/graphDataGenerator.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/components/tideGraph/graphDataGenerator.ts diff --git a/src/components/tideGraph/graphDataGenerator.ts b/src/components/tideGraph/graphDataGenerator.ts new file mode 100644 index 0000000..c564204 --- /dev/null +++ b/src/components/tideGraph/graphDataGenerator.ts @@ -0,0 +1,34 @@ +/** + * Calculates a tidal curve for a given location + * According to "TIME & HEIGHT DIFFERENCES FOR PREDICTING THE TIDE AT SECONDARY PORTS" (https://assets.admiralty.co.uk/public/2022-04/5609%202022%20Feb.pdf) + * For Milford Haven: + * MHWS 7.0 + * MHWN 5.2 + * MLWN 2.5 + * MLWS 0.7 + * Midpoint therefore 3.85 (calculated as average of MHWS-((MHWS-MLWS)/2) & MHWN-((MHWN-MLWN)/2) ) + * + * For Criccieth: + * MHWS -2 5.0 + * MHWN -1.8 3.4 + * MLWN -0.7 1.8 + * MLWS -0.3 0.4 + * Midpoint therefore = 2.65 + * + * For Porthmadog: + * MHWS -1.9 5.1 + * MHWN -1.8 3.4 + * MLWN ??? so we assume 1.8 + * MLWS ??? so we assume 0.3 + * Midpoint therefore assumed = 2.65 + * + * Using the midpoint we can derive the lowest height for a given time and high tide to be MIDPOINT - (HEIGHT - MIDPOINT) + * + * This isn't very acurate because of the river Glaslyn, so the admiralty do not therefore publish tidal curves for this location, or low tide times - we have just derived them from their data + * + */ +import { TidesJson_ScheduleObject } from "../../types"; +// According to "TIME & HEIGHT DIFFERENCES FOR PREDICTING THE TIDE AT SECONDARY PORTS" +const generateTideGraphData = (tideData: Array<{ date: Date, height: number }>): Array<{ timestamp: number, height: number }> => { + return [] +} From 1d0efbdf885714161cd3531f9dac35aa36fc89bf Mon Sep 17 00:00:00 2001 From: James Bithell Date: Mon, 15 Jul 2024 22:19:00 +0100 Subject: [PATCH 04/12] Add chart --- .../tideGraph/graphDataGenerator.ts | 67 ++++++++++++-- src/pages/chart.tsx | 90 ++++++++++++------- 2 files changed, 115 insertions(+), 42 deletions(-) diff --git a/src/components/tideGraph/graphDataGenerator.ts b/src/components/tideGraph/graphDataGenerator.ts index c564204..6809ea2 100644 --- a/src/components/tideGraph/graphDataGenerator.ts +++ b/src/components/tideGraph/graphDataGenerator.ts @@ -7,28 +7,79 @@ * MLWN 2.5 * MLWS 0.7 * Midpoint therefore 3.85 (calculated as average of MHWS-((MHWS-MLWS)/2) & MHWN-((MHWN-MLWN)/2) ) - * + * * For Criccieth: * MHWS -2 5.0 * MHWN -1.8 3.4 * MLWN -0.7 1.8 * MLWS -0.3 0.4 * Midpoint therefore = 2.65 - * + * * For Porthmadog: * MHWS -1.9 5.1 * MHWN -1.8 3.4 * MLWN ??? so we assume 1.8 * MLWS ??? so we assume 0.3 * Midpoint therefore assumed = 2.65 - * + * * Using the midpoint we can derive the lowest height for a given time and high tide to be MIDPOINT - (HEIGHT - MIDPOINT) - * + * * This isn't very acurate because of the river Glaslyn, so the admiralty do not therefore publish tidal curves for this location, or low tide times - we have just derived them from their data - * + * */ +import { stringify } from "querystring"; import { TidesJson_ScheduleObject } from "../../types"; +const MIDPOINT = 2.65; // According to "TIME & HEIGHT DIFFERENCES FOR PREDICTING THE TIDE AT SECONDARY PORTS" -const generateTideGraphData = (tideData: Array<{ date: Date, height: number }>): Array<{ timestamp: number, height: number }> => { - return [] -} +export const generateTideGraphData = ( + highTides: Array<{ timestamp: number; height: number }> +): Array<{ date: number; Height: number }> => { + const highAndLowTides = []; + for (let i = 0; i < highTides.length; i++) { + highAndLowTides.push(highTides[i]); + if (i < highTides.length - 1) { + highAndLowTides.push({ + timestamp: + highTides[i].timestamp + + (highTides[i + 1].timestamp - highTides[i].timestamp) / 2, + height: (MIDPOINT - (highTides[i].height - MIDPOINT)).toFixed(3), + }); + } + } + const plotPoints = []; + for (let i = 0; i < highAndLowTides.length; i++) { + plotPoints.push({ + date: highAndLowTides[i].timestamp, + Height: Number(highAndLowTides[i].height), + }); + if (i < highAndLowTides.length - 1) { + let differenceToNextHeight = + Number(highAndLowTides[i].height) - + Number(highAndLowTides[i + 1].height); + let timeDifference = + highAndLowTides[i + 1].timestamp - highAndLowTides[i].timestamp; + for ( + let t = highAndLowTides[i].timestamp; + t < highAndLowTides[i + 1].timestamp; + t += 60 * 5 // 5 minutes + ) { + plotPoints.push({ + date: t, + Height: Number( + ( + Number(highAndLowTides[i].height) + + differenceToNextHeight * + -1 * + Math.sin( + + ((Math.PI / 2) * (t - highAndLowTides[i].timestamp)) / + timeDifference + ) + ).toFixed(3) + ), + }); + } + } + } + return plotPoints; +}; diff --git a/src/pages/chart.tsx b/src/pages/chart.tsx index 06f2da9..d9859a4 100644 --- a/src/pages/chart.tsx +++ b/src/pages/chart.tsx @@ -1,5 +1,5 @@ import { LineChart } from "@mantine/charts"; -import { Container } from "@mantine/core"; +import { Container, Paper, Text } from "@mantine/core"; import type { HeadFC, PageProps } from "gatsby"; import { DateTime } from "luxon"; import * as React from "react"; @@ -7,12 +7,41 @@ import TidalData from "../../data/tides.json"; import { SEO } from "../components/SEO"; import Layout from "../components/navigation/Layout"; import { TidesJson_ScheduleObject } from "../types"; +import { generateTideGraphData } from "../components/tideGraph/graphDataGenerator"; + +interface ChartTooltipProps { + label: string; + payload: Record[] | undefined; +} + +function ChartTooltip({ label, payload }: ChartTooltipProps) { + if (!payload) return null; + + return ( + + + {new Date(Number(label) * 1000).toLocaleDateString("en-GB", { + day: "numeric", + month: "short", + year: "numeric", + hour: "numeric", + minute: "numeric", + })} + + {payload.map((item: any) => ( + + {item.name}: {item.value}m + + ))} + + ); +} const Page: React.FC = () => { const startTimestamp = new Date(); startTimestamp.setHours(0, 0, 0, 0); const endTimestamp = new Date(startTimestamp); - endTimestamp.setDate(endTimestamp.getDate() + 2); + endTimestamp.setDate(endTimestamp.getDate() + 0); // 2 let startIndex = TidalData.schedule.findIndex( (date: TidesJson_ScheduleObject) => { return new Date(date.date) >= startTimestamp; @@ -38,47 +67,40 @@ const Page: React.FC = () => { })) ); - const highAndLowTides = []; - for (let i = 0; i < highTides.length; i++) { - highAndLowTides.push(highTides[i]); - if (i < highTides.length - 1) { - highAndLowTides.push({ - timestamp: - highTides[i].timestamp + - (highTides[i + 1].timestamp - highTides[i].timestamp) / 2, - height: 0, - }); - } - } - console.log(highAndLowTides); + const graphData = generateTideGraphData(highTides); + return ( ({ - date: tide.timestamp, - Height: Number(tide.height), - })) - .flat()} + h={800} + data={graphData} dataKey="date" xAxisLabel="Date" - yAxisLabel="Amount" - yAxisProps={{ domain: [0, 6] }} - xAxisProps={ - { - //tickFormatter: (date: number) => - // DateTime.fromMillis(date * 1000).toLocaleString( - // DateTime.TIME_SIMPLE - // ), - //domain: [new Date().getTime(), nextWeek.getTime()], - } - } + yAxisLabel="Height" + yAxisProps={{ + domain: [0, 5.5], + allowDataOverflow: false, + }} + xAxisProps={{ + tickFormatter: (value: number) => + new Date(value * 1000).toLocaleDateString("en-GB", { + day: "numeric", + month: "short", + hour: "numeric", + minute: "numeric", + }), + //domain: [new Date().getTime(), nextWeek.getTime()], + }} + tooltipProps={{ + content: ({ label, payload }) => ( + + ), + }} unit="m" connectNulls={false} series={[{ name: "Height", color: "indigo.6" }]} - curveType="step" + curveType="natural" gridAxis="y" /> From 551bed51d576c1700bf811dadcb36017edc17ed0 Mon Sep 17 00:00:00 2001 From: James Bithell Date: Wed, 17 Jul 2024 20:31:07 +0100 Subject: [PATCH 05/12] Add tidal graph page --- gatsby-node.ts | 16 +++ src/components/templates/TideGraphPage.tsx | 83 +++++++++++++ src/components/tideGraph/TidalGraph.tsx | 43 +++++++ .../tideGraph/TidalGraphComponent.tsx | 109 +++++++++++++++++ .../tideGraph/graphDataGenerator.ts | 43 ++++--- src/pages/chart.tsx | 113 ------------------ 6 files changed, 277 insertions(+), 130 deletions(-) create mode 100644 src/components/templates/TideGraphPage.tsx create mode 100644 src/components/tideGraph/TidalGraph.tsx create mode 100644 src/components/tideGraph/TidalGraphComponent.tsx delete mode 100644 src/pages/chart.tsx diff --git a/gatsby-node.ts b/gatsby-node.ts index ef0f6b0..af7d391 100644 --- a/gatsby-node.ts +++ b/gatsby-node.ts @@ -27,6 +27,22 @@ export const createPages = async function ({ }); }); + TidalData.schedule.forEach((day: TidesJson_ScheduleObject, index: number) => { + actions.createPage({ + path: "/tide-graph/" + day.date, + component: path.resolve(`./src/components/templates/TideGraphPage.tsx`), + context: { + day, + nextDay: + index < TidalData.schedule.length - 1 + ? TidalData.schedule[index + 1].date + : false, + previousDay: index > 0 ? TidalData.schedule[index - 1].date : false, + }, + defer: false, + }); + }); + // Legacy page actions.createRedirect({ fromPath: "/historical-tables", diff --git a/src/components/templates/TideGraphPage.tsx b/src/components/templates/TideGraphPage.tsx new file mode 100644 index 0000000..90223e4 --- /dev/null +++ b/src/components/templates/TideGraphPage.tsx @@ -0,0 +1,83 @@ +import * as React from "react"; +import { Link, type HeadFC, type PageProps } from "gatsby"; +import { Box, Button, Text } from "@mantine/core"; +import TidalData from "../../../data/tides.json"; +import { SEO } from "../SEO"; +import Layout from "../navigation/Layout"; +import { DateTime } from "luxon"; +import { TidesJson_PDFObject, TidesJson_ScheduleObject } from "../../types"; +import { + IconArrowLeft, + IconArrowRight, + IconDownload, + IconFileTypePdf, +} from "@tabler/icons-react"; +import { TideTable } from "../tideTables/TideTable"; +import { TideTableMobile } from "../tideTables/TideTableMobile"; +import { DataInformation } from "../navigation/DataInformation"; +import { TidalGraph } from "../tideGraph/TidalGraph"; + +const Page: React.FC = ({ pageContext }) => { + const { day, previousDay, nextDay } = pageContext as { + day: TidesJson_ScheduleObject; + previousDay: string | false; + nextDay: string | false; + }; + console.log(day); + return ( + + {previousDay ? ( + + + + ) : null} + {nextDay ? ( + + + + ) : null} + + } + > + + + + + + ); +}; + +export default Page; + +export const Head: HeadFC = ({ pageContext }) => { + const { day } = pageContext as { day: TidesJson_PDFObject }; + const pageTitle = + DateTime.fromSQL(day.date).toLocaleString({ + day: "numeric", + month: "long", + year: "numeric", + }) + " Porthmadog Tide Graph"; + return ; +}; diff --git a/src/components/tideGraph/TidalGraph.tsx b/src/components/tideGraph/TidalGraph.tsx new file mode 100644 index 0000000..204c17a --- /dev/null +++ b/src/components/tideGraph/TidalGraph.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { TidesJson_ScheduleObject } from "../../types"; +import TidalData from "../../../data/tides.json"; +import { TidalGraphComponent } from "./TidalGraphComponent"; + +export function TidalGraph({ date }: { date: number }) { + const startTimestamp = new Date(); + startTimestamp.setHours(0, 0, 0, 0); + const endTimestamp = new Date(startTimestamp); + endTimestamp.setDate(endTimestamp.getDate() + 1); // The charts don't work so well beyond a day + let startIndex = TidalData.schedule.findIndex( + (date: TidesJson_ScheduleObject) => { + return new Date(date.date) >= startTimestamp; + } + ); + let endIndex = TidalData.schedule.findIndex( + (date: TidesJson_ScheduleObject) => { + return new Date(date.date) >= endTimestamp; + } + ); + + // Adjust indices to include the days immediately before and after the range to capture them in the graph + startIndex = startIndex > 0 ? startIndex - 1 : startIndex; + endIndex = endIndex < TidalData.schedule.length ? endIndex + 1 : endIndex; + + // Slice the array to get the desired elements + const highTides = TidalData.schedule + .slice(startIndex, endIndex) + .flatMap((date: TidesJson_ScheduleObject) => + date.groups.map((tide) => ({ + timestamp: new Date(date.date + " " + tide.time).getTime() / 1000, + height: Number(tide.height), + })) + ); + + return ( + + ); +} diff --git a/src/components/tideGraph/TidalGraphComponent.tsx b/src/components/tideGraph/TidalGraphComponent.tsx new file mode 100644 index 0000000..9eaeb25 --- /dev/null +++ b/src/components/tideGraph/TidalGraphComponent.tsx @@ -0,0 +1,109 @@ +import React from "react"; +import { Paper, Text } from "@mantine/core"; +import { LineChart } from "@mantine/charts"; +import { graphDataGenerator } from "./graphDataGenerator"; + +interface ChartTooltipProps { + label: string; + payload: Record[] | undefined; + highTides: Array<{ timestamp: number; height: number }>; +} + +function ChartTooltip({ label, payload, highTides }: ChartTooltipProps) { + if (!payload) return null; + const currentTide = highTides.find( + (tide) => tide.timestamp === Number(label) + ); + return ( + + + {new Date(Number(label) * 1000).toLocaleDateString("en-GB", { + day: "numeric", + month: "short", + year: "numeric", + hour: "numeric", + minute: "numeric", + })} + + {payload.map((item: any) => ( + + {item.name}: {item.value}m + + ))} + {currentTide === undefined && ( + + Caution: Estimated tide height + + )} + + ); +} + +export function TidalGraphComponent({ + highTides, + startTimestamp, + endTimestamp, +}: { + highTides: Array<{ timestamp: number; height: number }>; + startTimestamp: number; + endTimestamp: number; +}) { + const graphData = graphDataGenerator(highTides); + return ( + data.date >= startTimestamp && data.date <= endTimestamp + )} + gridAxis="none" + dataKey="date" + xAxisLabel="Date" + yAxisLabel="Height" + yAxisProps={{ + domain: [0, 5.5], + allowDataOverflow: false, + interval: "equidistantPreserveStart", + tickCount: 28, + type: "number", + }} + xAxisProps={{ + tickFormatter: (value: number) => + new Date(value * 1000).toLocaleTimeString("en-GB", { + hour: "numeric", + hour12: true, + }), + padding: { left: 30, right: 30 }, + interval: "equidistantPreserveStart", + allowDecimals: false, + domain: [startTimestamp, endTimestamp], + type: "number", + ticks: Array.from(Array(25).keys()).map( + (i) => startTimestamp + i * 60 * 60 + ), + }} + tooltipProps={{ + content: ({ label, payload }) => ( + + ), + }} + unit="m" + connectNulls={false} + series={[{ name: "Height", color: "indigo.6" }]} + curveType="natural" + dotProps={{ r: 0 }} + strokeWidth={2} + activeDotProps={{ r: 8, strokeWidth: 1, fill: "#fff" }} + referenceLines={[ + { y: 5.1, label: "MHWS", color: "red.6" }, + { y: 3.4, label: "MHWN", color: "red.6" }, + //{ y: 1.8, label: "MLWN", color: "red.6" }, + //{ y: 0.3, label: "MLWS", color: "red.6" }, + ...highTides.flatMap((tide) => ({ + x: tide.timestamp, + label: tide.height + "m", + color: "gray.6", + })), + ]} + /> + ); +} diff --git a/src/components/tideGraph/graphDataGenerator.ts b/src/components/tideGraph/graphDataGenerator.ts index 6809ea2..c71f5f8 100644 --- a/src/components/tideGraph/graphDataGenerator.ts +++ b/src/components/tideGraph/graphDataGenerator.ts @@ -27,17 +27,19 @@ * This isn't very acurate because of the river Glaslyn, so the admiralty do not therefore publish tidal curves for this location, or low tide times - we have just derived them from their data * */ -import { stringify } from "querystring"; -import { TidesJson_ScheduleObject } from "../../types"; const MIDPOINT = 2.65; // According to "TIME & HEIGHT DIFFERENCES FOR PREDICTING THE TIDE AT SECONDARY PORTS" -export const generateTideGraphData = ( +export const graphDataGenerator = ( highTides: Array<{ timestamp: number; height: number }> ): Array<{ date: number; Height: number }> => { const highAndLowTides = []; for (let i = 0; i < highTides.length; i++) { - highAndLowTides.push(highTides[i]); + highAndLowTides.push({ + ...highTides[i], + type: "high", + }); if (i < highTides.length - 1) { + // Add low tide highAndLowTides.push({ timestamp: highTides[i].timestamp + @@ -53,28 +55,35 @@ export const generateTideGraphData = ( Height: Number(highAndLowTides[i].height), }); if (i < highAndLowTides.length - 1) { - let differenceToNextHeight = - Number(highAndLowTides[i].height) - - Number(highAndLowTides[i + 1].height); - let timeDifference = + let heightDifferenceToNextTide = + Number(highAndLowTides[i + 1].height) - + Number(highAndLowTides[i].height); + let timeDifferenceToNextTide = highAndLowTides[i + 1].timestamp - highAndLowTides[i].timestamp; for ( let t = highAndLowTides[i].timestamp; t < highAndLowTides[i + 1].timestamp; - t += 60 * 5 // 5 minutes + t += 60 //Data point every minute ) { + /** + * In a cycle from high tide to low tide, the high tide is 1/2π and the low tide is 3/2π + */ + let sinePoint = + ((highAndLowTides[i + 1].timestamp - t) / timeDifferenceToNextTide) * + Math.PI + + Math.PI / 2; plotPoints.push({ date: t, Height: Number( ( - Number(highAndLowTides[i].height) + - differenceToNextHeight * - -1 * - Math.sin( - - ((Math.PI / 2) * (t - highAndLowTides[i].timestamp)) / - timeDifference - ) + heightDifferenceToNextTide * 0.5 * Math.sin(sinePoint) + + Number( + highAndLowTides[i].type === "high" // If next tide is low, then use that height, otherwise use the current heigh + ? highAndLowTides[i + 1].height + : highAndLowTides[i].height + ) + + // Add back in half the height difference - but only as a positive value + Math.abs(heightDifferenceToNextTide) / 2 ).toFixed(3) ), }); diff --git a/src/pages/chart.tsx b/src/pages/chart.tsx deleted file mode 100644 index d9859a4..0000000 --- a/src/pages/chart.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { LineChart } from "@mantine/charts"; -import { Container, Paper, Text } from "@mantine/core"; -import type { HeadFC, PageProps } from "gatsby"; -import { DateTime } from "luxon"; -import * as React from "react"; -import TidalData from "../../data/tides.json"; -import { SEO } from "../components/SEO"; -import Layout from "../components/navigation/Layout"; -import { TidesJson_ScheduleObject } from "../types"; -import { generateTideGraphData } from "../components/tideGraph/graphDataGenerator"; - -interface ChartTooltipProps { - label: string; - payload: Record[] | undefined; -} - -function ChartTooltip({ label, payload }: ChartTooltipProps) { - if (!payload) return null; - - return ( - - - {new Date(Number(label) * 1000).toLocaleDateString("en-GB", { - day: "numeric", - month: "short", - year: "numeric", - hour: "numeric", - minute: "numeric", - })} - - {payload.map((item: any) => ( - - {item.name}: {item.value}m - - ))} - - ); -} - -const Page: React.FC = () => { - const startTimestamp = new Date(); - startTimestamp.setHours(0, 0, 0, 0); - const endTimestamp = new Date(startTimestamp); - endTimestamp.setDate(endTimestamp.getDate() + 0); // 2 - let startIndex = TidalData.schedule.findIndex( - (date: TidesJson_ScheduleObject) => { - return new Date(date.date) >= startTimestamp; - } - ); - let endIndex = TidalData.schedule.findIndex( - (date: TidesJson_ScheduleObject) => { - return new Date(date.date) > endTimestamp; - } - ); - - // Adjust indices to include the days immediately before and after the range - startIndex = startIndex > 0 ? startIndex - 1 : startIndex; - endIndex = endIndex < TidalData.schedule.length ? endIndex + 1 : endIndex; - - // Slice the array to get the desired elements - const highTides = TidalData.schedule - .slice(startIndex, endIndex) - .flatMap((date: TidesJson_ScheduleObject) => - date.groups.map((tide) => ({ - timestamp: new Date(date.date + " " + tide.time).getTime() / 1000, - height: Number(tide.height), - })) - ); - - const graphData = generateTideGraphData(highTides); - - return ( - - - - new Date(value * 1000).toLocaleDateString("en-GB", { - day: "numeric", - month: "short", - hour: "numeric", - minute: "numeric", - }), - //domain: [new Date().getTime(), nextWeek.getTime()], - }} - tooltipProps={{ - content: ({ label, payload }) => ( - - ), - }} - unit="m" - connectNulls={false} - series={[{ name: "Height", color: "indigo.6" }]} - curveType="natural" - gridAxis="y" - /> - - - ); -}; - -export default Page; - -export const Head: HeadFC = () => ; From 256a0e797a60d22454c35979dd3fd3e3d90d4aa8 Mon Sep 17 00:00:00 2001 From: James Bithell Date: Wed, 17 Jul 2024 20:36:11 +0100 Subject: [PATCH 06/12] Add link to page from tide table --- src/components/templates/TideGraphPage.tsx | 6 +++++ src/components/tideTables/TideTable.tsx | 28 ++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/templates/TideGraphPage.tsx b/src/components/templates/TideGraphPage.tsx index 90223e4..fdaf02b 100644 --- a/src/components/templates/TideGraphPage.tsx +++ b/src/components/templates/TideGraphPage.tsx @@ -11,6 +11,7 @@ import { IconArrowRight, IconDownload, IconFileTypePdf, + IconHome, } from "@tabler/icons-react"; import { TideTable } from "../tideTables/TideTable"; import { TideTableMobile } from "../tideTables/TideTableMobile"; @@ -45,6 +46,11 @@ const Page: React.FC = ({ pageContext }) => { ) : null} + + + {nextDay ? ( - + + + + + + {tides.map((element: TidesJson_ScheduleObject, index: React.Key) => ( @@ -95,21 +107,27 @@ const Page: React.FC = () => { ))} - {DateTime.fromJSDate(nextWeek).toLocaleString({ + Today's Graph + + + + + + + + + + {DateTime.fromJSDate(today).toLocaleString({ month: "long", })}{" "} Tide Table From 4ad54bcddabd36223232f693fa58bbe0e94e025b Mon Sep 17 00:00:00 2001 From: James Bithell Date: Wed, 17 Jul 2024 21:08:57 +0100 Subject: [PATCH 10/12] Do not allow heights below 0... some edge cases there! --- .../tideGraph/graphDataGenerator.ts | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/components/tideGraph/graphDataGenerator.ts b/src/components/tideGraph/graphDataGenerator.ts index c71f5f8..d3938c9 100644 --- a/src/components/tideGraph/graphDataGenerator.ts +++ b/src/components/tideGraph/graphDataGenerator.ts @@ -72,21 +72,24 @@ export const graphDataGenerator = ( ((highAndLowTides[i + 1].timestamp - t) / timeDifferenceToNextTide) * Math.PI + Math.PI / 2; - plotPoints.push({ - date: t, - Height: Number( - ( - heightDifferenceToNextTide * 0.5 * Math.sin(sinePoint) + - Number( - highAndLowTides[i].type === "high" // If next tide is low, then use that height, otherwise use the current heigh - ? highAndLowTides[i + 1].height - : highAndLowTides[i].height - ) + - // Add back in half the height difference - but only as a positive value - Math.abs(heightDifferenceToNextTide) / 2 - ).toFixed(3) - ), - }); + let derivedHeight = Number( + ( + heightDifferenceToNextTide * 0.5 * Math.sin(sinePoint) + + Number( + highAndLowTides[i].type === "high" // If next tide is low, then use that height, otherwise use the current heigh + ? highAndLowTides[i + 1].height + : highAndLowTides[i].height + ) + + // Add back in half the height difference - but only as a positive value + Math.abs(heightDifferenceToNextTide) / 2 + ).toFixed(3) + ); + if (derivedHeight >= 0) { + plotPoints.push({ + date: t, + Height: derivedHeight, + }); + } } } } From 8a5567aa3cd8a7270008f0e6ba72520eee8d8c94 Mon Sep 17 00:00:00 2001 From: James Bithell Date: Wed, 17 Jul 2024 21:28:34 +0100 Subject: [PATCH 11/12] Block all heights below 0 --- src/components/tideGraph/TidalGraphComponent.tsx | 5 ++++- src/components/tideGraph/graphDataGenerator.ts | 10 ++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/tideGraph/TidalGraphComponent.tsx b/src/components/tideGraph/TidalGraphComponent.tsx index 9eaeb25..76c9ed8 100644 --- a/src/components/tideGraph/TidalGraphComponent.tsx +++ b/src/components/tideGraph/TidalGraphComponent.tsx @@ -53,7 +53,10 @@ export function TidalGraphComponent({ data.date >= startTimestamp && data.date <= endTimestamp + (data) => + data.date >= startTimestamp && + data.date <= endTimestamp && + data.Height >= 0 )} gridAxis="none" dataKey="date" diff --git a/src/components/tideGraph/graphDataGenerator.ts b/src/components/tideGraph/graphDataGenerator.ts index d3938c9..9d8bdf7 100644 --- a/src/components/tideGraph/graphDataGenerator.ts +++ b/src/components/tideGraph/graphDataGenerator.ts @@ -84,12 +84,10 @@ export const graphDataGenerator = ( Math.abs(heightDifferenceToNextTide) / 2 ).toFixed(3) ); - if (derivedHeight >= 0) { - plotPoints.push({ - date: t, - Height: derivedHeight, - }); - } + plotPoints.push({ + date: t, + Height: derivedHeight, + }); } } } From 85fa05f5ea03fa9814f6d68e84f1b74f28294d82 Mon Sep 17 00:00:00 2001 From: James Bithell Date: Wed, 17 Jul 2024 21:45:15 +0100 Subject: [PATCH 12/12] Add disclaimer --- src/components/navigation/DataInformation.tsx | 5 ++-- src/components/templates/TideGraphPage.tsx | 29 ++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/components/navigation/DataInformation.tsx b/src/components/navigation/DataInformation.tsx index 2b14c46..900bb84 100644 --- a/src/components/navigation/DataInformation.tsx +++ b/src/components/navigation/DataInformation.tsx @@ -4,10 +4,9 @@ import React from "react"; export function DataInformation() { return ( <> - Tide times for Porthmadog, North Wales, United Kingdom. - Times are GMT/BST. Heights shown are heights above chart datum. Low - water times are not provided due to seasonal variations and river flows. + Tide times for Porthmadog, North Wales, United Kingdom. Times are + GMT/BST. Heights shown are heights above chart datum. No warranty is provided for the accuracy of data displayed. Tidal Data diff --git a/src/components/templates/TideGraphPage.tsx b/src/components/templates/TideGraphPage.tsx index ecb3a08..3637f44 100644 --- a/src/components/templates/TideGraphPage.tsx +++ b/src/components/templates/TideGraphPage.tsx @@ -1,15 +1,17 @@ import * as React from "react"; import { Link, type HeadFC, type PageProps } from "gatsby"; -import { Box, Button, Text } from "@mantine/core"; +import { Badge, Box, Button, Group, Paper, rem, Text } from "@mantine/core"; import TidalData from "../../../data/tides.json"; import { SEO } from "../SEO"; import Layout from "../navigation/Layout"; import { DateTime } from "luxon"; import { TidesJson_PDFObject, TidesJson_ScheduleObject } from "../../types"; import { + IconAlertTriangleFilled, IconArrowLeft, IconArrowRight, IconDownload, + IconExclamationCircle, IconFileTypePdf, IconHome, } from "@tabler/icons-react"; @@ -67,6 +69,31 @@ const Page: React.FC = ({ pageContext }) => { } > + + + + } + > + Warning + + + Not to be used for navigation + + + + {" "} + Tide Graphs for Porthmadog are not published by authoritative sources + and should be considered highly unreliable due to seasonal river flows + and poorly understood tidal dynamics in the estuary. This graph is + produced by extrapolating from published high water times and heights. + +