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  };