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