diff --git a/frontend/src/app/_homeComponents/DailySolarActivity.tsx b/frontend/src/app/_homeComponents/DailySolarActivity.tsx index d970c9c..dfeb3c5 100644 --- a/frontend/src/app/_homeComponents/DailySolarActivity.tsx +++ b/frontend/src/app/_homeComponents/DailySolarActivity.tsx @@ -38,6 +38,7 @@ export default function DailySolarActivity() { console.error("Error fetching Kp index data:", error); }); }, []); + const optionsDailyCharts = { chart: { type: "column", diff --git a/frontend/src/app/_homeComponents/HistoricalSolarCycleData.tsx b/frontend/src/app/_homeComponents/HistoricalSolarCycleData.tsx index b8b22fd..6e0b0f7 100644 --- a/frontend/src/app/_homeComponents/HistoricalSolarCycleData.tsx +++ b/frontend/src/app/_homeComponents/HistoricalSolarCycleData.tsx @@ -1,14 +1,14 @@ "use client"; -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; import Highcharts from "highcharts/highstock"; import HighchartsReact from "highcharts-react-official"; +import { createStockChartBaseConfig } from "@/lib/chartTemplate"; export default function HistoricalSolarCycleData() { const [historicalSunSpot, setHistoricalSunSpot] = useState(null); const [historicalTimestamps, setHistoricalTimestamps] = useState( [], ); - const stockChart = useRef(null); useEffect(() => { fetch("https://services.swpc.noaa.gov/json/solar-cycle/sunspots.json") @@ -66,151 +66,11 @@ export default function HistoricalSolarCycleData() { formattedHistoricalData, 10, ); - const optionsHistoricalChart = { - chart: { - backgroundColor: "transparent", - reflow: true, - zoomType: "x", // Enable zooming on the x-axis - }, - title: { - margin: 20, - text: "Solar Cycle Progression - Historical Data", - style: { - color: "#ffffff", - fontSize: "24px", - }, - }, - xAxis: { - categories: "datetime", - title: { - style: { - color: "#ffffff", - fontSize: "18px", - }, - margin: 20, - }, - labels: { - style: { - color: "#ffffff", - fontSize: "14px", - }, - formatter: function ( - // eslint-disable-next-line no-unused-vars - this: Highcharts.AxisLabelsFormatterContextObject, - ): any { - const extremes = this.axis.getExtremes(); - const zoomLevel = extremes.max - extremes.min; // Get the current zoom range - if (zoomLevel <= 18 * 30 * 24 * 3600 * 1000) { - // If zoomed in to less than or equal to 1 month - return Highcharts.dateFormat( - "%b %Y", - Number(this.value), - ); // Show month and year - } else { - return Highcharts.dateFormat("%Y", Number(this.value)); // Show only the year - } - }, - }, - tickColor: "#ffffff", // Customize the tick color - lineColor: "#ffffff", // Customize the axis line color - }, - - yAxis: { - opposite: false, // Place the y-axis on the right side - title: { - text: "Sunspot Number (SSN)", - style: { - color: "#ffffff", - fontSize: "18px", - }, - }, - labels: { - style: { - color: "#ffffff", - fontSize: "14px", - }, - }, - tickColor: "#ffffff", // Customize the tick color - lineColor: "#ffffff", // Customize the axis line color - }, - - navigator: { - enabled: true, // Enable the navigator (zoom scroll bar) - outlineColor: "#ffffff", // Customize the outline color - maskFill: "rgba(255, 255, 255, 0.2)", // Customize the mask fill - handles: { - backgroundColor: "#ffffff", // Customize the handles - borderColor: "#000000", - }, - }, - rangeSelector: { - enabled: true, // Enable the range selector - buttonSpacing: 10, // Space between buttons - buttonTheme: { - fill: "#333333", - stroke: "#ffffff", - "stroke-width": 2, // Border width - r: 5, - width: 100, // Explicitly set the button width - height: 30, // Explicitly set the button height - style: { - color: "#ffffff", - fontSize: "14px", - fontWeight: "bold", - whiteSpace: "nowrap", // Prevent text wrapping - }, - states: { - hover: { - fill: "#555555", // Background color on hover - style: { - color: "#ffffff", // Text color on hover - }, - }, - select: { - fill: "#00d3ff", // Background color when selected - style: { - color: "#000000", // Text color when selected - }, - }, - }, - }, - labelStyle: { - color: "#ffffff", // Customize the "Zoom" label color - fontSize: "14px", - }, - - inputEnabled: false, // Disable input fields for date selection - selected: 0, - buttons: [ - { - type: "custom", - text: "Default", - events: { - click: function () { - if (stockChart.current) { - const chart = stockChart.current.chart; - const now = new Date().getTime(); // Current timestamp - const twentyFiveYearsAgo = - new Date().setFullYear( - new Date().getFullYear() - 25, - ); // Timestamp for 25 years ago - - // Set the x-axis extremes to the last 25 years - chart.xAxis[0].setExtremes( - twentyFiveYearsAgo, - now, - ); - } - }, - }, - }, - { - type: "all", - text: "All", - }, - ], - }, + const optionsHistoricalChart = createStockChartBaseConfig({ + title: "Historical Solar Cycle Data", + yAxisTitle: "Sunspot Number (SSN)", + yAxisArray: [], series: [ { name: "Sunspot Number", @@ -225,46 +85,17 @@ export default function HistoricalSolarCycleData() { color: "#ffabc8", // Customize the smoothed line color }, ], + legend: { - enabled: true, // Hide legend for cleaner look + enabled: true, itemStyle: { - color: "#ffffff", // Customize legend text color - fontSize: "14px", // Customize legend text size + color: "#ffffff", + fontSize: "14px", }, itemHoverStyle: { - color: "#00d3ff", // Customize legend hover text color + color: "#00d3ff", }, }, - responsive: { - rules: [ - { - condition: { - maxWidth: 600, // Apply this rule for small screens - }, - chartOptions: { - title: { - style: { - fontSize: "18px", - }, - }, - xAxis: { - labels: { - style: { - fontSize: "12px", - }, - }, - }, - yAxis: { - labels: { - style: { - fontSize: "12px", - }, - }, - }, - }, - }, - ], - }, credits: { enabled: true, text: "Data Source: NOAA SWPC", @@ -275,19 +106,7 @@ export default function HistoricalSolarCycleData() { textDecoration: "underline", }, }, - }; - useEffect(() => { - if (stockChart.current) { - const chart = stockChart.current.chart; - const now = new Date().getTime(); // Current timestamp - const twentyFiveYearsAgo = new Date().setFullYear( - new Date().getFullYear() - 25, - ); // Timestamp for 25 years ago - - // Set the x-axis extremes to the last 25 years - chart.xAxis[0].setExtremes(twentyFiveYearsAgo, now); - } - }, [stockChart]); + }); return (
); diff --git a/frontend/src/app/satellites/[satelliteSlug]/_infoSat/satImage.tsx b/frontend/src/app/satellites/[satelliteSlug]/_infoSat/satImage.tsx index f2ad65a..68cb0e9 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/_infoSat/satImage.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/_infoSat/satImage.tsx @@ -84,7 +84,6 @@ export default function SatImage({ files: SlackFile[]; } - console.log("Fetched Slack images:", data); const rightMessage: SlackMessage | undefined = ( data as SlackMessage[] ).find((message: SlackMessage) => { diff --git a/frontend/src/app/satellites/[satelliteSlug]/_infoSat/satTelemetry.tsx b/frontend/src/app/satellites/[satelliteSlug]/_infoSat/satTelemetry.tsx index c5e53fc..5583ea3 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/_infoSat/satTelemetry.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/_infoSat/satTelemetry.tsx @@ -87,7 +87,6 @@ export default function SatTelemetry({ batteryVoltageData[1][index] / 1000, ], ); - { /* Battery Current Data */ } diff --git a/frontend/src/app/satellites/[satelliteSlug]/_infoSat/tabBars.tsx b/frontend/src/app/satellites/[satelliteSlug]/_infoSat/tabBars.tsx index e33588f..60a641f 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/_infoSat/tabBars.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/_infoSat/tabBars.tsx @@ -9,7 +9,10 @@ import { useTabContext } from "@/app/satellites/tabContext"; export default function TabBar() { const { selectedTab, setSelectedTab } = useTabContext(); return ( -
+
@@ -27,6 +32,8 @@ export default function TabBar() { : "border-gray-600" } bg-black p-5 text-white hover:bg-gray-700`} onClick={() => setSelectedTab("satellite image")} + aria-selected={selectedTab === "satellite image"} + role="tab" > Satellite Last Image @@ -37,6 +44,8 @@ export default function TabBar() { : "border-gray-600" } bg-black p-5 text-white hover:bg-gray-700`} onClick={() => setSelectedTab("satellite telemetry")} + aria-selected={selectedTab === "satellite telemetry"} + role="tab" > Satellite Telemetry diff --git a/frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx b/frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx index affe3da..5c2ee6e 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/orbitDataGraph.tsx @@ -1,518 +1,176 @@ "use client"; -import React, { - useState, - useLayoutEffect, - useRef, - useCallback, - useMemo, -} 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 from "react"; +import Highcharts from "highcharts/highstock"; +import HighchartsReact from "highcharts-react-official"; +import { createStockChartBaseConfig } from "@/lib/chartTemplate"; type OrbitDataProps = { - launchDateString: LaunchDateCountDownProps["launchDate"]; orbitalData: any; }; type ChartData = { epoch: Date; - inclination: number; - eccentricity: number; + inclination: string; + eccentricity: string; 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 = useMemo( - () => (launchDateString ? new Date(launchDateString) : new Date()), - [launchDateString], +const OrbitDataGraph: React.FC = ({ orbitalData }) => { + const filteredData = orbitalData.map((data: any) => { + return { + ...data, + semiMajorAxis: data.semiMajorAxis - 6371, + epoch: new Date(data.epoch.slice(0, 23) + "Z"), + }; + }); + + const inclination = filteredData.map((data: ChartData) => [ + data.epoch.getTime(), + parseFloat(data.inclination), + ]); + const eccentricity = filteredData.map((data: ChartData) => [ + data.epoch.getTime(), + parseFloat(data.eccentricity), + ]); + const altitude = filteredData.map((data: ChartData) => [ + data.epoch.getTime(), + data.semiMajorAxis, + ]); + + console.log( + "Filtered orbital data:", + filteredData.map((data: ChartData) => [ + data.epoch.getTime(), + parseFloat(data.inclination), + ]), ); - 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, + const optChart = createStockChartBaseConfig({ + title: "Orbital Graph Data", + yAxisArray: [ + // First Y-axis (left) - Inclination + { + id: "inclination-axis", + title: { + text: "", }, - ); - } - }; - - // Callback function for updating the chart when the scrollbar thumb is moved - const handleChartScroll = useCallback( - ( - 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, - semiMajorAxis: data.semiMajorAxis - 6371, - epoch: new Date(data.epoch.slice(0, 23) + "Z"), - }; - }); - - setChartData(filteredData); - }, - [orbitalData, launchDate, scrollBarTimeFrame, setChartData], - ); - - // 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, + labels: { + style: { + color: "#00d3ff", // All numbers in white + fontSize: "14px", }, - ); - } - }; - window.addEventListener("resize", updateSize); - updateSize(); - - return () => window.removeEventListener("resize", updateSize); - }, [handleChartScroll, months]); + }, + }, + // Second Y-axis (right) - Eccentricity + { + id: "eccentricity-axis", + title: { + text: "", + }, + labels: { + style: { + color: "#ff0000", // All numbers in red + fontSize: "14px", + }, + }, + opposite: true, // Right side + }, + // Third Y-axis (far right) - Altitude + { + id: "altitude-axis", + title: { + text: "", + }, + labels: { + style: { + color: "#d3ff00", // All numbers in green + fontSize: "14px", + }, + }, + }, + ], + series: [ + { + name: "Inclination (°)", + data: inclination, + type: "line", + color: "#00d3ff", + marker: { + enabled: false, // Disable markers for cleaner look + }, + yAxis: "inclination-axis", // Link to first Y-axis + }, + { + name: "Eccentricity", + data: eccentricity, + type: "line", + color: "red", + marker: { + enabled: false, // Disable markers for cleaner look + }, + yAxis: "eccentricity-axis", // Link to second Y-axis + }, + { + name: "Altitude (km)", + data: altitude, + type: "line", + color: "#d3ff00", + marker: { + enabled: false, // Disable markers for cleaner look + }, + yAxis: "altitude-axis", // Link to third Y-axis + }, + ], + buttons: [ + { + type: "all", + text: "All", + }, + { + type: "year", + count: 1, + text: "1Y", + }, + { type: "ytd", count: 1, text: "YTD" }, + { + type: "month", + count: 6, + text: "6M", + }, + { + type: "month", + count: 3, + text: "3M", + }, + { + type: "month", + count: 1, + text: "1M", + }, + ], + legend: { + enabled: true, + itemStyle: { + color: "#ffffff", + fontSize: "14px", + }, + itemHoverStyle: { + color: "#00d3ff", + }, + }, + }); return ( - <> +
{orbitalData && ( -
-
-

Orbital parameters history (source :

- - Space Track - -

)

-
-
-

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 */} - - - -
-
+ )} - +
); }; diff --git a/frontend/src/app/satellites/[satelliteSlug]/page.tsx b/frontend/src/app/satellites/[satelliteSlug]/page.tsx index 5b13f73..03c91d6 100644 --- a/frontend/src/app/satellites/[satelliteSlug]/page.tsx +++ b/frontend/src/app/satellites/[satelliteSlug]/page.tsx @@ -11,9 +11,13 @@ import { import { SatelliteNumber } from "@/lib/store"; import { graphql } from "@/lib/tada/graphql"; import { getClient } from "@/lib/ApolloClient"; -import OrbitDataGraph from "./orbitDataGraph"; import SatInfo from "./_infoSat/satInfo"; import { SatAttributes } from "@/lib/utils"; +import dynamic from "next/dynamic"; + +const OrbitDataGraph = dynamic(() => import("./orbitDataGraph"), { + ssr: false, +}); export interface ProjectOrSatellite { id: string; @@ -115,7 +119,6 @@ export default async function SatelliteInfoPage({ {noradId ? ( satAttributes?.launchDate ? ( ) : null diff --git a/frontend/src/lib/chartTemplate.tsx b/frontend/src/lib/chartTemplate.tsx new file mode 100644 index 0000000..9fde65e --- /dev/null +++ b/frontend/src/lib/chartTemplate.tsx @@ -0,0 +1,193 @@ +import Highcharts from "highcharts/highstock"; + +interface ChartTheme { + background?: string; + text?: string; + accent?: string; + line?: string; + maskFill?: string; + handleBorder?: string; +} + +interface StockChartBaseConfigProps { + title: string; + yAxisArray: Highcharts.YAxisOptions[]; + yAxisTitle?: string; + theme?: ChartTheme; + enableNavigator?: boolean; + enableZoom?: boolean; + xAxisMargin?: number; + titleMargin?: number; + series?: Highcharts.SeriesOptionsType[]; + buttons?: Highcharts.RangeSelectorButtonsOptions[]; + legend?: Highcharts.LegendOptions; + credits?: Highcharts.CreditsOptions; + customOptions?: Highcharts.Options; +} + +export function createStockChartBaseConfig({ + title, + yAxisArray = [], + yAxisTitle = "", + theme = { + background: "transparent", + text: "#ffffff", + accent: "#00d3ff", + line: "#ffffff", + maskFill: "rgba(255, 255, 255, 0.2)", + handleBorder: "#000000", + }, + enableNavigator = true, + enableZoom = true, + xAxisMargin = 20, + titleMargin = 20, + customOptions = {}, + legend = {}, + series = [], + buttons = [ + { + type: "all", + text: "All", + }, + { + type: "year", + count: 1, + text: "1Y", + }, + { type: "ytd", count: 1, text: "YTD" }, + ], + credits = { + enabled: false, // Disable credits by default + }, +}: StockChartBaseConfigProps) { + if (yAxisArray.length === 0) { + yAxisArray.push({ + title: { + text: yAxisTitle, + style: { + color: theme.text, + fontSize: "18px", + }, + }, + labels: { + style: { + color: theme.text, + fontSize: "14px", + }, + }, + tickColor: theme.line, + lineColor: theme.line, + }); + } + return { + chart: { + backgroundColor: theme.background, + reflow: true, + zoomType: enableZoom ? "x" : undefined, + }, + title: { + margin: titleMargin, + text: title, + style: { + color: theme.text, + fontSize: "24px", + }, + }, + xAxis: { + type: "datetime", + title: { + style: { + color: theme.text, + fontSize: "18px", + }, + margin: xAxisMargin, + }, + labels: { + style: { + color: theme.text, + fontSize: "14px", + }, + formatter: function ( + //eslint-disable-next-line no-unused-vars + this: Highcharts.AxisLabelsFormatterContextObject, + ): string { + const extremes = this.axis.getExtremes(); + const zoomLevel = extremes.max - extremes.min; // Get the current zoom range + if (zoomLevel <= 10 * 12 * 30 * 24 * 3600 * 1000) { + // If zoomed in to less than or equal to 1 month + return Highcharts.dateFormat( + "%b %Y", + Number(this.value), + ); // Show month and year + } else { + return Highcharts.dateFormat("%Y", Number(this.value)); // Show only the year + } + }, + }, + tickColor: theme.line, + lineColor: theme.line, + }, + yAxis: yAxisArray.map((axis: Highcharts.YAxisOptions) => ({ + ...axis, + })), + navigator: { + enabled: enableNavigator, + outlineColor: theme.line, + maskFill: theme.maskFill, + handles: { + backgroundColor: theme.line, + borderColor: theme.handleBorder, + }, + }, + rangeSelector: { + enabled: true, // Enable the range selector + buttonSpacing: 10, // Space between buttons + buttonTheme: { + fill: "#333333", + stroke: "#ffffff", + "stroke-width": 2, // Border width + r: 5, + width: 100, // Explicitly set the button width + height: 30, // Explicitly set the button height + style: { + color: "#ffffff", + fontSize: "14px", + fontWeight: "bold", + whiteSpace: "nowrap", // Prevent text wrapping + }, + states: { + hover: { + fill: "#555555", // Background color on hover + style: { + color: "#ffffff", // Text color on hover + }, + }, + select: { + fill: "#00d3ff", // Background color when selected + style: { + color: "#000000", // Text color when selected + }, + }, + }, + }, + labelStyle: { + color: "#ffffff", // Customize the "Zoom" label color + fontSize: "14px", + }, + inputEnabled: false, // Disable input fields + buttons: buttons, // Use the provided buttons + selected: 0, // Default selected button + }, + legend: legend, + credits: credits, + series: series.map((serie) => ({ + ...serie, + showInNavigator: true, // Ensure series appear in the navigator + navigatorOptions: { + type: "line", + lineWidth: 1, + }, + })), + ...customOptions, + }; +} diff --git a/frontend/tests/e2e/satellite.desktop.spec.ts b/frontend/tests/e2e/satellite.desktop.spec.ts index b131e4e..bbef78d 100644 --- a/frontend/tests/e2e/satellite.desktop.spec.ts +++ b/frontend/tests/e2e/satellite.desktop.spec.ts @@ -43,4 +43,24 @@ test.describe("Individual Satellite Page Test", () => { await slider.fill("180"); //Prediction of position in 180 minutes await expect(slider).toHaveValue("180"); }); + test("TestSwitchingTabs", async ({ page }) => { + await page.goto("/"); + await page.getByRole("button", { name: "Satellites" }).click(); + await page.getByTestId("satellitesTableRow").first().click(); + await expect(page).toHaveURL(/\/satellites\/.+$/); + const tabs = page.getByTestId("satellite-tab-bar"); + await expect(tabs).toBeVisible(); + // Check if the first tab is active + await expect(tabs.getByRole("tab").first()).toHaveAttribute( + "aria-selected", + "true", + ); + // Click on the second tab + await tabs.getByRole("tab").nth(1).click(); + // Check if the second tab is now active + await expect(tabs.getByRole("tab").nth(1)).toHaveAttribute( + "aria-selected", + "true", + ); + }); }); diff --git a/frontend/tests/e2e/satellite.mobile.spec.ts b/frontend/tests/e2e/satellite.mobile.spec.ts index 18a290a..ac885a0 100644 --- a/frontend/tests/e2e/satellite.mobile.spec.ts +++ b/frontend/tests/e2e/satellite.mobile.spec.ts @@ -53,4 +53,24 @@ test.describe("Individual Satellite Page Test", () => { await slider.fill("180"); //Prediction of position in 180 minutes await expect(slider).toHaveValue("180"); }); + test("TestSwitchingTabs", async ({ page }) => { + await page.goto("/"); + await page.getByRole("button", { name: "Satellites" }).click(); + await page.getByTestId("satellitesTableRow").first().click(); + await expect(page).toHaveURL(/\/satellites\/.+$/); + const tabs = page.getByTestId("satellite-tab-bar"); + await expect(tabs).toBeVisible(); + // Check if the first tab is active + await expect(tabs.getByRole("tab").first()).toHaveAttribute( + "aria-selected", + "true", + ); + // Click on the second tab + await tabs.getByRole("tab").nth(1).click(); + // Check if the second tab is now active + await expect(tabs.getByRole("tab").nth(1)).toHaveAttribute( + "aria-selected", + "true", + ); + }); });