github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/components/TimelineChart/Annotations.plugin.tsx (about)

     1  import React from 'react';
     2  import * as ReactDOM from 'react-dom';
     3  import Color from 'color';
     4  import { Provider } from 'react-redux';
     5  import store from '@webapp/redux/store';
     6  import extractRange from './extractRange';
     7  import AnnotationMark from './AnnotationMark';
     8  
     9  type AnnotationType = {
    10    content: string;
    11    timestamp: number;
    12    type: 'message';
    13    color: Color;
    14  };
    15  
    16  interface IFlotOptions extends jquery.flot.plotOptions {
    17    annotations?: AnnotationType[];
    18    wrapperId?: string;
    19  }
    20  
    21  interface IPlot extends jquery.flot.plot, jquery.flot.plotOptions {}
    22  
    23  (function ($) {
    24    function init(plot: IPlot) {
    25      plot.hooks!.draw!.push(renderAnnotationListInTimeline);
    26    }
    27  
    28    $.plot.plugins.push({
    29      init,
    30      options: {},
    31      name: 'annotations',
    32      version: '1.0',
    33    });
    34  })(jQuery);
    35  
    36  function renderAnnotationListInTimeline(
    37    plot: IPlot,
    38    ctx: CanvasRenderingContext2D
    39  ) {
    40    const options: IFlotOptions = plot.getOptions();
    41  
    42    if (options.annotations?.length) {
    43      const plotOffset: { top: number; left: number } = plot.getPlotOffset();
    44      const extractedX = extractRange(plot, 'x');
    45      const extractedY = extractRange(plot, 'y');
    46  
    47      options.annotations.forEach((annotation: AnnotationType) => {
    48        const left: number =
    49          Math.floor(extractedX.axis.p2c(annotation.timestamp * 1000)) +
    50          plotOffset.left;
    51  
    52        renderAnnotationIcon({
    53          annotation,
    54          options,
    55          left,
    56        });
    57  
    58        drawAnnotationLine({
    59          ctx,
    60          yMin: plotOffset.top,
    61          yMax:
    62            Math.floor(extractedY.axis.p2c(extractedY.axis.min)) + plotOffset.top,
    63          left,
    64          color: annotation.color,
    65        });
    66      });
    67    }
    68  }
    69  
    70  function drawAnnotationLine({
    71    ctx,
    72    color,
    73    left,
    74    yMax,
    75    yMin,
    76  }: {
    77    ctx: CanvasRenderingContext2D;
    78    color: Color;
    79    left: number;
    80    yMax: number;
    81    yMin: number;
    82  }) {
    83    ctx.beginPath();
    84    ctx.strokeStyle = color.hex();
    85    ctx.lineWidth = 1;
    86    ctx.moveTo(left + 0.5, yMax);
    87    ctx.lineTo(left + 0.5, yMin);
    88    ctx.stroke();
    89  }
    90  
    91  function renderAnnotationIcon({
    92    annotation,
    93    options,
    94    left,
    95  }: {
    96    annotation: AnnotationType;
    97    options: { wrapperId?: string };
    98    left: number;
    99  }) {
   100    const annotationMarkElementId =
   101      `${options.wrapperId}_annotation_mark_`.concat(
   102        String(annotation.timestamp)
   103      );
   104  
   105    const annotationMarkElement = $(`#${annotationMarkElementId}`);
   106  
   107    if (!annotationMarkElement.length) {
   108      $(
   109        `<div id="${annotationMarkElementId}" style="position: absolute; top: 0; left: ${left}px; width: 0" />`
   110      ).appendTo(`#${options.wrapperId}`);
   111    } else {
   112      annotationMarkElement.css({ left });
   113    }
   114  
   115    ReactDOM.render(
   116      <Provider store={store}>
   117        <AnnotationMark
   118          type={annotation.type}
   119          posX={left}
   120          color={annotation.color}
   121          value={{ content: annotation.content, timestamp: annotation.timestamp }}
   122        />
   123      </Provider>,
   124      document.getElementById(annotationMarkElementId)
   125    );
   126  }