From 5fbe936ee1bbd44d21a8646c68eef48d7e97e16b Mon Sep 17 00:00:00 2001 From: Thibault <54189871+Asaren1070@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:18:31 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=20modifying=20the=20daily=20solar=20da?= =?UTF-8?q?ta=20chart=20and=20adding=20the=20historical=E2=80=A6=20(#459)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: modifying the daily solar data chart and adding the historical solar data diagram * fixing lint * disabling some lint rules * lint rules * lint rules --- backend/dbLocation.sqlite | Bin 2547712 -> 2547712 bytes frontend/package-lock.json | 25 ++ frontend/package.json | 3 + .../_homeComponents/DailySolarActivity.tsx | 215 ++++++++++++ .../HistoricalSolarCycleData.tsx | 308 ++++++++++++++++++ .../app/_homeComponents/HistorySolarData.tsx | 49 ++- frontend/src/app/page.tsx | 7 +- package-lock.json | 25 ++ package.json | 3 + 9 files changed, 609 insertions(+), 26 deletions(-) create mode 100644 frontend/src/app/_homeComponents/DailySolarActivity.tsx create mode 100644 frontend/src/app/_homeComponents/HistoricalSolarCycleData.tsx diff --git a/backend/dbLocation.sqlite b/backend/dbLocation.sqlite index aed30d09d97904872ecc4fbcb8abd44780ac0bdf..6f7a24339dbec02d8bf289d7752798fa6119264b 100644 GIT binary patch delta 177 zcmWN=Hx7aT7>40sN3nOof^EhaTO7TH!*>)M{Xe&IX%ovCR&kHzF1Y0ClZNZ__YXN@KT7}r delta 177 zcmWN=ISRr66h%?u9F6mA;t;3W1VzEpZCJde#NuPCUBGpSo!Dew-UBE3y_|#(-$|dr z#Yt~6Ii{Q`Zz`CIrjn^_s+e3;)l_@SyM}Ibi1*QP7oYA~=&{ZQn{2Vo4!i8J&jE)V Zam)#Q2Ap!nITsAM=6.0.0", + "react": ">=16.8.0" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -14675,6 +14694,12 @@ "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==", "dev": true }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index e868ed9..d71609b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,6 +48,9 @@ "framer-motion": "^11.0.24", "globe.gl": "^2.32.2", "gql.tada": "^1.6.2", + "highcharts": "^12.3.0", + "highcharts-react-official": "^3.2.2", + "lodash.debounce": "^4.0.8", "lucide-react": "^0.314.0", "luxon": "^3.4.4", "next": "14.1.0", diff --git a/frontend/src/app/_homeComponents/DailySolarActivity.tsx b/frontend/src/app/_homeComponents/DailySolarActivity.tsx new file mode 100644 index 0000000..d970c9c --- /dev/null +++ b/frontend/src/app/_homeComponents/DailySolarActivity.tsx @@ -0,0 +1,215 @@ +import React, { useState, useEffect } from "react"; +import HighchartsReact from "highcharts-react-official"; +import Highcharts from "highcharts/highstock"; + +export default function DailySolarActivity() { + const [dailyKpIndex, setDailyKpIndex] = useState(null); + const [dailyTimestamps, setDailyTimestamps] = useState([]); + useEffect(() => { + fetch( + `https://services.swpc.noaa.gov/products/noaa-planetary-k-index.json`, + ) + .then((response) => { + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }) + .then((data) => { + // The first row is header, skip it + const formattedData = data.slice(1).map((entry: any) => { + const dateObj = new Date(entry[0]); + // Format: YYYY-MM-DD HH:MM + const formattedDate = + dateObj.toISOString().split("T")[0] + + " " + + dateObj.toISOString().split("T")[1].slice(0, 5); + return { + date: formattedDate, + kp: Number(entry[1]), + }; + }); + setDailyTimestamps( + formattedData.slice(-20).map((d: any) => d.date), + ); + setDailyKpIndex(formattedData.slice(-20).map((d: any) => d.kp)); + }) + .catch((error) => { + console.error("Error fetching Kp index data:", error); + }); + }, []); + const optionsDailyCharts = { + chart: { + type: "column", + backgroundColor: "transparent", + reflow: true, + }, + title: { + margin: 20, + text: "Geomagnetic Activity Index (Kp) - Daily Data", + style: { + color: "#ffffff", + fontSize: "24px", + }, + }, + xAxis: { + categories: dailyTimestamps, // Example categories, replace with actual dates + title: { + text: "Universal Time (UT)", + style: { + color: "#ffffff", + fontSize: "18px", + }, + margin: 20, + }, + labels: { + style: { + color: "#ffffff", + fontSize: "14px", + }, + }, + }, + yAxis: { + title: { + text: "Kp Index", + style: { + color: "#ffffff", + fontSize: "18px", + }, + }, + labels: { + style: { + color: "#ffffff", + fontSize: "14px", + }, + }, + }, + series: [ + { + type: "column", + data: dailyKpIndex, // Example data, replace with actual Kp index data + showInLegend: false, + zones: [ + { value: 5, color: "green" }, + { value: 6, color: "yellow" }, + { value: 7, color: "orange" }, + { value: 8, color: "darkorange" }, + { value: 9, color: "red" }, + { value: 10, color: "darkred" }, + ], + point: { + events: { + // eslint-disable-next-line no-unused-vars + mouseOver(this: Highcharts.Point) { + let groupName = ""; + if (typeof this.y === "number") { + if (this.y < 5) { + groupName = "Quiet"; + } else if (this.y == 5) { + groupName = "Minor Storm"; + } else if (this.y == 6) { + groupName = "Moderate Storm"; + } else if (this.y == 7) { + groupName = "Strong Storm"; + } else if (this.y == 8) { + groupName = "Severe Storm"; + } else { + groupName = "Extreme Storm"; + } + } + this.series.name = `Geomagnetic Activity Index (Kp) - ${groupName}`; + }, + }, + }, + }, + ], + legend: { + enabled: false, + }, + 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", + href: "https://www.swpc.noaa.gov/products/planetary-k-index", + style: { + color: "#ffffff", // Customize credits text color + fontSize: "12px", // Customize credits text size + textDecoration: "underline", + }, + }, + }; + const customLegend = [ + { name: "Quiet (Kp < 5)", color: "green" }, + { name: "Minor Storm (Kp = 5)", color: "yellow" }, + { name: "Moderate Storm (Kp = 6)", color: "orange" }, + { name: "Strong Storm (Kp = 7)", color: "darkorange" }, + { name: "Severe Storm (Kp = 8)", color: "red" }, + { name: "Extreme Storm (Kp = 9)", color: "darkred" }, + ]; + return ( +
+
+ +
+ {/* Custom Legend */} +
+ {customLegend.map((item, index) => ( +
+ + {item.name} +
+ ))} +
+
+ ); +} diff --git a/frontend/src/app/_homeComponents/HistoricalSolarCycleData.tsx b/frontend/src/app/_homeComponents/HistoricalSolarCycleData.tsx new file mode 100644 index 0000000..b8b22fd --- /dev/null +++ b/frontend/src/app/_homeComponents/HistoricalSolarCycleData.tsx @@ -0,0 +1,308 @@ +"use client"; +import React, { useEffect, useState, useRef } from "react"; +import Highcharts from "highcharts/highstock"; +import HighchartsReact from "highcharts-react-official"; + +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") + .then((response) => { + // Check if the response is ok + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }) + .then((data) => { + const formattedData = data.map((entry: any) => { + return { + date: entry["time-tag"], + ssn: Number(entry.ssn), + }; + }); + setHistoricalTimestamps(formattedData.map((d: any) => d.date)); + setHistoricalSunSpot(formattedData.map((d: any) => d.ssn)); + }) + .catch((error) => { + console.error( + "Error fetching full historical Kp index data:", + error, + ); + }); + }, []); + + const formattedHistoricalData = historicalTimestamps.map( + (timestamp, index) => [ + new Date(timestamp).getTime(), // Convert to Unix timestamp (milliseconds) + historicalSunSpot[index], // Corresponding sunspot value + ], + ); + + const calculateMovingAverage = ( + data: number[][], + windowSize: number, + ): number[][] => { + const smoothedData: number[][] = []; + for (let i = 0; i < data.length; i++) { + if (i < windowSize - 1) { + smoothedData.push([data[i][0], data[i][1]]); // Keep original value for the first points + } else { + const window = data.slice(i - windowSize + 1, i + 1); + const average = + window.reduce((sum, point) => sum + point[1], 0) / + windowSize; + smoothedData.push([data[i][0], average]); + } + } + return smoothedData; + }; + const smoothedHistoricalData = calculateMovingAverage( + 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", + }, + ], + }, + + series: [ + { + name: "Sunspot Number", + data: formattedHistoricalData, // Use timestamped data + type: "line", // Line chart for historical data + color: "#00d3ff", // Customize the line color + }, + { + name: "Smoothed Sunspot Number", + data: smoothedHistoricalData, + type: "line", // Smoothed line chart + color: "#ffabc8", // Customize the smoothed line color + }, + ], + legend: { + enabled: true, // Hide legend for cleaner look + itemStyle: { + color: "#ffffff", // Customize legend text color + fontSize: "14px", // Customize legend text size + }, + itemHoverStyle: { + color: "#00d3ff", // Customize legend hover text color + }, + }, + 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", + href: "https://www.swpc.noaa.gov/products/solar-cycle-progression", + style: { + color: "#ffffff", // Customize credits text color + fontSize: "12px", // Customize credits text size + 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/_homeComponents/HistorySolarData.tsx b/frontend/src/app/_homeComponents/HistorySolarData.tsx index 4d3222f..8ee2db9 100644 --- a/frontend/src/app/_homeComponents/HistorySolarData.tsx +++ b/frontend/src/app/_homeComponents/HistorySolarData.tsx @@ -1,33 +1,32 @@ "use client"; -import React from "react"; -import Image from "next/image"; - -const solarDataUrl = - "https://services.swpc.noaa.gov/images/swx-overview-large.gif"; +import HistoricalSolarCycleData from "./HistoricalSolarCycleData"; +import DailySolarActivity from "./DailySolarActivity"; export default function HistorySolarData() { return ( -
-
-
-

{"Historical Solar Data"}

-

- { - "Here is shown multiple charts informing the current state of the solar weather" - } -

-
-
- {/* Images side by side */} -
- Historical Solar Data +
+
+

{"Historical Solar Data"}

+

+ { + "Here is shown the daily and historical Geomagnetic Activity Index (Kp)" + } +

+

+ {"You can find more details on the website of the"} + + {"Space Weather Prediction Center."} + +

+ {/* Daily Chart container */} + + {/* Historical Chart container */} +
); } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 414a9a5..a6e0fa5 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -4,7 +4,12 @@ import MissionStatement from "./_homeComponents/MissionStatement"; import FeaturedImage from "./_homeComponents/FeaturedImage"; import GlobeWithStats from "./_homeComponents/GlobeWithStats"; import ScrollIndicator from "./_homeComponents/ScrollIndicator"; -import HistorySolarData from "./_homeComponents/HistorySolarData"; +import dynamic from "next/dynamic"; + +const HistorySolarData = dynamic( + () => import("./_homeComponents/HistorySolarData"), + { ssr: false }, +); export default async function Home() { return ( diff --git a/package-lock.json b/package-lock.json index 7cf9a4a..5b252a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,9 @@ "@types/recharts": "^1.8.29", "ci": "^2.3.0", "concurrently": "^8.2.2", + "highcharts": "^12.3.0", + "highcharts-react-official": "^3.2.2", + "lodash.debounce": "^4.0.8", "node-schedule": "^2.1.1", "openapi-typescript": "^6.7.4", "recharts": "^2.12.7", @@ -927,6 +930,22 @@ "node": ">=8" } }, + "node_modules/highcharts": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-12.3.0.tgz", + "integrity": "sha512-QIKmaemgheRa1K2Ia9MLj1KLtBU1Tu/VQ6KAMqtMBMsAC4NzcFq6g96LF03ZO3IFFiSifmZx8ItEyRcz4w75cg==", + "license": "https://www.highcharts.com/license" + }, + "node_modules/highcharts-react-official": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.2.tgz", + "integrity": "sha512-2kkWOB6RpdR26fmAJkrtJFG9xWFUDGKWyat88tW3fa/3l/Jc7D5ZfwTng2MZsdiKIH32AFy0Pr75udUe7uN6LA==", + "license": "MIT", + "peerDependencies": { + "highcharts": ">=6.0.0", + "react": ">=16.8.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1038,6 +1057,12 @@ "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==", "license": "MIT" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.omit": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", diff --git a/package.json b/package.json index e9b349e..f0f68d0 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,9 @@ "@types/recharts": "^1.8.29", "ci": "^2.3.0", "concurrently": "^8.2.2", + "highcharts": "^12.3.0", + "highcharts-react-official": "^3.2.2", + "lodash.debounce": "^4.0.8", "node-schedule": "^2.1.1", "openapi-typescript": "^6.7.4", "recharts": "^2.12.7",