diff --git a/frontend/src/components/Graph/__tests__/xAxisConfig.test.ts b/frontend/src/components/Graph/__tests__/xAxisConfig.test.ts new file mode 100644 index 0000000000..e0320e29b0 --- /dev/null +++ b/frontend/src/components/Graph/__tests__/xAxisConfig.test.ts @@ -0,0 +1,75 @@ +import { expect } from '@jest/globals'; +import dayjs from 'dayjs'; + +import { convertTimeRange, TIME_UNITS } from '../xAxisConfig'; + +describe('xAxisConfig for Chart', () => { + describe('convertTimeRange', () => { + it('should return relevant time units for given range', () => { + { + const start = dayjs(); + const end = start.add(10, 'millisecond'); + + expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual( + TIME_UNITS.millisecond, + ); + } + { + const start = dayjs(); + const end = start.add(10, 'second'); + + expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual( + TIME_UNITS.second, + ); + } + { + const start = dayjs(); + const end = start.add(10, 'minute'); + + expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual( + TIME_UNITS.minute, + ); + } + { + const start = dayjs(); + const end = start.add(10, 'hour'); + + expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual( + TIME_UNITS.hour, + ); + } + { + const start = dayjs(); + const end = start.add(10, 'day'); + + expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual( + TIME_UNITS.day, + ); + } + { + const start = dayjs(); + const end = start.add(10, 'week'); + + expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual( + TIME_UNITS.week, + ); + } + { + const start = dayjs(); + const end = start.add(10, 'month'); + + expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual( + TIME_UNITS.month, + ); + } + { + const start = dayjs(); + const end = start.add(10, 'year'); + + expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual( + TIME_UNITS.year, + ); + } + }); + }); +}); diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index a7647dbee7..4cf6c04a0f 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -27,7 +27,7 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; -import { ITimeRange, useXAxisTimeUnit } from './xAxisConfig'; +import { useXAxisTimeUnit } from './xAxisConfig'; Chart.register( LineElement, PointElement, @@ -60,10 +60,7 @@ const Graph = ({ const chartRef = useRef(null); const currentTheme = isDarkMode ? 'dark' : 'light'; - /** - * Computes the relevant time unit for x axis by analyzing the time stamp data - */ - const xAxisTimeUnit = useXAxisTimeUnit(data); + const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data const lineChartRef = useRef(); diff --git a/frontend/src/components/Graph/xAxisConfig.ts b/frontend/src/components/Graph/xAxisConfig.ts index a8bc01d05e..493c36e5b7 100644 --- a/frontend/src/components/Graph/xAxisConfig.ts +++ b/frontend/src/components/Graph/xAxisConfig.ts @@ -4,7 +4,10 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; -interface IAxisTimeUint { +interface ITimeUnit { + [key: string]: TimeUnit; +} +interface IAxisTimeUintConfig { unitName: TimeUnit; multiplier: number; } @@ -19,44 +22,59 @@ export interface ITimeRange { maxTime: number | null; } -const TIME_UNITS: IAxisTimeUint[] = [ +export const TIME_UNITS: ITimeUnit = { + millisecond: 'millisecond', + second: 'second', + minute: 'minute', + hour: 'hour', + day: 'day', + week: 'week', + month: 'month', + year: 'year', +}; + +const TIME_UNITS_CONFIG: IAxisTimeUintConfig[] = [ { - unitName: 'millisecond', + unitName: TIME_UNITS.millisecond, multiplier: 1, }, { - unitName: 'second', + unitName: TIME_UNITS.second, multiplier: 1 / 1e3, }, { - unitName: 'minute', + unitName: TIME_UNITS.minute, multiplier: 1 / (1e3 * 60), }, { - unitName: 'hour', + unitName: TIME_UNITS.hour, multiplier: 1 / (1e3 * 60 * 60), }, { - unitName: 'day', + unitName: TIME_UNITS.day, multiplier: 1 / (1e3 * 60 * 60 * 24), }, { - unitName: 'week', + unitName: TIME_UNITS.week, multiplier: 1 / (1e3 * 60 * 60 * 24 * 7), }, { - unitName: 'month', + unitName: TIME_UNITS.month, multiplier: 1 / (1e3 * 60 * 60 * 24 * 30), }, { - unitName: 'year', + unitName: TIME_UNITS.year, multiplier: 1 / (1e3 * 60 * 60 * 24 * 365), }, ]; +/** + * Accepts Chart.js data's data-structure and returns the relevant time unit for the axis based on the range of the data. + */ export const useXAxisTimeUnit = (data: Chart['data']): IAxisTimeConfig => { - let localTime: ITimeRange; - { + // Local time is the time range inferred from the input chart data. + let localTime: ITimeRange | null; + try { let minTime = Number.POSITIVE_INFINITY; let maxTime = Number.NEGATIVE_INFINITY; data?.labels?.forEach((timeStamp: any) => { @@ -69,11 +87,17 @@ export const useXAxisTimeUnit = (data: Chart['data']): IAxisTimeConfig => { minTime: minTime === Number.POSITIVE_INFINITY ? null : minTime, maxTime: maxTime === Number.NEGATIVE_INFINITY ? null : maxTime, }; + } catch (error) { + localTime = null; + console.error(error); } + + // Global time is the time selected from the global time selector menu. const globalTime = useSelector( (state) => state.globalTime, ); + // Use local time if valid else use the global time range const { maxTime, minTime } = useMemo(() => { if (localTime && localTime.maxTime && localTime.minTime) { return { @@ -87,24 +111,34 @@ export const useXAxisTimeUnit = (data: Chart['data']): IAxisTimeConfig => { }; } }, [globalTime, localTime]); - // debugger; + return convertTimeRange(minTime, maxTime); }; -const convertTimeRange = (start: number, end: number): IAxisTimeConfig => { +/** + * Finds the relevant time unit based on the input time stamps (in ms) + */ +export const convertTimeRange = ( + start: number, + end: number, +): IAxisTimeConfig => { const MIN_INTERVALS = 6; const range = end - start; - let relevantTimeUnit = TIME_UNITS[1]; + let relevantTimeUnit = TIME_UNITS_CONFIG[1]; let stepSize = 1; - for (let idx = TIME_UNITS.length - 1; idx >= 0; idx--) { - const timeUnit = TIME_UNITS[idx]; - const units = range * timeUnit.multiplier; - const steps = units / MIN_INTERVALS; - if (steps >= 1) { - relevantTimeUnit = timeUnit; - stepSize = steps; - break; + try { + for (let idx = TIME_UNITS_CONFIG.length - 1; idx >= 0; idx--) { + const timeUnit = TIME_UNITS_CONFIG[idx]; + const units = range * timeUnit.multiplier; + const steps = units / MIN_INTERVALS; + if (steps >= 1) { + relevantTimeUnit = timeUnit; + stepSize = steps; + break; + } } + } catch (error) { + console.error(error); } return { unitName: relevantTimeUnit.unitName,