From 535cc084ad741ed2c891da73881aae733b88f631 Mon Sep 17 00:00:00 2001 From: Florian Graule Date: Wed, 17 Jul 2024 15:00:46 +0200 Subject: [PATCH] Prettier checked --- .../ScrollBarThumb.tsx | 110 ++- .../[satelliteSlug]/orbitDataGraph.tsx | 677 +++++++++++++----- .../app/satellites/[satelliteSlug]/page.tsx | 8 +- frontend/src/components/layout/Footer.tsx | 3 +- 4 files changed, 575 insertions(+), 223 deletions(-) diff --git a/frontend/src/app/satellites/[satelliteSlug]/_orbitDataGraphComponents/ScrollBarThumb.tsx b/frontend/src/app/satellites/[satelliteSlug]/_orbitDataGraphComponents/ScrollBarThumb.tsx index 1c30f36..65223ce 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/_orbitDataGraphComponents/ScrollBarThumb.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/_orbitDataGraphComponents/ScrollBarThumb.tsx @@ -1,17 +1,26 @@ "use client"; -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef } from "react"; export interface ScrollBarThumbProps { scrollBarThumbWidth: number; - svgContainerRect: {topLeft: number, width: number, height: number}; + svgContainerRect: { topLeft: number; width: number; height: number }; // eslint-disable-next-line no-unused-vars - handleChartScroll: (thumbX: number, svgContainerRect: ScrollBarThumbProps['svgContainerRect']) => void; + handleChartScroll: ( + thumbX: number, + svgContainerRect: ScrollBarThumbProps["svgContainerRect"], + ) => void; } -const ScrollBarThumb : React.FC = ({ scrollBarThumbWidth, svgContainerRect, handleChartScroll}) => { +const ScrollBarThumb: React.FC = ({ + scrollBarThumbWidth, + svgContainerRect, + handleChartScroll, +}) => { const isDragging = useRef(false); - {/* SB is used for ScrollBar */} + { + /* SB is used for ScrollBar */ + } const [sBThumbX, setSBThumbX] = useState(0); const thumbRef = useRef(null); // Distance between the left of the thumb and the mouse click @@ -23,66 +32,109 @@ const ScrollBarThumb : React.FC = ({ scrollBarThumbWidth, s if (thumbRef.current) { isDragging.current = false; } - } + }; const handleMouseMove = (e: MouseEvent) => { if (isDragging.current && thumbRef.current) { // Scrollbar starts at the right of the svg container and goes to the left by increasing SBThumbX setSBThumbX(() => { // Calculating min and max x positions following the SBThumbX axis for moving the thumb with mouse movement - const minX= 0.5; + const minX = 0.5; const maxX = svgContainerRect.width - scrollBarThumbWidth; // newPos represents left border of the thumb - const newPos = svgContainerRect.topLeft + svgContainerRect.width - scrollBarThumbWidth - (e.clientX - (distThumbClick.current? distThumbClick.current : 0)); + const newPos = + svgContainerRect.topLeft + + svgContainerRect.width - + scrollBarThumbWidth - + (e.clientX - + (distThumbClick.current + ? distThumbClick.current + : 0)); // If mouse movement isn't in the scrollable area if (newPos <= minX) { return minX; } else if (newPos >= maxX) { return maxX; - } + } return newPos; }); // Change the displayed data on the chart - handleChartScroll(Math.round(thumbRef.current.getBoundingClientRect().x * 10) / 10, svgContainerRect); + handleChartScroll( + Math.round( + thumbRef.current.getBoundingClientRect().x * 10, + ) / 10, + svgContainerRect, + ); } - } + }; - window.addEventListener('mouseup', handleMouseUp); - window.addEventListener('mousemove', handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + window.addEventListener("mousemove", handleMouseMove); // Managing the resize of the thumb - if (thumbRef.current && thumbRef.current.getBoundingClientRect().x < svgContainerRect.topLeft) { + if ( + thumbRef.current && + thumbRef.current.getBoundingClientRect().x < + svgContainerRect.topLeft + ) { setSBThumbX(svgContainerRect.width - scrollBarThumbWidth); } return () => { - window.removeEventListener('mouseup', handleMouseUp); - window.removeEventListener('mousemove', handleMouseMove); - } + window.removeEventListener("mouseup", handleMouseUp); + window.removeEventListener("mousemove", handleMouseMove); + }; }, [scrollBarThumbWidth, svgContainerRect]); - const handleMouseDown = (e: React.MouseEvent) => { + const handleMouseDown = ( + e: React.MouseEvent, + ) => { // If the thumb is clicked with left mouse button, we start dragging if (thumbRef.current && e.button === 0) { isDragging.current = true; - distThumbClick.current = e.clientX - thumbRef.current.getBoundingClientRect().left; + distThumbClick.current = + e.clientX - thumbRef.current.getBoundingClientRect().left; } - } + }; return ( <> - - - - + + + + ); -} +}; -export default ScrollBarThumb; \ No newline at end of file +export default ScrollBarThumb; diff --git a/frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx b/frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx index 1b3f892..5c6295f 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx @@ -1,199 +1,496 @@ "use client"; -import React, { useState, useLayoutEffect, useRef } from 'react' -import { XAxis, CartesianGrid, Line, LineChart, Tooltip, YAxis, ResponsiveContainer, Legend } from 'recharts'; -import { LaunchDateCountDownProps } from './launchDateCountDown'; -import ScrollBarThumb, { ScrollBarThumbProps } from './_orbitDataGraphComponents/ScrollBarThumb'; +import React, { useState, useLayoutEffect, useRef } from "react"; +import { + XAxis, + CartesianGrid, + Line, + LineChart, + Tooltip, + YAxis, + ResponsiveContainer, + Legend, +} from "recharts"; +import { LaunchDateCountDownProps } from "./launchDateCountDown"; +import ScrollBarThumb, { + ScrollBarThumbProps, +} from "./_orbitDataGraphComponents/ScrollBarThumb"; type OrbitDataProps = { - launchDateString: LaunchDateCountDownProps['launchDate']; - orbitalData: any; -} + launchDateString: LaunchDateCountDownProps["launchDate"]; + orbitalData: any; +}; type ChartData = { - epoch: Date; - inclination: number; - eccentricity: number; - semiMajorAxis: number; -} - -const OrbitDataGraph : React.FC = ({ launchDateString, orbitalData }) => { - - // href for the svg component, tracking the size of the container - const svgContainer = useRef(null); - const [svgSize, setSvgSize] = useState({width: 0, height: 200}); - {/* SB use for ScrollBar*/} - const [scrollBarThumbWidth, setSBThumbWidth] = useState(0); - // scrollBarTimeFrame is how many months the scrollbar thumb represents - const scrollBarTimeFrame = useRef(0); - // Chart Data - const [chartData, setChartData] = useState([]); - - // Handling button for zooming in and out of the graph on a time scale - const launchDate = launchDateString? new Date(launchDateString) : new Date(); - const calculateMonthsDiff = () => { - const currentDate = new Date(); - return currentDate.getMonth() - launchDate.getMonth() + (12 * (currentDate.getFullYear() - launchDate.getFullYear())); - } - - const months = calculateMonthsDiff(); - - const handleZoomClick = (e: React.MouseEvent) => { - // We round the width of the scrollbar thumb at one decimal - const period = e.currentTarget.textContent; - const regExp = new RegExp('^([1234567890]+)([my])$'); - const match = period?.match(regExp); - let SBThumbWidth = 0; - - if (period === "All") { - SBThumbWidth = svgSize.width*0.8-40.5; - scrollBarTimeFrame.current = months; - setSBThumbWidth(SBThumbWidth); - } else { - const number = match ? parseInt(match[1]) : 0; - const periodType = match? match[2] : ""; - - if (periodType === "m") { - scrollBarTimeFrame.current = number; - SBThumbWidth = Math.round((svgSize.width*0.8 * number *10) / months) / 10; - setSBThumbWidth(SBThumbWidth); - } else if (periodType === "y") { - scrollBarTimeFrame.current = number * 12; - SBThumbWidth = Math.round((svgSize.width*0.8 * number * 12 *10) / months) / 10 - setSBThumbWidth(SBThumbWidth); - } - } - // Updating chart due to resizing the scrollbar thumb - if (svgContainer.current) { - handleChartScroll( - svgContainer.current.getBoundingClientRect().x + 0.9*svgContainer.current.getBoundingClientRect().width - 20.5 - SBThumbWidth, - {topLeft: svgContainer.current.getBoundingClientRect().x + svgSize.width*0.1 + 20.5, width: svgSize.width*0.8-40.5, height: 20} - ); + epoch: Date; + inclination: number; + eccentricity: number; + semiMajorAxis: number; +}; + +const OrbitDataGraph: React.FC = ({ + launchDateString, + orbitalData, +}) => { + // href for the svg component, tracking the size of the container + const svgContainer = useRef(null); + const [svgSize, setSvgSize] = useState({ width: 0, height: 200 }); + { + /* SB use for ScrollBar*/ } - } - - - // Callback function for updating the chart when the scrollbar thumb is moved - const handleChartScroll = (thumbX: number, svgContainerRect: ScrollBarThumbProps['svgContainerRect']) => { - - // Ratio of the thumb left border position to the svg container width - const dateRatio = (thumbX - svgContainerRect.topLeft)/svgContainerRect.width; - // Taking the last date of the data period (first date is the launch date) - const lastDataDate = new Date(orbitalData[orbitalData.length-1].epoch.slice(0, 23) + "Z"); - // Calculating the displayed period in milliseconds - const displayedPeriodMs = lastDataDate.getTime() - launchDate.getTime(); - // Calculating the first and last date of the chart - const firstChartDate = new Date(launchDate.getTime() + displayedPeriodMs * dateRatio); - const lastChartDate = new Date(firstChartDate); - lastChartDate.setMonth(firstChartDate.getMonth() + scrollBarTimeFrame.current); - - // Filtering the data to display only the data in the selected period - const filteredData = orbitalData.filter((data: any) => { - const dataDate = new Date(data.epoch.slice(0, 23) + "Z"); - return dataDate >= firstChartDate && dataDate <= lastChartDate; - }).map((data: any) => { - return { - ...data, - epoch: new Date(data.epoch.slice(0, 23) + "Z") - }; - }); - - setChartData(filteredData); - }; - - // Layout effect to track the size of the container and update the svg size - useLayoutEffect(() => { - const updateSize = () => { - if (svgContainer.current) { - // Update svg container size - const width = svgContainer.current.offsetWidth; - setSvgSize((prevSvgSize) => {return { width: width, height: prevSvgSize.height }}); - // Initially set the scrollbar thumb width to represent 1/10 of the total period - !scrollBarTimeFrame.current? scrollBarTimeFrame.current = Math.round(months/10) : null; - // Setting the scrollbar thumb width - const newSBThumbWidth = Math.round((width*0.8 * scrollBarTimeFrame.current *10) / months) / 10; - setSBThumbWidth(newSBThumbWidth); - handleChartScroll( - svgContainer.current.getBoundingClientRect().x + 0.9*svgContainer.current.getBoundingClientRect().width - 20.5 - newSBThumbWidth, - {topLeft: svgContainer.current.getBoundingClientRect().x + width*0.1 + 20.5, width: width*0.8-40.5, height: 20} + const [scrollBarThumbWidth, setSBThumbWidth] = useState(0); + // scrollBarTimeFrame is how many months the scrollbar thumb represents + const scrollBarTimeFrame = useRef(0); + // Chart Data + const [chartData, setChartData] = useState([]); + + // Handling button for zooming in and out of the graph on a time scale + const launchDate = launchDateString + ? new Date(launchDateString) + : new Date(); + const calculateMonthsDiff = () => { + const currentDate = new Date(); + return ( + currentDate.getMonth() - + launchDate.getMonth() + + 12 * (currentDate.getFullYear() - launchDate.getFullYear()) ); - } - } - window.addEventListener('resize', updateSize); - updateSize(); - - return () => window.removeEventListener('resize', updateSize); - }, []); - - return ( - <> - {orbitalData &&
-
-

Zoom :

- {/* Scrollbar thumb represents the zoom period selected, in case it fits bad we don't display + }; + + const months = calculateMonthsDiff(); + + const handleZoomClick = (e: React.MouseEvent) => { + // We round the width of the scrollbar thumb at one decimal + const period = e.currentTarget.textContent; + const regExp = new RegExp("^([1234567890]+)([my])$"); + const match = period?.match(regExp); + let SBThumbWidth = 0; + + if (period === "All") { + SBThumbWidth = svgSize.width * 0.8 - 40.5; + scrollBarTimeFrame.current = months; + setSBThumbWidth(SBThumbWidth); + } else { + const number = match ? parseInt(match[1]) : 0; + const periodType = match ? match[2] : ""; + + if (periodType === "m") { + scrollBarTimeFrame.current = number; + SBThumbWidth = + Math.round((svgSize.width * 0.8 * number * 10) / months) / + 10; + setSBThumbWidth(SBThumbWidth); + } else if (periodType === "y") { + scrollBarTimeFrame.current = number * 12; + SBThumbWidth = + Math.round( + (svgSize.width * 0.8 * number * 12 * 10) / months, + ) / 10; + setSBThumbWidth(SBThumbWidth); + } + } + // Updating chart due to resizing the scrollbar thumb + if (svgContainer.current) { + handleChartScroll( + svgContainer.current.getBoundingClientRect().x + + 0.9 * svgContainer.current.getBoundingClientRect().width - + 20.5 - + SBThumbWidth, + { + topLeft: + svgContainer.current.getBoundingClientRect().x + + svgSize.width * 0.1 + + 20.5, + width: svgSize.width * 0.8 - 40.5, + height: 20, + }, + ); + } + }; + + // Callback function for updating the chart when the scrollbar thumb is moved + const handleChartScroll = ( + thumbX: number, + svgContainerRect: ScrollBarThumbProps["svgContainerRect"], + ) => { + // Ratio of the thumb left border position to the svg container width + const dateRatio = + (thumbX - svgContainerRect.topLeft) / svgContainerRect.width; + // Taking the last date of the data period (first date is the launch date) + const lastDataDate = new Date( + orbitalData[orbitalData.length - 1].epoch.slice(0, 23) + "Z", + ); + // Calculating the displayed period in milliseconds + const displayedPeriodMs = lastDataDate.getTime() - launchDate.getTime(); + // Calculating the first and last date of the chart + const firstChartDate = new Date( + launchDate.getTime() + displayedPeriodMs * dateRatio, + ); + const lastChartDate = new Date(firstChartDate); + lastChartDate.setMonth( + firstChartDate.getMonth() + scrollBarTimeFrame.current, + ); + + // Filtering the data to display only the data in the selected period + const filteredData = orbitalData + .filter((data: any) => { + const dataDate = new Date(data.epoch.slice(0, 23) + "Z"); + return dataDate >= firstChartDate && dataDate <= lastChartDate; + }) + .map((data: any) => { + return { + ...data, + epoch: new Date(data.epoch.slice(0, 23) + "Z"), + }; + }); + + setChartData(filteredData); + }; + + // Layout effect to track the size of the container and update the svg size + useLayoutEffect(() => { + const updateSize = () => { + if (svgContainer.current) { + // Update svg container size + const width = svgContainer.current.offsetWidth; + setSvgSize((prevSvgSize) => { + return { width: width, height: prevSvgSize.height }; + }); + // Initially set the scrollbar thumb width to represent 1/10 of the total period + !scrollBarTimeFrame.current + ? (scrollBarTimeFrame.current = Math.round(months / 10)) + : null; + // Setting the scrollbar thumb width + const newSBThumbWidth = + Math.round( + (width * 0.8 * scrollBarTimeFrame.current * 10) / + months, + ) / 10; + setSBThumbWidth(newSBThumbWidth); + handleChartScroll( + svgContainer.current.getBoundingClientRect().x + + 0.9 * + svgContainer.current.getBoundingClientRect().width - + 20.5 - + newSBThumbWidth, + { + topLeft: + svgContainer.current.getBoundingClientRect().x + + width * 0.1 + + 20.5, + width: width * 0.8 - 40.5, + height: 20, + }, + ); + } + }; + window.addEventListener("resize", updateSize); + updateSize(); + + return () => window.removeEventListener("resize", updateSize); + }, []); + + return ( + <> + {orbitalData && ( +
+
+

Zoom :

+ {/* Scrollbar thumb represents the zoom period selected, in case it fits bad we don't display ie. containerSize > SBThumbWidth > 20px */} - { (Math.round((svgSize.width*0.8 * 1 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 1 * 10) / months) / 10 > 20) && } - { (Math.round((svgSize.width*0.8 * 3 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 3 * 10) / months) / 10 > 40) && } - { (Math.round((svgSize.width*0.8 * 6 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 6 * 10) / months) / 10 > 40) && } - { (Math.round((svgSize.width*0.8 * 12 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 12 * 10) / months) / 10 > 40) && } - { (Math.round((svgSize.width*0.8 * 12 * 5 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 12 * 5 * 10) / months) / 10 > 40) && } - { (Math.round((svgSize.width*0.8 * 12 * 10 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 12 * 10 * 10) / months) / 10 > 40) && } - { (Math.round((svgSize.width*0.8 * 12 * 20 * 10) / months) / 10 < svgSize.width) && (Math.round((svgSize.width*0.8 * 12 * 20 * 10) / months) / 10 > 40) && } - - -
-
- {/* Chart */} - - - - - - {return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }).format(date);}} interval={Math.floor(chartData.length / 6)-1} /> - (0.999*dataMin), (dataMax: number) => (dataMax * 1.001)]} tick={{ fill: '#8884d8' }} tickFormatter={(tick: number) => tick.toFixed(1)} tickLine={false}> - - (0.9*dataMin), (dataMax: number) => (dataMax * 1.1)]} tick={{ fill: '#82ca9d'}} tickFormatter={(tick: number) => tick.toFixed(4)} tickLine={false}> - - (0.999*dataMin), (dataMax: number) => (dataMax * 1.001)]} tick={{ fill: '#ff0000'}} tickFormatter={(tick: number) => tick.toFixed(0)} tickLine={false}> - - - {return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year:'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' }).format(date)}} - contentStyle={{ backgroundColor: 'black' }} - /> - - - - - {/* Scrollbar for time navigation */} - - - - - - - {/* Scrollbar left navigation arrow */} - - - - - {/* Scrollbar right navigation arrow */} - - - - - {/* Scrollbar thumb */} - - - -
-
} - - ) -} - -export default OrbitDataGraph; \ No newline at end of file + {Math.round((svgSize.width * 0.8 * 1 * 10) / months) / + 10 < + svgSize.width && + Math.round( + (svgSize.width * 0.8 * 1 * 10) / months, + ) / + 10 > + 20 && ( + + )} + {Math.round((svgSize.width * 0.8 * 3 * 10) / months) / + 10 < + svgSize.width && + Math.round( + (svgSize.width * 0.8 * 3 * 10) / months, + ) / + 10 > + 40 && ( + + )} + {Math.round((svgSize.width * 0.8 * 6 * 10) / months) / + 10 < + svgSize.width && + Math.round( + (svgSize.width * 0.8 * 6 * 10) / months, + ) / + 10 > + 40 && ( + + )} + {Math.round((svgSize.width * 0.8 * 12 * 10) / months) / + 10 < + svgSize.width && + Math.round( + (svgSize.width * 0.8 * 12 * 10) / months, + ) / + 10 > + 40 && ( + + )} + {Math.round( + (svgSize.width * 0.8 * 12 * 5 * 10) / months, + ) / + 10 < + svgSize.width && + Math.round( + (svgSize.width * 0.8 * 12 * 5 * 10) / months, + ) / + 10 > + 40 && ( + + )} + {Math.round( + (svgSize.width * 0.8 * 12 * 10 * 10) / months, + ) / + 10 < + svgSize.width && + Math.round( + (svgSize.width * 0.8 * 12 * 10 * 10) / months, + ) / + 10 > + 40 && ( + + )} + {Math.round( + (svgSize.width * 0.8 * 12 * 20 * 10) / months, + ) / + 10 < + svgSize.width && + Math.round( + (svgSize.width * 0.8 * 12 * 20 * 10) / months, + ) / + 10 > + 40 && ( + + )} + + +
+
+ {/* Chart */} + + + + + + { + return new Intl.DateTimeFormat( + "en-US", + { month: "short", day: "numeric" }, + ).format(date); + }} + interval={ + Math.floor(chartData.length / 6) - 1 + } + /> + 0.999 * dataMin, + (dataMax: number) => dataMax * 1.001, + ]} + tick={{ fill: "#8884d8" }} + tickFormatter={(tick: number) => + tick.toFixed(1) + } + tickLine={false} + > + 0.9 * dataMin, + (dataMax: number) => dataMax * 1.1, + ]} + tick={{ fill: "#82ca9d" }} + tickFormatter={(tick: number) => + tick.toFixed(4) + } + tickLine={false} + > + 0.999 * dataMin, + (dataMax: number) => dataMax * 1.001, + ]} + tick={{ fill: "#ff0000" }} + tickFormatter={(tick: number) => + tick.toFixed(0) + } + tickLine={false} + > + + { + return new Intl.DateTimeFormat( + "en-US", + { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric", + }, + ).format(date); + }} + contentStyle={{ backgroundColor: "black" }} + /> + + + + + {/* Scrollbar for time navigation */} + + + + + + + {/* Scrollbar left navigation arrow */} + + + + + {/* Scrollbar right navigation arrow */} + + + + + {/* Scrollbar thumb */} + + + +
+
+ )} + + ); +}; + +export default OrbitDataGraph; diff --git a/frontend/src/app/satellites/[satelliteSlug]/page.tsx b/frontend/src/app/satellites/[satelliteSlug]/page.tsx index 7b47630..5ee8338 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/page.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/page.tsx @@ -158,11 +158,13 @@ export default async function SatelliteInfoPage({ {/* Orbit data */}
{/*Pass the satNum and the launchDate as props to OrbitDataGraph*/} - { noradId? ( + {noradId ? ( satAttributes?.launchDate ? ( - + ) : null - ) : null}
diff --git a/frontend/src/components/layout/Footer.tsx b/frontend/src/components/layout/Footer.tsx index 846f34a..05759fe 100644 --- a/frontend/src/components/layout/Footer.tsx +++ b/frontend/src/components/layout/Footer.tsx @@ -51,7 +51,8 @@ export default function Footer() {

- © 2017 - {now.getFullYear()} NTNU Small Satellite Lab + © 2017 - {now.getFullYear()} NTNU Small + Satellite Lab