go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/components/timeline/top_axis.tsx (about) 1 // Copyright 2024 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import { useTheme } from '@mui/material'; 16 import { axisTop, select } from 'd3'; 17 import { Duration } from 'luxon'; 18 import { useEffect, useRef } from 'react'; 19 20 import { displayDuration } from '@/common/tools/time_utils'; 21 22 import { TEXT_HEIGHT, TEXT_MARGIN, TOP_AXIS_HEIGHT } from './constants'; 23 import { useRulerState, useTimelineConfig } from './context'; 24 25 function TopAxisRuler() { 26 const state = useRulerState(); 27 const config = useTimelineConfig(); 28 const startMs = config.startTimeMs; 29 const selectedMs = state ? config.xScale.invert(state).getTime() : null; 30 31 return ( 32 <> 33 <line 34 opacity={state === null ? 0 : 1} 35 x1={state || 0} 36 x2={state || 0} 37 y2={-TOP_AXIS_HEIGHT} 38 stroke="red" 39 pointerEvents="none" 40 /> 41 <text 42 opacity={state === null ? 0 : 1} 43 x={(state || 0) + TEXT_MARGIN} 44 y={-TEXT_HEIGHT - TEXT_MARGIN} 45 fill="red" 46 textAnchor="start" 47 > 48 {selectedMs ? ( 49 selectedMs > startMs ? ( 50 <> 51 {displayDuration(Duration.fromMillis(selectedMs - startMs))} since 52 start 53 </> 54 ) : ( 55 <> 56 {displayDuration(Duration.fromMillis(startMs - selectedMs))}{' '} 57 before start 58 </> 59 ) 60 ) : ( 61 '' 62 )} 63 </text> 64 </> 65 ); 66 } 67 68 export function TopAxis() { 69 const theme = useTheme(); 70 const config = useTimelineConfig(); 71 72 const topAxisElement = useRef<SVGGElement>(null); 73 useEffect(() => { 74 const topAxis = axisTop(config.xScale).ticks(config.timeInterval); 75 topAxis(select(topAxisElement.current!)); 76 }, [config.xScale, config.timeInterval]); 77 78 return ( 79 <svg 80 width={config.bodyWidth} 81 height={TOP_AXIS_HEIGHT} 82 css={{ 83 gridArea: 'top-axis', 84 position: 'sticky', 85 top: 0, 86 zIndex: 1, 87 background: theme.palette.background.default, 88 }} 89 > 90 <g transform="translate(0.5, 0.5)"> 91 <g transform={`translate(0, ${TOP_AXIS_HEIGHT - 1})`}> 92 <g ref={topAxisElement}></g> 93 <TopAxisRuler /> 94 </g> 95 </g> 96 <path 97 d={`m${config.bodyWidth - 0.5},0v${TOP_AXIS_HEIGHT}`} 98 stroke="currentcolor" 99 /> 100 </svg> 101 ); 102 }