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 }