diff --git a/.betterer.results b/.betterer.results index b8a9aacc3a6..b27d682ee35 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1131,14 +1131,6 @@ exports[`better eslint`] = { "public/app/core/components/CloseButton/CloseButton.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], - "public/app/core/components/ColorScale/ColorScale.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"] - ], "public/app/core/components/Divider.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] diff --git a/public/app/core/components/ColorScale/ColorScale.tsx b/public/app/core/components/ColorScale/ColorScale.tsx index 0a689c8addd..4fabd2bc4b8 100644 --- a/public/app/core/components/ColorScale/ColorScale.tsx +++ b/public/app/core/components/ColorScale/ColorScale.tsx @@ -128,40 +128,40 @@ function clampPercent100(v: number) { } const getStyles = (theme: GrafanaTheme2, colors: string[]) => ({ - scaleWrapper: css` - width: 100%; - font-size: 11px; - opacity: 1; - `, - scaleGradient: css` - background: linear-gradient(90deg, ${colors.join()}); - height: 10px; - pointer-events: none; - `, - legendValues: css` - display: flex; - justify-content: space-between; - pointer-events: none; - `, - hoverValue: css` - position: absolute; - margin-top: -14px; - padding: 3px 15px; - background: ${theme.colors.background.primary}; - transform: translateX(-50%); - `, - followerContainer: css` - position: relative; - pointer-events: none; - white-space: nowrap; - `, - follower: css` - position: absolute; - height: 14px; - width: 14px; - border-radius: 50%; - transform: translateX(-50%) translateY(-50%); - border: 2px solid ${theme.colors.text.primary}; - margin-top: 5px; - `, + scaleWrapper: css({ + width: '100%', + fontSize: '11px', + opacity: 1, + }), + scaleGradient: css({ + background: `linear-gradient(90deg, ${colors.join()})`, + height: '10px', + pointerEvents: 'none', + }), + legendValues: css({ + display: 'flex', + justifyContent: 'space-between', + pointerEvents: 'none', + }), + hoverValue: css({ + position: 'absolute', + marginTop: '-14px', + padding: '3px 15px', + background: theme.colors.background.primary, + transform: 'translateX(-50%)', + }), + followerContainer: css({ + position: 'relative', + pointerEvents: 'none', + whiteSpace: 'nowrap', + }), + follower: css({ + position: 'absolute', + height: '14px', + width: '14px', + borderRadius: theme.shape.radius.default, + transform: 'translateX(-50%) translateY(-50%)', + border: `2px solid ${theme.colors.text.primary}`, + marginTop: '5px', + }), }); diff --git a/public/app/features/visualization/data-hover/DataHoverView.tsx b/public/app/features/visualization/data-hover/DataHoverView.tsx index 5c651d3ff10..7a212631f1e 100644 --- a/public/app/features/visualization/data-hover/DataHoverView.tsx +++ b/public/app/features/visualization/data-hover/DataHoverView.tsx @@ -21,24 +21,27 @@ export interface Props { sortOrder?: SortOrder; mode?: TooltipDisplayMode | null; header?: string; + padding?: number; } -interface DisplayValue { +export interface DisplayValue { name: string; value: unknown; valueString: string; highlight: boolean; } -export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, header = undefined }: Props) => { - const styles = useStyles2(getStyles); - - if (!data || rowIndex == null) { - return null; - } +export function getDisplayValuesAndLinks( + data: DataFrame, + rowIndex: number, + columnIndex?: number | null, + sortOrder?: SortOrder, + mode?: TooltipDisplayMode | null +) { const fields = data.fields.map((f, idx) => { return { ...f, hovered: idx === columnIndex }; }); + const visibleFields = fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip)); const traceIDField = visibleFields.find((field) => field.name === 'traceID') || fields[0]; const orderedVisibleFields = []; @@ -89,6 +92,24 @@ export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, he displayValues.sort((a, b) => arrayUtils.sortValues(sortOrder)(a.value, b.value)); } + return { displayValues, links }; +} + +export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, header, padding = 0 }: Props) => { + const styles = useStyles2(getStyles, padding); + + if (!data || rowIndex == null) { + return null; + } + + const dispValuesAndLinks = getDisplayValuesAndLinks(data, rowIndex, columnIndex, sortOrder, mode); + + if (dispValuesAndLinks == null) { + return null; + } + + const { displayValues, links } = dispValuesAndLinks; + return (
{header && ( @@ -119,9 +140,10 @@ export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, he
); }; -const getStyles = (theme: GrafanaTheme2) => { +const getStyles = (theme: GrafanaTheme2, padding = 0) => { return { wrapper: css` + padding: ${padding}px; background: ${theme.components.tooltip.background}; border-radius: ${theme.shape.borderRadius(2)}; `, diff --git a/public/app/plugins/panel/heatmap/HeatmapHoverView.tsx b/public/app/plugins/panel/heatmap/HeatmapHoverView.tsx index 5a05b0ba09e..7ef653a2d5f 100644 --- a/public/app/plugins/panel/heatmap/HeatmapHoverView.tsx +++ b/public/app/plugins/panel/heatmap/HeatmapHoverView.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; +import uPlot from 'uplot'; import { DataFrameType, @@ -19,6 +20,7 @@ import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/tra import { DataHoverView } from 'app/features/visualization/data-hover/DataHoverView'; import { HeatmapData } from './fields'; +import { renderHistogram } from './renderHistogram'; import { HeatmapHoverEvent } from './utils'; type Props = { @@ -37,8 +39,13 @@ export const HeatmapHoverView = (props: Props) => { return ; }; -const HeatmapHoverCell = ({ data, hover, showHistogram, scopedVars, replaceVars }: Props) => { +const HeatmapHoverCell = ({ data, hover, showHistogram = false, scopedVars, replaceVars }: Props) => { const index = hover.dataIdx; + + const [isSparse] = useState( + () => data.heatmap?.meta?.type === DataFrameType.HeatmapCells && !isHeatmapCellsDense(data.heatmap) + ); + const xField = data.heatmap?.fields[0]; const yField = data.heatmap?.fields[1]; const countField = data.heatmap?.fields[2]; @@ -151,78 +158,21 @@ const HeatmapHoverCell = ({ data, hover, showHistogram, scopedVars, replaceVars let can = useRef(null); - let histCssWidth = 150; - let histCssHeight = 50; - let histCanWidth = Math.round(histCssWidth * devicePixelRatio); - let histCanHeight = Math.round(histCssHeight * devicePixelRatio); + let histCssWidth = 264; + let histCssHeight = 64; + let histCanWidth = Math.round(histCssWidth * uPlot.pxRatio); + let histCanHeight = Math.round(histCssHeight * uPlot.pxRatio); useEffect( () => { - if (showHistogram) { - let histCtx = can.current?.getContext('2d'); - - if (histCtx && xVals && yVals && countVals) { - let fromIdx = index; - - while (xVals[fromIdx--] === xVals[index]) {} - - fromIdx++; - - let toIdx = fromIdx + data.yBucketCount!; - - let maxCount = 0; - - let i = fromIdx; - while (i < toIdx) { - let c = countVals[i]; - maxCount = Math.max(maxCount, c); - i++; - } - - let pHov = new Path2D(); - let pRest = new Path2D(); - - i = fromIdx; - let j = 0; - while (i < toIdx) { - let c = countVals[i]; - - if (c > 0) { - let pctY = c / maxCount; - let pctX = j / (data.yBucketCount! + 1); - - let p = i === index ? pHov : pRest; - - p.rect( - Math.round(histCanWidth * pctX), - Math.round(histCanHeight * (1 - pctY)), - Math.round(histCanWidth / data.yBucketCount!), - Math.round(histCanHeight * pctY) - ); - } - - i++; - j++; - } - - histCtx.clearRect(0, 0, histCanWidth, histCanHeight); - - histCtx.fillStyle = '#ffffff80'; - histCtx.fill(pRest); - - histCtx.fillStyle = '#ff000080'; - histCtx.fill(pHov); - } + if (showHistogram && xVals != null && countVals != null) { + renderHistogram(can, histCanWidth, histCanHeight, xVals, countVals, index, data.yBucketCount!); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [index] ); - const [isSparse] = useState( - () => data.heatmap?.meta?.type === DataFrameType.HeatmapCells && !isHeatmapCellsDense(data.heatmap) - ); - if (isSparse) { return (
@@ -258,7 +208,7 @@ const HeatmapHoverCell = ({ data, hover, showHistogram, scopedVars, replaceVars width={histCanWidth} height={histCanHeight} ref={can} - style={{ width: histCanWidth + 'px', height: histCanHeight + 'px' }} + style={{ width: histCssWidth + 'px', height: histCssHeight + 'px' }} /> )}
diff --git a/public/app/plugins/panel/heatmap/renderHistogram.tsx b/public/app/plugins/panel/heatmap/renderHistogram.tsx new file mode 100644 index 00000000000..6169151089c --- /dev/null +++ b/public/app/plugins/panel/heatmap/renderHistogram.tsx @@ -0,0 +1,64 @@ +export function renderHistogram( + can: React.RefObject, + histCanWidth: number, + histCanHeight: number, + xVals: number[], + countVals: number[], + index: number, + yBucketCount: number +) { + let histCtx = can.current?.getContext('2d'); + + if (histCtx != null) { + let fromIdx = index; + + while (xVals[fromIdx--] === xVals[index]) {} + + fromIdx++; + + let toIdx = fromIdx + yBucketCount; + + let maxCount = 0; + + let i = fromIdx; + while (i < toIdx) { + let c = countVals[i]; + maxCount = Math.max(maxCount, c); + i++; + } + + let pHov = new Path2D(); + let pRest = new Path2D(); + + i = fromIdx; + let j = 0; + while (i < toIdx) { + let c = countVals[i]; + + if (c > 0) { + let pctY = c / maxCount; + let pctX = j / (yBucketCount + 1); + + let p = i === index ? pHov : pRest; + + p.rect( + Math.round(histCanWidth * pctX), + Math.round(histCanHeight * (1 - pctY)), + Math.round(histCanWidth / yBucketCount), + Math.round(histCanHeight * pctY) + ); + } + + i++; + j++; + } + + histCtx.clearRect(0, 0, histCanWidth, histCanHeight); + + histCtx.fillStyle = '#2E3036'; + histCtx.fill(pRest); + + histCtx.fillStyle = '#5794F2'; + histCtx.fill(pHov); + } +}