gridLines.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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 {TimeWindowOptions} from 'sentry/views/monitors/components/overviewTimeline/types';
  7. import {useTimelineCursor} from './timelineCursor';
  8. interface Props {
  9. end: Date;
  10. start: Date;
  11. timeWindowConfig: TimeWindowOptions;
  12. width: number;
  13. className?: string;
  14. showCursor?: boolean;
  15. stickyCursor?: boolean;
  16. }
  17. /**
  18. * Aligns a date to a clean offset such as start of minute, hour, day
  19. * based on the interval of how far each time label is placed.
  20. */
  21. function alignTimeMarkersToStartOf(date: moment.Moment, timeMarkerInterval: number) {
  22. if (timeMarkerInterval < 60) {
  23. date.minute(date.minutes() - (date.minutes() % timeMarkerInterval));
  24. } else if (timeMarkerInterval < 60 * 24) {
  25. date.startOf('hour');
  26. } else {
  27. date.startOf('day');
  28. }
  29. }
  30. interface TimeMarker {
  31. date: Date;
  32. position: number;
  33. }
  34. function getTimeMarkersFromConfig(
  35. start: Date,
  36. end: Date,
  37. config: TimeWindowOptions,
  38. width: number
  39. ) {
  40. const {elapsedMinutes, timeMarkerInterval} = config;
  41. const msPerPixel = (elapsedMinutes * 60 * 1000) / width;
  42. const times: TimeMarker[] = [];
  43. const lastTimeMark = moment(end);
  44. alignTimeMarkersToStartOf(lastTimeMark, timeMarkerInterval);
  45. // Generate time markers which represent location of grid lines/time labels
  46. for (let i = 1; i < elapsedMinutes / timeMarkerInterval; i++) {
  47. const timeMark = moment(lastTimeMark).subtract(i * timeMarkerInterval, 'minute');
  48. const position = (timeMark.valueOf() - start.valueOf()) / msPerPixel;
  49. times.push({date: timeMark.toDate(), position});
  50. }
  51. return times;
  52. }
  53. export function GridLineTimeLabels({
  54. width,
  55. timeWindowConfig,
  56. start,
  57. end,
  58. className,
  59. }: Props) {
  60. return (
  61. <LabelsContainer className={className}>
  62. {getTimeMarkersFromConfig(start, end, timeWindowConfig, width).map(
  63. ({date, position}) => (
  64. <TimeLabelContainer key={date.getTime()} left={position}>
  65. <TimeLabel date={date} {...timeWindowConfig.dateTimeProps} />
  66. </TimeLabelContainer>
  67. )
  68. )}
  69. </LabelsContainer>
  70. );
  71. }
  72. export function GridLineOverlay({
  73. end,
  74. width,
  75. timeWindowConfig,
  76. start,
  77. showCursor,
  78. stickyCursor,
  79. className,
  80. }: Props) {
  81. const {dateLabelFormat} = timeWindowConfig;
  82. const makeCursorText = useCallback(
  83. (percentPosition: number) => {
  84. const timeOffset = (end.getTime() - start.getTime()) * percentPosition;
  85. return moment(start.getTime() + timeOffset).format(dateLabelFormat);
  86. },
  87. [dateLabelFormat, end, start]
  88. );
  89. const {cursorContainerRef, timelineCursor} = useTimelineCursor<HTMLDivElement>({
  90. enabled: showCursor,
  91. sticky: stickyCursor,
  92. labelText: makeCursorText,
  93. });
  94. return (
  95. <Overlay ref={cursorContainerRef} className={className}>
  96. {timelineCursor}
  97. <GridLineContainer>
  98. {getTimeMarkersFromConfig(start, end, timeWindowConfig, width).map(
  99. ({date, position}) => (
  100. <Gridline key={date.getTime()} left={position} />
  101. )
  102. )}
  103. </GridLineContainer>
  104. </Overlay>
  105. );
  106. }
  107. const Overlay = styled('div')`
  108. grid-row: 1;
  109. grid-column: 3;
  110. height: 100%;
  111. width: 100%;
  112. position: absolute;
  113. pointer-events: none;
  114. `;
  115. const GridLineContainer = styled('div')`
  116. position: relative;
  117. height: 100%;
  118. z-index: 1;
  119. `;
  120. const LabelsContainer = styled('div')`
  121. position: relative;
  122. align-self: stretch;
  123. border-bottom: 1px solid ${p => p.theme.border};
  124. `;
  125. const Gridline = styled('div')<{left: number}>`
  126. position: absolute;
  127. left: ${p => p.left}px;
  128. border-left: 1px solid ${p => p.theme.translucentInnerBorder};
  129. height: 100%;
  130. `;
  131. const TimeLabelContainer = styled(Gridline)`
  132. display: flex;
  133. height: 100%;
  134. align-items: center;
  135. border-left: none;
  136. `;
  137. const TimeLabel = styled(DateTime)`
  138. font-variant-numeric: tabular-nums;
  139. font-size: ${p => p.theme.fontSizeSmall};
  140. color: ${p => p.theme.subText};
  141. margin-left: ${space(1)};
  142. `;