gridLines.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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}: Props) {
  50. return (
  51. <LabelsContainer>
  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. }: Props) {
  67. const {cursorLabelFormat} = timeWindowConfig[timeWindow];
  68. const makeCursorText = useCallback(
  69. (percentPosition: number) => {
  70. const start = getStartFromTimeWindow(end, timeWindow);
  71. const timeOffset = (end.getTime() - start.getTime()) * percentPosition;
  72. return moment(start.getTime() + timeOffset).format(cursorLabelFormat);
  73. },
  74. [cursorLabelFormat, end, timeWindow]
  75. );
  76. const {cursorContainerRef, timelineCursor} = useTimelineCursor<HTMLDivElement>({
  77. enabled: showCursor,
  78. sticky: stickyCursor,
  79. labelText: makeCursorText,
  80. });
  81. return (
  82. <Overlay ref={cursorContainerRef}>
  83. {timelineCursor}
  84. <GridLineContainer>
  85. {getTimeMarkers(end, timeWindow, width).map(({date, position}) => (
  86. <Gridline key={date.getTime()} left={position} />
  87. ))}
  88. </GridLineContainer>
  89. </Overlay>
  90. );
  91. }
  92. const Overlay = styled('div')`
  93. grid-row: 1;
  94. grid-column: 3;
  95. height: 100%;
  96. width: 100%;
  97. position: absolute;
  98. pointer-events: none;
  99. `;
  100. const GridLineContainer = styled('div')`
  101. position: relative;
  102. height: 100%;
  103. z-index: 1;
  104. `;
  105. const LabelsContainer = styled('div')`
  106. position: relative;
  107. align-self: stretch;
  108. `;
  109. const Gridline = styled('div')<{left: number}>`
  110. position: absolute;
  111. left: ${p => p.left}px;
  112. border-left: 1px solid ${p => p.theme.translucentInnerBorder};
  113. height: 100%;
  114. `;
  115. const TimeLabelContainer = styled(Gridline)`
  116. display: flex;
  117. height: 100%;
  118. align-items: center;
  119. border-left: none;
  120. `;
  121. const TimeLabel = styled(DateTime)`
  122. font-variant-numeric: tabular-nums;
  123. font-size: ${p => p.theme.fontSizeSmall};
  124. color: ${p => p.theme.subText};
  125. margin-left: ${space(1)};
  126. `;