import {mat3} from 'gl-matrix'; import {computeInterval, Rect} from 'sentry/utils/profiling/speedscope'; import {FlamegraphTheme} from '../flamegraph/flamegraphTheme'; import {getContext, measureText} from '../gl/utils'; export function getIntervalTimeAtX(logicalSpaceToConfigView: mat3, x: number): number { const vector = logicalSpaceToConfigView[0] * x + logicalSpaceToConfigView[6]; if (vector > 1) { return Math.round(vector); } return Math.round(vector * 10) / 10; } class GridRenderer { canvas: HTMLCanvasElement; context: CanvasRenderingContext2D; theme: FlamegraphTheme; formatter: (value: number) => string; constructor( canvas: HTMLCanvasElement, theme: FlamegraphTheme, formatter: (value: number) => string ) { this.canvas = canvas; this.theme = theme; this.formatter = formatter; this.context = getContext(canvas, '2d'); } draw( configViewSpace: Rect, physicalViewRect: Rect, configViewToPhysicalSpace: mat3, logicalSpaceToConfigView: mat3, drawGridTicks: boolean = true ): void { this.context.font = `${ this.theme.SIZES.LABEL_FONT_SIZE * window.devicePixelRatio }px ${this.theme.FONTS.FONT}`; this.context.textBaseline = 'top'; this.context.lineWidth = this.theme.SIZES.GRID_LINE_WIDTH / 2; // Draw the background of the top timeline this.context.fillStyle = this.theme.COLORS.GRID_FRAME_BACKGROUND_COLOR; this.context.fillRect( 0, 0, physicalViewRect.width, this.theme.SIZES.TIMELINE_HEIGHT * window.devicePixelRatio ); // Draw top timeline lines this.context.fillStyle = this.theme.COLORS.GRID_LINE_COLOR; this.context.fillRect( 0, 0, physicalViewRect.width, this.theme.SIZES.GRID_LINE_WIDTH / 2 ); this.context.fillRect( 0, this.theme.SIZES.TIMELINE_HEIGHT * window.devicePixelRatio, physicalViewRect.width, this.theme.SIZES.GRID_LINE_WIDTH / 2 ); if (drawGridTicks) { const intervals = computeInterval( configViewSpace, logicalSpaceToConfigView, getIntervalTimeAtX ); for (let i = 0; i < intervals.length; i++) { // Compute the x position of our interval from config space to physical const physicalIntervalPosition = Math.round( intervals[i] * configViewToPhysicalSpace[0] + configViewToPhysicalSpace[6] ); // Format the label text const labelText = this.formatter(intervals[i]); this.context.fillStyle = this.theme.COLORS.LABEL_FONT_COLOR; // Subtract width of the text and padding so that the text is align to the left of our interval this.context.fillText( labelText, physicalIntervalPosition - measureText(labelText, this.context).width - this.theme.SIZES.LABEL_FONT_PADDING * window.devicePixelRatio, this.theme.SIZES.LABEL_FONT_PADDING * window.devicePixelRatio ); // Draw the vertical grid line this.context.strokeStyle = this.theme.COLORS.GRID_LINE_COLOR; this.context.strokeRect( physicalIntervalPosition - this.theme.SIZES.GRID_LINE_WIDTH / 2, physicalViewRect.y, this.theme.SIZES.GRID_LINE_WIDTH / 2, physicalViewRect.height ); } } } } export {GridRenderer};