mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 10:39:07 +08:00
feat(timeline): add new timeline v2 component (#6760)
* feat(timeline): base commit for timeline v2 * feat(timeline): svg rendering for timeline v2 * feat(timeline): dynamic scale based on screen size * feat(timeline): cleanup code * feat(timeline): make position functioning of timeline height
This commit is contained in:
parent
15f85a645f
commit
ecd50f7232
@ -0,0 +1,4 @@
|
|||||||
|
.timeline-v2-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
90
frontend/src/components/TimelineV2/TimelineV2.tsx
Normal file
90
frontend/src/components/TimelineV2/TimelineV2.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import './TimelineV2.styles.scss';
|
||||||
|
|
||||||
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useMeasure } from 'react-use';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getIntervals,
|
||||||
|
getMinimumIntervalsBasedOnWidth,
|
||||||
|
Interval,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
interface ITimelineV2Props {
|
||||||
|
startTimestamp: number;
|
||||||
|
endTimestamp: number;
|
||||||
|
timelineHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TimelineV2(props: ITimelineV2Props): JSX.Element {
|
||||||
|
const { startTimestamp, endTimestamp, timelineHeight } = props;
|
||||||
|
const [intervals, setIntervals] = useState<Interval[]>([]);
|
||||||
|
const [ref, { width }] = useMeasure<HTMLDivElement>();
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const spread = endTimestamp - startTimestamp;
|
||||||
|
if (spread < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minIntervals = getMinimumIntervalsBasedOnWidth(width);
|
||||||
|
const intervalisedSpread = (spread / minIntervals) * 1.0;
|
||||||
|
setIntervals(getIntervals(intervalisedSpread, spread));
|
||||||
|
}, [startTimestamp, endTimestamp, width]);
|
||||||
|
|
||||||
|
if (endTimestamp < startTimestamp) {
|
||||||
|
console.error(
|
||||||
|
'endTimestamp cannot be less than startTimestamp',
|
||||||
|
startTimestamp,
|
||||||
|
endTimestamp,
|
||||||
|
);
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref as never} className="timeline-v2-container">
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
height={timelineHeight}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
overflow="visible"
|
||||||
|
>
|
||||||
|
<line
|
||||||
|
x1="0"
|
||||||
|
y1={timelineHeight}
|
||||||
|
x2={width}
|
||||||
|
y2={timelineHeight}
|
||||||
|
stroke={isDarkMode ? 'white' : 'black'}
|
||||||
|
strokeWidth="1"
|
||||||
|
/>
|
||||||
|
{intervals &&
|
||||||
|
intervals.length > 0 &&
|
||||||
|
intervals.map((interval, index) => (
|
||||||
|
<g
|
||||||
|
transform={`translate(${(interval.percentage * width) / 100},0)`}
|
||||||
|
key={`${interval.percentage + interval.label + index}`}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="0.6rem"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
x={index === intervals.length - 1 ? -10 : 0}
|
||||||
|
y={2 * Math.floor(timelineHeight / 4)}
|
||||||
|
fill={isDarkMode ? 'white' : 'black'}
|
||||||
|
>
|
||||||
|
{interval.label}
|
||||||
|
</text>
|
||||||
|
<line
|
||||||
|
y1={3 * Math.floor(timelineHeight / 4)}
|
||||||
|
y2={timelineHeight + 0.5}
|
||||||
|
stroke={isDarkMode ? 'white' : 'black'}
|
||||||
|
strokeWidth="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimelineV2;
|
118
frontend/src/components/TimelineV2/utils.ts
Normal file
118
frontend/src/components/TimelineV2/utils.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { toFixed } from 'utils/toFixed';
|
||||||
|
|
||||||
|
type TTimeUnitName = 'ms' | 's' | 'm' | 'hr' | 'day' | 'week';
|
||||||
|
|
||||||
|
export interface IIntervalUnit {
|
||||||
|
name: TTimeUnitName;
|
||||||
|
multiplier: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Interval {
|
||||||
|
label: string;
|
||||||
|
percentage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INTERVAL_UNITS: IIntervalUnit[] = [
|
||||||
|
{
|
||||||
|
name: 'ms',
|
||||||
|
multiplier: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 's',
|
||||||
|
multiplier: 1 / 1e3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'm',
|
||||||
|
multiplier: 1 / (1e3 * 60),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hr',
|
||||||
|
multiplier: 1 / (1e3 * 60 * 60),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'day',
|
||||||
|
multiplier: 1 / (1e3 * 60 * 60 * 24),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'week',
|
||||||
|
multiplier: 1 / (1e3 * 60 * 60 * 24 * 7),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getMinimumIntervalsBasedOnWidth = (width: number): number => {
|
||||||
|
// S
|
||||||
|
if (width < 640) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
// M
|
||||||
|
if (width < 768) {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
// L
|
||||||
|
if (width < 1024) {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resolveTimeFromInterval = (
|
||||||
|
intervalTime: number,
|
||||||
|
intervalUnit: IIntervalUnit,
|
||||||
|
): number => intervalTime * intervalUnit.multiplier;
|
||||||
|
|
||||||
|
export function getIntervals(
|
||||||
|
intervalSpread: number,
|
||||||
|
baseSpread: number,
|
||||||
|
): Interval[] {
|
||||||
|
const integerPartString = intervalSpread.toString().split('.')[0];
|
||||||
|
const integerPartLength = integerPartString.length;
|
||||||
|
const intervalSpreadNormalized =
|
||||||
|
intervalSpread < 1.0
|
||||||
|
? intervalSpread
|
||||||
|
: Math.floor(Number(integerPartString) / 10 ** (integerPartLength - 1)) *
|
||||||
|
10 ** (integerPartLength - 1);
|
||||||
|
|
||||||
|
let intervalUnit = INTERVAL_UNITS[0];
|
||||||
|
for (let idx = INTERVAL_UNITS.length - 1; idx >= 0; idx -= 1) {
|
||||||
|
const standardInterval = INTERVAL_UNITS[idx];
|
||||||
|
if (intervalSpread * standardInterval.multiplier >= 1) {
|
||||||
|
intervalUnit = INTERVAL_UNITS[idx];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intervalUnit = intervalUnit || INTERVAL_UNITS[0];
|
||||||
|
|
||||||
|
const intervals: Interval[] = [
|
||||||
|
{
|
||||||
|
label: `${toFixed(resolveTimeFromInterval(0, intervalUnit), 2)}${
|
||||||
|
intervalUnit.name
|
||||||
|
}`,
|
||||||
|
percentage: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let tempBaseSpread = baseSpread;
|
||||||
|
let elapsedIntervals = 0;
|
||||||
|
|
||||||
|
while (tempBaseSpread && intervals.length < 20) {
|
||||||
|
let intervalTime;
|
||||||
|
if (tempBaseSpread <= 1.5 * intervalSpreadNormalized) {
|
||||||
|
intervalTime = elapsedIntervals + tempBaseSpread;
|
||||||
|
tempBaseSpread = 0;
|
||||||
|
} else {
|
||||||
|
intervalTime = elapsedIntervals + intervalSpreadNormalized;
|
||||||
|
tempBaseSpread -= intervalSpreadNormalized;
|
||||||
|
}
|
||||||
|
elapsedIntervals = intervalTime;
|
||||||
|
const interval: Interval = {
|
||||||
|
label: `${toFixed(resolveTimeFromInterval(intervalTime, intervalUnit), 2)}${
|
||||||
|
intervalUnit.name
|
||||||
|
}`,
|
||||||
|
percentage: (intervalTime / baseSpread) * 100,
|
||||||
|
};
|
||||||
|
intervals.push(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return intervals;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user