From 361b7013b7ffaef76ed60cb8eb9b38b8f6b76fb3 Mon Sep 17 00:00:00 2001 From: braddf Date: Thu, 28 Nov 2024 17:10:35 +0000 Subject: [PATCH] fix zone y axis max + breakpoints --- .../charts/gsp-pv-remix-chart/index.tsx | 20 +++-- .../gsp-pv-remix-chart/use-get-gsp-data.ts | 24 ++++- .../components/charts/remix-line.tsx | 5 +- .../components/helpers/chartUtils.ts | 89 +++++++++++++++++++ 4 files changed, 129 insertions(+), 9 deletions(-) diff --git a/apps/nowcasting-app/components/charts/gsp-pv-remix-chart/index.tsx b/apps/nowcasting-app/components/charts/gsp-pv-remix-chart/index.tsx index a1bac20f..3450d3ad 100644 --- a/apps/nowcasting-app/components/charts/gsp-pv-remix-chart/index.tsx +++ b/apps/nowcasting-app/components/charts/gsp-pv-remix-chart/index.tsx @@ -13,12 +13,20 @@ import Spinner from "../../icons/spinner"; import { ForecastValue } from "../../types"; import React, { FC } from "react"; import { NationalAggregation } from "../../map/types"; +import { getTicks } from "../../helpers/chartUtils"; -// We want to have the ymax of the graph to be related to the capacity of the GspPvRemixChart +// Static constant below of this function so we don't call dynamically unnecessarily. +// import { generateYMaxTickArray } from "../../helpers/chartUtils"; +// console.log("Y_MAX_TICKS", generateYMaxTickArray()); +// +// We want to have the yMax of the graph to be related to the capacity of the GspPvRemixChart. // If we use the raw values, the graph looks funny, i.e y major ticks are 0 100 232 -// So, we round these up to the following numbers -const yMax_levels = [ - 3, 9, 20, 28, 36, 45, 60, 80, 100, 120, 160, 200, 240, 300, 320, 360, 400, 450, 600 +// So, we round these up to the following numbers, which hopefully split nicely into the y-axis. +// Uncomment the above function to get updated values should we need to change these +const Y_MAX_TICKS = [ + 1, 2, 3, 4, 5, 6, 9, 10, 12, 15, 18, 20, 25, 30, 40, 45, 50, 60, 75, 80, 90, 100, 150, 200, 250, + 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, + 6000, 7000, 8000, 9000, 10000 ]; const GspPvRemixChart: FC<{ @@ -51,7 +59,6 @@ const GspPvRemixChart: FC<{ } = useGetGspData(gspId); // TODO – temp reset; if aggregation is zones, make sure data is all set if ([NationalAggregation.DNO, NationalAggregation.zone].includes(nationalAggregationLevel)) { - gspLocationInfo = []; gspNHourData = []; } // const gspData = fcAll?.forecasts.find((fc) => fc.location.gspId === gspId); @@ -117,7 +124,7 @@ const GspPvRemixChart: FC<{ // set ymax to the installed capacity of the graph let yMax = gspInstalledCapacity || 100; - yMax = getRoundedTickBoundary(yMax, yMax_levels); + yMax = getRoundedTickBoundary(yMax, Y_MAX_TICKS); const title = nationalAggregationLevel === NationalAggregation.GSP ? gspName || "" : String(gspId); @@ -166,6 +173,7 @@ const GspPvRemixChart: FC<{ visibleLines={visibleLines} deltaView={deltaView} deltaYMaxOverride={Math.ceil(Number(gspInstalledCapacity) / 200) * 100 || 500} + yTicks={getTicks(yMax, Y_MAX_TICKS)} /> diff --git a/apps/nowcasting-app/components/charts/gsp-pv-remix-chart/use-get-gsp-data.ts b/apps/nowcasting-app/components/charts/gsp-pv-remix-chart/use-get-gsp-data.ts index 15cb2500..1a1db0f6 100644 --- a/apps/nowcasting-app/components/charts/gsp-pv-remix-chart/use-get-gsp-data.ts +++ b/apps/nowcasting-app/components/charts/gsp-pv-remix-chart/use-get-gsp-data.ts @@ -52,6 +52,9 @@ const useGetGspData = (gspId: number | string) => { const [nHourForecast] = useGlobalState("nHourForecast"); const [nationalAggregationLevel] = useGlobalState("nationalAggregationLevel"); let errors: Error[] = []; + let isZoneAggregation = [NationalAggregation.DNO, NationalAggregation.zone].includes( + nationalAggregationLevel + ); let gspIds: number[] = typeof gspId === "number" ? [gspId] : []; if (nationalAggregationLevel === NationalAggregation.DNO) { @@ -98,9 +101,26 @@ const useGetGspData = (gspId: number | string) => { const gspForecastDataOneGSP = aggregateForecastData(gspForecastDataOneGSPRaw, gspIds); //add new useSWR for gspLocationInfo since this is not - const { data: gspLocationInfo, error: gspLocationError } = useLoadDataFromApi( - `${API_PREFIX}/system/GB/gsp/?gsp_id=${gspId}` + const { data: gspLocationInfoRaw, error: gspLocationError } = useLoadDataFromApi( + isZoneAggregation + ? `${API_PREFIX}/system/GB/gsp/?zones=true` // TODO: API seems to struggle with UI flag if no other query params + : `${API_PREFIX}/system/GB/gsp/?gsp_id=${gspId}` ); + let gspLocationInfo = gspLocationInfoRaw?.filter((gsp) => gspIds.includes(gsp.gspId)); + if (isZoneAggregation && gspLocationInfo) { + const zoneCapacity = gspLocationInfo.reduce((acc, gsp) => acc + gsp.installedCapacityMw, 0); + gspLocationInfo = [ + { + gspId: gspId as number, + gspName: gspId as string, + regionName: gspId as string, + installedCapacityMw: zoneCapacity, + rmMode: true, + label: "Zone", + gspGroup: "Zone" + } + ]; + } // TODO: nHour with aggregation const nMinuteForecast = nHourForecast * 60; diff --git a/apps/nowcasting-app/components/charts/remix-line.tsx b/apps/nowcasting-app/components/charts/remix-line.tsx index 3544436e..218430ef 100644 --- a/apps/nowcasting-app/components/charts/remix-line.tsx +++ b/apps/nowcasting-app/components/charts/remix-line.tsx @@ -85,6 +85,7 @@ type RemixLineProps = { zoomEnabled?: boolean; deltaView?: boolean; deltaYMaxOverride?: number; + yTicks?: number[]; }; const CustomizedLabel: FC = ({ value, @@ -142,7 +143,8 @@ const RemixLine: React.FC = ({ visibleLines, zoomEnabled = true, deltaView = false, - deltaYMaxOverride + deltaYMaxOverride, + yTicks }) => { // Set the y max. If national then set to 12000, for gsp plot use 'auto' const preppedData = data.sort((a, b) => a.formattedDate.localeCompare(b.formattedDate)); @@ -412,6 +414,7 @@ const RemixLine: React.FC = ({ yAxisId={"y-axis"} tick={{ fill: "white", style: { fontSize: "12px" } }} tickLine={false} + ticks={yTicks} domain={ globalIsZoomed && view !== VIEWS.SOLAR_SITES ? [0, Number(zoomYMax * 1.1)] diff --git a/apps/nowcasting-app/components/helpers/chartUtils.ts b/apps/nowcasting-app/components/helpers/chartUtils.ts index 7d15644c..4be91085 100644 --- a/apps/nowcasting-app/components/helpers/chartUtils.ts +++ b/apps/nowcasting-app/components/helpers/chartUtils.ts @@ -22,3 +22,92 @@ export const getZoomYMax = (filteredPreppedData: ChartData[]) => { .sort((a, b) => Number(b) - Number(a))[0]; } }; + +// Function not "in use" but useful for regenerating yMax levels as a constant array for the chart +export const generateYMaxTickArray = () => { + // Generate yMax levels + // Small values + let yMax_levels = Array.from({ length: 4 }, (_, i) => i + 1); + // Multiples of 3 + yMax_levels = [...yMax_levels, ...Array.from({ length: 6 }, (_, i) => (i + 1) * 3)]; + // Multiples of 5 + yMax_levels = [...yMax_levels, ...Array.from({ length: 6 }, (_, i) => (i + 1) * 5)]; + // Multiples of 10 + yMax_levels = [...yMax_levels, ...Array.from({ length: 5 }, (_, i) => (i + 1) * 10)]; + // Multiples of 15 + yMax_levels = [...yMax_levels, ...Array.from({ length: 6 }, (_, i) => (i + 1) * 15)]; + // Multiples of 20 + yMax_levels = [...yMax_levels, ...Array.from({ length: 5 }, (_, i) => (i + 1) * 20)]; + // Multiples of 25 + yMax_levels = [...yMax_levels, ...Array.from({ length: 3 }, (_, i) => (i + 1) * 25)]; + // Multiples of 50 + yMax_levels = [...yMax_levels, ...Array.from({ length: 10 }, (_, i) => (i + 1) * 50)]; + // Multiples of 100 + yMax_levels = [...yMax_levels, ...Array.from({ length: 10 }, (_, i) => (i + 1) * 100)]; + // Multiples of 500 + yMax_levels = [...yMax_levels, ...Array.from({ length: 10 }, (_, i) => (i + 1) * 500)]; + // Multiples of 1000 + yMax_levels = [...yMax_levels, ...Array.from({ length: 10 }, (_, i) => (i + 1) * 1000)]; + // Remove duplicates + yMax_levels = [...new Set(yMax_levels)]; + // Sort + yMax_levels.sort((a, b) => a - b); + return yMax_levels; +}; + +export const getTicks = (yMax: number, yMax_levels: number[]) => { + const ticks: number[] = []; + const third = yMax / 3; + const quarter = yMax / 4; + const fifth = yMax / 5; + const seventh = yMax / 7; + const testTicksToAdd = (fractionN: number) => { + let canSplit = true; + let tempTicks = []; + for (let i = fractionN; i <= yMax; i += fractionN) { + if (isRoundNumber(i) || i === yMax) { + tempTicks.push(i); + } else { + canSplit = false; + break; + } + } + if (canSplit) { + ticks.push(...tempTicks); + } + }; + const isRoundNumber = (n: number) => { + if (n > 2000) { + return n % 500 === 0; + } + if (n > 1000) { + return n % 250 === 0 || n % 100 === 0; + } + if (n > 200) { + return n % 50 === 0; + } + if (n > 20) { + return n % 5 === 0; + } + if (n > 3) { + return n % 1 === 0; + } + return n % 0.5 === 0; + }; + if (isRoundNumber(third)) { + testTicksToAdd(third); + } + if (ticks.length === 0 && isRoundNumber(quarter)) { + testTicksToAdd(quarter); + } + if (ticks.length === 0 && isRoundNumber(fifth)) { + testTicksToAdd(fifth); + } + if (ticks.length === 0 && isRoundNumber(seventh)) { + testTicksToAdd(seventh); + } + if (ticks.length === 0) { + testTicksToAdd(yMax > 500 ? 100 : 50); + } + return ticks; +};