github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/components/Heatmap/utils.ts (about) 1 import type { Heatmap } from '@webapp/services/render'; 2 3 import { getTimelineFormatDate, getUTCdate } from '@webapp/util/formatDate'; 4 import { SELECTED_AREA_BACKGROUND, HEATMAP_HEIGHT } from './constants'; 5 import type { SelectedAreaCoordsType } from './useHeatmapSelection.hook'; 6 7 export const drawRect = ( 8 canvas: HTMLCanvasElement, 9 x: number, 10 y: number, 11 w: number, 12 h: number 13 ) => { 14 clearRect(canvas); 15 const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; 16 17 ctx.fillStyle = SELECTED_AREA_BACKGROUND.toString(); 18 ctx.globalAlpha = 1; 19 ctx.fillRect(x, y, w, h); 20 }; 21 22 export const clearRect = (canvas: HTMLCanvasElement) => { 23 const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; 24 25 ctx.clearRect(0, 0, canvas.width, canvas.height); 26 }; 27 28 export const sortCoordinates = ( 29 v1: number, 30 v2: number 31 ): { smaller: number; bigger: number } => { 32 const isFirstBigger = v1 > v2; 33 34 return { 35 smaller: isFirstBigger ? v2 : v1, 36 bigger: isFirstBigger ? v1 : v2, 37 }; 38 }; 39 40 interface SelectionData { 41 selectionMinValue: number; 42 selectionMaxValue: number; 43 selectionStartTime: number; 44 selectionEndTime: number; 45 } 46 47 export const getTimeDataByXCoord = ( 48 heatmap: Heatmap, 49 heatmapW: number, 50 x: number 51 ) => { 52 const unitsForPixel = (heatmap.endTime - heatmap.startTime) / heatmapW; 53 54 return x * unitsForPixel + heatmap.startTime; 55 }; 56 57 export const getBucketsDurationByYCoord = (heatmap: Heatmap, y: number) => { 58 const unitsForPixel = (heatmap.maxValue - heatmap.minValue) / HEATMAP_HEIGHT; 59 60 return (HEATMAP_HEIGHT - y) * unitsForPixel; 61 }; 62 63 export const getSelectionData = ( 64 heatmap: Heatmap, 65 heatmapW: number, 66 startCoords: SelectedAreaCoordsType, 67 endCoords: SelectedAreaCoordsType, 68 isClickOnYBottomEdge?: boolean 69 ): SelectionData => { 70 const timeForPixel = (heatmap.endTime - heatmap.startTime) / heatmapW; 71 const valueForPixel = (heatmap.maxValue - heatmap.minValue) / HEATMAP_HEIGHT; 72 73 const { smaller: smallerX, bigger: biggerX } = sortCoordinates( 74 startCoords.x, 75 endCoords.x 76 ); 77 const { smaller: smallerY, bigger: biggerY } = sortCoordinates( 78 HEATMAP_HEIGHT - startCoords.y, 79 HEATMAP_HEIGHT - endCoords.y 80 ); 81 82 // to fetch correct profiles when clicking on edge cells 83 const selectionMinValue = Math.round( 84 valueForPixel * smallerY + heatmap.minValue 85 ); 86 87 return { 88 selectionMinValue: isClickOnYBottomEdge 89 ? selectionMinValue - 1 90 : selectionMinValue, 91 selectionMaxValue: Math.round(valueForPixel * biggerY + heatmap.minValue), 92 selectionStartTime: timeForPixel * smallerX + heatmap.startTime, 93 selectionEndTime: timeForPixel * biggerX + heatmap.startTime, 94 }; 95 }; 96 97 // TODO(dogfrogfog): refactor (reuse existing formatters) 98 export const timeFormatter = 99 (min: number, max: number, timezone: string) => (v: number) => { 100 const d = getUTCdate( 101 new Date(v / 1000000), 102 timezone === 'utc' ? 0 : new Date().getTimezoneOffset() 103 ); 104 // nanoseconds -> hours 105 const hours = (max - min) / 60 / 60 / 1000 / 1000 / 1000; 106 107 return getTimelineFormatDate(d, hours); 108 }; 109 110 // TODO(dogfrogfog): refactor types 111 interface TickOptions { 112 formatter?: ShamefulAny; 113 ticksCount: number; 114 timezone?: string; 115 } 116 117 export const getTicks = ( 118 min: number, 119 max: number, 120 options: TickOptions, 121 sampleRate?: number 122 ): string[] => { 123 let formatter; 124 if (sampleRate && options.formatter) { 125 formatter = (v: number) => options.formatter.format(v, sampleRate, false); 126 } else { 127 formatter = timeFormatter(min, max, options.timezone as string); 128 } 129 130 const step = (max - min) / options.ticksCount; 131 const ticksArray = [formatter(min)]; 132 133 for (let i = 1; i <= options.ticksCount; i += 1) { 134 ticksArray.push(formatter(min + step * i)); 135 } 136 137 return ticksArray; 138 };