github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/components/TimelineChart/Tooltip.plugin.tsx (about) 1 import React from 'react'; 2 import * as ReactDOM from 'react-dom'; 3 import getFormatLabel from './getFormatLabel'; 4 import clamp from './clamp'; 5 import injectTooltip from './injectTooltip'; 6 import { ITooltipWrapperProps } from './TooltipWrapper'; 7 8 const TOOLTIP_WRAPPER_ID = 'explore_tooltip_parent'; 9 10 // TooltipCallbackProps refers to the data available for the tooltip body construction 11 export interface TooltipCallbackProps { 12 timeLabel: string; 13 values: Array<{ 14 closest: number[]; 15 color: number[]; 16 // TODO: remove this 17 tagName: string; 18 }>; 19 coordsToCanvasPos?: jquery.flot.axis['p2c']; 20 canvasX?: number; 21 } 22 23 (function ($: JQueryStatic) { 24 function init(plot: jquery.flot.plot & jquery.flot.plotOptions) { 25 const exploreTooltip = injectTooltip($, TOOLTIP_WRAPPER_ID); 26 27 const params = { 28 canvasX: -1, 29 canvasY: -1, 30 pageX: -1, 31 pageY: -1, 32 xToTime: -1, 33 }; 34 35 function onMouseMove(e: { pageX: number; pageY: number; which?: number }) { 36 const offset = plot.getPlaceholder().offset()!; 37 const plotOffset = plot.getPlotOffset(); 38 39 params.canvasX = clamp( 40 0, 41 plot.width(), 42 e.pageX - offset.left - plotOffset.left 43 ); 44 params.canvasY = clamp( 45 0, 46 plot.height(), 47 e.pageY - offset.top - plotOffset.top 48 ); 49 params.pageX = e.pageX; 50 params.pageY = e.pageY; 51 } 52 53 function onMouseLeave() { 54 params.canvasX = -1; 55 params.canvasY = -1; 56 params.pageX = -1; 57 params.pageY = -1; 58 } 59 60 function onPlotHover(e: unknown, position: { x?: number }) { 61 if (position.x) { 62 params.xToTime = position.x; 63 } 64 } 65 66 plot.hooks!.drawOverlay!.push(() => { 67 const options = plot.getOptions() as jquery.flot.plotOptions & { 68 onHoverDisplayTooltip?: ( 69 data: Omit<ITooltipWrapperProps & TooltipCallbackProps, 'children'> 70 ) => React.ReactElement; 71 }; 72 const { onHoverDisplayTooltip } = options; 73 const { xaxis } = plot.getAxes() as ShamefulAny; 74 const data = plot.getData(); 75 76 if (onHoverDisplayTooltip && exploreTooltip?.length) { 77 const align = params.canvasX > plot.width() / 2 ? 'left' : 'right'; 78 const { timezone } = options.xaxis!; 79 80 const timeLabel = getFormatLabel({ 81 date: params.xToTime, 82 xaxis, 83 timezone, 84 }); 85 86 const values = data?.map((dataSeries, i) => { 87 // Sometimes we also pass a tagName/color 88 // Eg in tagExplorer page 89 // TODO: use generics 90 const d = dataSeries as jquery.flot.dataSeries & { 91 tagName: string; 92 color: { color: number[] }; 93 }; 94 95 let closest = null; 96 let color = null; 97 let tagName = String(i); 98 99 if (d?.data?.length && params.xToTime && params.pageX > 0) { 100 color = d?.color?.color; 101 tagName = d.tagName; 102 closest = (d?.data || []).reduce(function (prev, curr) { 103 return Math.abs(curr?.[0] - params.xToTime) < 104 Math.abs(prev?.[0] - params.xToTime) 105 ? curr 106 : prev; 107 }); 108 } 109 110 return { 111 closest, 112 color, 113 tagName, 114 }; 115 }); 116 117 if (!values?.length) { 118 return; 119 } 120 121 // Returns an element 122 const Tooltip = onHoverDisplayTooltip({ 123 pageX: params.pageX, 124 pageY: params.pageY, 125 timeLabel, 126 values, 127 align, 128 canvasX: params.canvasX, 129 130 coordsToCanvasPos: plot.p2c.bind(plot), 131 }); 132 133 // Type checking will be wrong if a React 18 app tries to use this code 134 ReactDOM.render(Tooltip as ShamefulAny, exploreTooltip?.[0]); 135 } 136 }); 137 138 plot.hooks!.bindEvents!.push((p, eventHolder) => { 139 eventHolder.mousemove(onMouseMove); 140 eventHolder.mouseleave(onMouseLeave); 141 plot.getPlaceholder().bind('plothover', onPlotHover); 142 }); 143 144 plot.hooks!.shutdown!.push((p, eventHolder) => { 145 eventHolder.unbind('mousemove', onMouseMove); 146 eventHolder.unbind('mouseleave', onMouseLeave); 147 plot.getPlaceholder().unbind('plothover', onPlotHover); 148 }); 149 } 150 151 $.plot.plugins.push({ 152 init, 153 options: {}, 154 name: 'rich_tooltip', 155 version: '1.0', 156 }); 157 })(jQuery);