gridLines.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import {useCallback} from 'react';
  2. import styled from '@emotion/styled';
  3. import moment from 'moment';
  4. import DateTime from 'sentry/components/dateTime';
  5. import {space} from 'sentry/styles/space';
  6. import {TimeWindow} from 'sentry/views/monitors/components/overviewTimeline/types';
  7. import {
  8. getStartFromTimeWindow,
  9. timeWindowConfig,
  10. } from 'sentry/views/monitors/components/overviewTimeline/utils';
  11. import {useTimelineCursor} from './timelineCursor';
  12. interface Props {
  13. end: Date;
  14. timeWindow: TimeWindow;
  15. width: number;
  16. className?: string;
  17. showCursor?: boolean;
  18. stickyCursor?: boolean;
  19. }
  20. function clampTimeBasedOnResolution(date: moment.Moment, resolution: string) {
  21. date.startOf('minute');
  22. if (resolution === '1h') {
  23. date.minute(date.minutes() - (date.minutes() % 10));
  24. } else if (resolution === '30d') {
  25. date.startOf('day');
  26. } else {
  27. date.startOf('hour');
  28. }
  29. }
  30. interface TimeMarker {
  31. date: Date;
  32. position: number;
  33. }
  34. function getTimeMarkers(end: Date, timeWindow: TimeWindow, width: number): TimeMarker[] {
  35. const {elapsedMinutes, timeMarkerInterval} = timeWindowConfig[timeWindow];
  36. const msPerPixel = (elapsedMinutes * 60 * 1000) / width;
  37. const times: TimeMarker[] = [];
  38. const start = getStartFromTimeWindow(end, timeWindow);
  39. const firstTimeMark = moment(start);
  40. clampTimeBasedOnResolution(firstTimeMark, timeWindow);
  41. // Generate time markers which represent location of grid lines/time labels
  42. for (let i = 1; i < elapsedMinutes / timeMarkerInterval; i++) {
  43. const timeMark = moment(firstTimeMark).add(i * timeMarkerInterval, 'minute');
  44. const position = (timeMark.valueOf() - start.valueOf()) / msPerPixel;
  45. times.push({date: timeMark.toDate(), position});
  46. }
  47. return times;
  48. }
  49. export function GridLineTimeLabels({end, timeWindow, width, className}: Props) {
  50. return (
  51. <LabelsContainer className={className}>
  52. {getTimeMarkers(end, timeWindow, width).map(({date, position}) => (
  53. <TimeLabelContainer key={date.getTime()} left={position}>
  54. <TimeLabel date={date} {...timeWindowConfig[timeWindow].dateTimeProps} />
  55. </TimeLabelContainer>
  56. ))}
  57. </LabelsContainer>
  58. );
  59. }
  60. export function GridLineOverlay({
  61. end,
  62. timeWindow,
  63. width,
  64. showCursor,
  65. stickyCursor,
  66. className,
  67. }: Props) {
  68. const {cursorLabelFormat} = timeWindowConfig[timeWindow];
  69. const makeCursorText = useCallback(
  70. (percentPosition: number) => {
  71. const start = getStartFromTimeWindow(end, timeWindow);
  72. const timeOffset = (end.getTime() - start.getTime()) * percentPosition;
  73. return moment(start.getTime() + timeOffset).format(cursorLabelFormat);
  74. },
  75. [cursorLabelFormat, end, timeWindow]
  76. );
  77. const {cursorContainerRef, timelineCursor} = useTimelineCursor<HTMLDivElement>({
  78. enabled: showCursor,
  79. sticky: stickyCursor,
  80. labelText: makeCursorText,
  81. });
  82. return (
  83. <Overlay ref={cursorContainerRef} className={className}>
  84. {timelineCursor}
  85. <GridLineContainer>
  86. {getTimeMarkers(end, timeWindow, width).map(({date, position}) => (
  87. <Gridline key={date.getTime()} left={position} />
  88. ))}
  89. </GridLineContainer>
  90. </Overlay>
  91. );
  92. }
  93. const Overlay = styled('div')`
  94. grid-row: 1;
  95. grid-column: 3;
  96. height: 100%;
  97. width: 100%;
  98. position: absolute;
  99. pointer-events: none;
  100. `;
  101. const GridLineContainer = styled('div')`
  102. position: relative;
  103. height: 100%;
  104. z-index: 1;
  105. `;
  106. const LabelsContainer = styled('div')`
  107. position: relative;
  108. align-self: stretch;
  109. `;
  110. const Gridline = styled('div')<{left: number}>`
  111. position: absolute;
  112. left: ${p => p.left}px;
  113. border-left: 1px solid ${p => p.theme.translucentInnerBorder};
  114. height: 100%;
  115. `;
  116. const TimeLabelContainer = styled(Gridline)`
  117. display: flex;
  118. height: 100%;
  119. align-items: center;
  120. border-left: none;
  121. `;
  122. const TimeLabel = styled(DateTime)`
  123. font-variant-numeric: tabular-nums;
  124. font-size: ${p => p.theme.fontSizeSmall};
  125. color: ${p => p.theme.subText};
  126. margin-left: ${space(1)};
  127. `;