From 1bf8e6bef6645a2460844cb7a1dddfd88d42b15c Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Fri, 11 Mar 2022 11:20:52 +0530 Subject: [PATCH] feat: better x-axis labels --- frontend/src/components/Graph/index.tsx | 20 +++- frontend/src/components/Graph/xAxisConfig.ts | 113 ++++++++++++++++++ .../GridGraphLayout/Graph/FullView/index.tsx | 1 - .../MetricsApplication/Tabs/Application.tsx | 7 +- frontend/src/container/Trace/Graph/config.ts | 1 - frontend/src/container/Trace/Graph/index.tsx | 10 +- 6 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/Graph/xAxisConfig.ts diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index b6905495d4..a7647dbee7 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -27,6 +27,7 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; +import { ITimeRange, useXAxisTimeUnit } from './xAxisConfig'; Chart.register( LineElement, PointElement, @@ -59,7 +60,11 @@ const Graph = ({ const chartRef = useRef(null); const currentTheme = isDarkMode ? 'dark' : 'light'; - // const [tooltipVisible, setTooltipVisible] = useState(false); + /** + * Computes the relevant time unit for x axis by analyzing the time stamp data + */ + const xAxisTimeUnit = useXAxisTimeUnit(data); + const lineChartRef = useRef(); const getGridColor = useCallback(() => { @@ -109,7 +114,18 @@ const Graph = ({ date: chartjsAdapter, }, time: { - unit: 'minute', + unit: xAxisTimeUnit?.unitName || 'minute', + stepSize: xAxisTimeUnit?.stepSize || 1, + displayFormats: { + millisecond: 'hh:mm:ss', + second: 'hh:mm:ss', + minute: 'HH:mm', + hour: 'MM/dd HH:mm', + day: 'MM/dd', + week: 'MM/dd', + month: 'yy-MM', + year: 'yy', + }, }, type: 'time', }, diff --git a/frontend/src/components/Graph/xAxisConfig.ts b/frontend/src/components/Graph/xAxisConfig.ts new file mode 100644 index 0000000000..a8bc01d05e --- /dev/null +++ b/frontend/src/components/Graph/xAxisConfig.ts @@ -0,0 +1,113 @@ +import { Chart, TimeUnit } from 'chart.js'; +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +interface IAxisTimeUint { + unitName: TimeUnit; + multiplier: number; +} + +interface IAxisTimeConfig { + unitName: TimeUnit; + stepSize: number; +} + +export interface ITimeRange { + minTime: number | null; + maxTime: number | null; +} + +const TIME_UNITS: IAxisTimeUint[] = [ + { + unitName: 'millisecond', + multiplier: 1, + }, + { + unitName: 'second', + multiplier: 1 / 1e3, + }, + { + unitName: 'minute', + multiplier: 1 / (1e3 * 60), + }, + { + unitName: 'hour', + multiplier: 1 / (1e3 * 60 * 60), + }, + { + unitName: 'day', + multiplier: 1 / (1e3 * 60 * 60 * 24), + }, + { + unitName: 'week', + multiplier: 1 / (1e3 * 60 * 60 * 24 * 7), + }, + { + unitName: 'month', + multiplier: 1 / (1e3 * 60 * 60 * 24 * 30), + }, + { + unitName: 'year', + multiplier: 1 / (1e3 * 60 * 60 * 24 * 365), + }, +]; + +export const useXAxisTimeUnit = (data: Chart['data']): IAxisTimeConfig => { + let localTime: ITimeRange; + { + let minTime = Number.POSITIVE_INFINITY; + let maxTime = Number.NEGATIVE_INFINITY; + data?.labels?.forEach((timeStamp: any) => { + timeStamp = Date.parse(timeStamp); + minTime = Math.min(timeStamp, minTime); + maxTime = Math.max(timeStamp, maxTime); + }); + + localTime = { + minTime: minTime === Number.POSITIVE_INFINITY ? null : minTime, + maxTime: maxTime === Number.NEGATIVE_INFINITY ? null : maxTime, + }; + } + const globalTime = useSelector( + (state) => state.globalTime, + ); + + const { maxTime, minTime } = useMemo(() => { + if (localTime && localTime.maxTime && localTime.minTime) { + return { + minTime: localTime.minTime, + maxTime: localTime.maxTime, + }; + } else { + return { + minTime: globalTime.minTime / 1e6, + maxTime: globalTime.maxTime / 1e6, + }; + } + }, [globalTime, localTime]); + // debugger; + return convertTimeRange(minTime, maxTime); +}; + +const convertTimeRange = (start: number, end: number): IAxisTimeConfig => { + const MIN_INTERVALS = 6; + const range = end - start; + let relevantTimeUnit = TIME_UNITS[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; + } + } + return { + unitName: relevantTimeUnit.unitName, + stepSize: Math.floor(stepSize) || 1, + }; +}; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index 5da66f3394..06665218fc 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -82,7 +82,6 @@ const FullView = ({ }; const queryMinMax = getMinMax(selectedTime.enum); - const response = await Promise.all( widget.query .filter((e) => e.query.length !== 0) diff --git a/frontend/src/container/MetricsApplication/Tabs/Application.tsx b/frontend/src/container/MetricsApplication/Tabs/Application.tsx index 005033f999..b96344d263 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Application.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Application.tsx @@ -1,5 +1,6 @@ import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js'; import Graph from 'components/Graph'; +import { getTimeRange, ITimeRange } from 'components/Graph/xAxisConfig'; import { METRICS_PAGE_QUERY_PARAM } from 'constants/query'; import ROUTES from 'constants/routes'; import FullView from 'container/GridGraphLayout/Graph/FullView'; @@ -34,8 +35,7 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => { urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString()); history.replace( - `${ - ROUTES.TRACE + `${ROUTES.TRACE }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["ok","error"]}&filterToFetchData=["duration","status","serviceName"]&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&isSelectedFilterSkipped=true`, ); }; @@ -88,8 +88,7 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => { urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString()); history.replace( - `${ - ROUTES.TRACE + `${ROUTES.TRACE }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&userSelectedFilter={"status":["error"],"serviceName":["${servicename}"]}&isSelectedFilterSkipped=true`, ); }; diff --git a/frontend/src/container/Trace/Graph/config.ts b/frontend/src/container/Trace/Graph/config.ts index 17344b52ef..f72e23f97a 100644 --- a/frontend/src/container/Trace/Graph/config.ts +++ b/frontend/src/container/Trace/Graph/config.ts @@ -27,7 +27,6 @@ export const getChartData = ( data: [], type: 'line', }; - const chartLabels: ChartData<'line'>['labels'] = []; Object.keys(allDataPoints).forEach((timestamp) => { diff --git a/frontend/src/container/Trace/Graph/index.tsx b/frontend/src/container/Trace/Graph/index.tsx index efc8a9d137..7ee311cc89 100644 --- a/frontend/src/container/Trace/Graph/index.tsx +++ b/frontend/src/container/Trace/Graph/index.tsx @@ -1,13 +1,13 @@ -import React, { useMemo } from 'react'; - +import { Typography } from 'antd'; import Graph from 'components/Graph'; +import Spinner from 'components/Spinner'; +import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { TraceReducer } from 'types/reducer/trace'; -import Spinner from 'components/Spinner'; -import { Container } from './styles'; -import { Typography } from 'antd'; + import { getChartData, getChartDataforGroupBy } from './config'; +import { Container } from './styles'; const TraceGraph = () => { const { spansGraph, selectedGroupBy } = useSelector(