mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 03:29:02 +08:00
Merge pull request #844 from pranshuchittora/pranshuchittora/feat/x-axis-adaptive-lables
feat(FE): adaptive x axis time labels
This commit is contained in:
commit
2ca67f1017
75
frontend/src/components/Graph/__tests__/xAxisConfig.test.ts
Normal file
75
frontend/src/components/Graph/__tests__/xAxisConfig.test.ts
Normal file
@ -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,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@ -27,6 +27,7 @@ import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { useXAxisTimeUnit } from './xAxisConfig';
|
||||
Chart.register(
|
||||
LineElement,
|
||||
PointElement,
|
||||
@ -59,7 +60,8 @@ const Graph = ({
|
||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||
const currentTheme = isDarkMode ? 'dark' : 'light';
|
||||
|
||||
// const [tooltipVisible, setTooltipVisible] = useState<boolean>(false);
|
||||
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
|
||||
|
||||
const lineChartRef = useRef<Chart>();
|
||||
|
||||
const getGridColor = useCallback(() => {
|
||||
@ -109,7 +111,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',
|
||||
},
|
||||
|
147
frontend/src/components/Graph/xAxisConfig.ts
Normal file
147
frontend/src/components/Graph/xAxisConfig.ts
Normal file
@ -0,0 +1,147 @@
|
||||
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 ITimeUnit {
|
||||
[key: string]: TimeUnit;
|
||||
}
|
||||
interface IAxisTimeUintConfig {
|
||||
unitName: TimeUnit;
|
||||
multiplier: number;
|
||||
}
|
||||
|
||||
interface IAxisTimeConfig {
|
||||
unitName: TimeUnit;
|
||||
stepSize: number;
|
||||
}
|
||||
|
||||
export interface ITimeRange {
|
||||
minTime: number | null;
|
||||
maxTime: number | null;
|
||||
}
|
||||
|
||||
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: TIME_UNITS.millisecond,
|
||||
multiplier: 1,
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.second,
|
||||
multiplier: 1 / 1e3,
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.minute,
|
||||
multiplier: 1 / (1e3 * 60),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.hour,
|
||||
multiplier: 1 / (1e3 * 60 * 60),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.day,
|
||||
multiplier: 1 / (1e3 * 60 * 60 * 24),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.week,
|
||||
multiplier: 1 / (1e3 * 60 * 60 * 24 * 7),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.month,
|
||||
multiplier: 1 / (1e3 * 60 * 60 * 24 * 30),
|
||||
},
|
||||
{
|
||||
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 => {
|
||||
// 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: string | number): void => {
|
||||
if (typeof timeStamp === 'string') 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,
|
||||
};
|
||||
} catch (error) {
|
||||
localTime = null;
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// Global time is the time selected from the global time selector menu.
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(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 {
|
||||
minTime: localTime.minTime,
|
||||
maxTime: localTime.maxTime,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
minTime: globalTime.minTime / 1e6,
|
||||
maxTime: globalTime.maxTime / 1e6,
|
||||
};
|
||||
}
|
||||
}, [globalTime, localTime]);
|
||||
|
||||
return convertTimeRange(minTime, maxTime);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_CONFIG[1];
|
||||
let stepSize = 1;
|
||||
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,
|
||||
stepSize: Math.floor(stepSize) || 1,
|
||||
};
|
||||
};
|
@ -82,7 +82,6 @@ const FullView = ({
|
||||
};
|
||||
|
||||
const queryMinMax = getMinMax(selectedTime.enum);
|
||||
|
||||
const response = await Promise.all(
|
||||
widget.query
|
||||
.filter((e) => e.query.length !== 0)
|
||||
|
@ -27,7 +27,6 @@ export const getChartData = (
|
||||
data: [],
|
||||
type: 'line',
|
||||
};
|
||||
|
||||
const chartLabels: ChartData<'line'>['labels'] = [];
|
||||
|
||||
Object.keys(allDataPoints).forEach((timestamp) => {
|
||||
|
@ -9,7 +9,7 @@ import { TraceReducer } from 'types/reducer/trace';
|
||||
import { getChartData, getChartDataforGroupBy } from './config';
|
||||
import { Container } from './styles';
|
||||
|
||||
const TraceGraph = () => {
|
||||
const TraceGraph = (): JSX.Element => {
|
||||
const { spansGraph, selectedGroupBy } = useSelector<AppState, TraceReducer>(
|
||||
(state) => state.traces,
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user