Heatmap: All tooltip mode selector (#79956)

Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
Leon Sorokin 2024-01-05 13:11:24 -06:00 committed by GitHub
parent 583b9797af
commit 1ec04243da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 198 additions and 75 deletions

View File

@ -126,7 +126,7 @@ Controls tooltip options
| Property | Type | Required | Default | Description |
|------------------|---------|----------|---------|----------------------------------------------------------------|
| `show` | boolean | **Yes** | | Controls if the tooltip is shown |
| `mode` | string | **Yes** | | TODO docs<br/>Possible values are: `single`, `multi`, `none`. |
| `showColorScale` | boolean | No | | Controls if the tooltip shows a color scale in header |
| `yHistogram` | boolean | No | | Controls if the tooltip shows a histogram of the y-axis values |
@ -138,7 +138,7 @@ Controls tooltip options
| `exemplars` | [ExemplarConfig](#exemplarconfig) | **Yes** | | Controls exemplar options |
| `legend` | [HeatmapLegend](#heatmaplegend) | **Yes** | | Controls legend options |
| `showValue` | string | **Yes** | | &#124; *{<br/> layout: ui.HeatmapCellLayout & "auto" // TODO: fix after remove when https://github.com/grafana/cuetsy/issues/74 is fixed<br/>}<br/>Controls the display of the value in the cell |
| `tooltip` | [HeatmapTooltip](#heatmaptooltip) | **Yes** | | Controls tooltip options |
| `tooltip` | [object](#tooltip) | **Yes** | `map[mode:single showColorScale:false yHistogram:false]` | Controls tooltip options |
| `yAxis` | [YAxisConfig](#yaxisconfig) | **Yes** | | Configuration options for the yAxis |
| `calculate` | boolean | No | `false` | Controls if the heatmap should be calculated from data |
| `calculation` | [HeatmapCalculationOptions](#heatmapcalculationoptions) | No | | |
@ -237,4 +237,12 @@ Filters values between a given range
|----------|-----------------------------------|----------|---------|-------------|
| `object` | Possible types are: [](#), [](#). | | |
### Tooltip
Controls tooltip options
| Property | Type | Required | Default | Description |
|----------|-----------------------------------|----------|---------|-------------|
| `object` | Possible types are: [](#), [](#). | | |

View File

@ -130,9 +130,9 @@ export interface FilterValueRange {
*/
export interface HeatmapTooltip {
/**
* Controls if the tooltip is shown
* Controls how the tooltip is shown
*/
show: boolean;
mode: ui.TooltipDisplayMode;
/**
* Controls if the tooltip shows a color scale in header
*/
@ -266,7 +266,7 @@ export const defaultOptions: Partial<Options> = {
},
showValue: ui.VisibilityMode.Auto,
tooltip: {
show: true,
mode: ui.TooltipDisplayMode.Single,
yHistogram: false,
showColorScale: false,
},

View File

@ -134,6 +134,7 @@ export const TooltipPlugin2 = ({ config, hoverMode, render, clientZoom = false,
winHeight = htmlEl.clientHeight - 5;
});
let seriesIdxs: Array<number | null> = plot?.cursor.idxs!.slice()!;
let closestSeriesIdx: number | null = null;
let pendingRender = false;
@ -192,9 +193,7 @@ export const TooltipPlugin2 = ({ config, hoverMode, render, clientZoom = false,
style: _style,
isPinned: _isPinned,
isHovering: _isHovering,
contents: _isHovering
? renderRef.current(_plot!, _plot!.cursor.idxs!, closestSeriesIdx, _isPinned, dismiss)
: null,
contents: _isHovering ? renderRef.current(_plot!, seriesIdxs, closestSeriesIdx, _isPinned, dismiss) : null,
dismiss,
};
@ -324,12 +323,12 @@ export const TooltipPlugin2 = ({ config, hoverMode, render, clientZoom = false,
// fires on data value hovers/unhovers (before setSeries)
config.addHook('setLegend', (u) => {
let hoveredSeriesIdx = _plot!.cursor.idxs!.findIndex((v, i) => i > 0 && v != null);
seriesIdxs = _plot?.cursor!.idxs!.slice()!;
let hoveredSeriesIdx = seriesIdxs.findIndex((v, i) => i > 0 && v != null);
let _isHoveringNow = hoveredSeriesIdx !== -1;
// in mode: 2 uPlot won't fire the proximity-based setSeries (below)
// so we set closestSeriesIdx here instead
// TODO: setSeries only fires for TimeSeries & Trend...not state timeline or statsus history
// setSeries may not fire if focus.prox is not set, so we set closestSeriesIdx here instead
if (hoverMode === TooltipHoverMode.xyOne) {
closestSeriesIdx = hoveredSeriesIdx;
}

View File

@ -16,7 +16,7 @@ import {
ScopedVars,
} from '@grafana/data';
import { HeatmapCellLayout } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui';
import { TooltipDisplayMode, useStyles2 } from '@grafana/ui';
import { VizTooltipContent } from '@grafana/ui/src/components/VizTooltip/VizTooltipContent';
import { VizTooltipFooter } from '@grafana/ui/src/components/VizTooltip/VizTooltipFooter';
import { VizTooltipHeader } from '@grafana/ui/src/components/VizTooltip/VizTooltipHeader';
@ -31,6 +31,7 @@ import { renderHistogram } from './renderHistogram';
import { formatMilliseconds, getFieldFromData, getHoverCellColor, getSparseCellMinMax } from './tooltip/utils';
interface Props {
mode: TooltipDisplayMode;
dataIdxs: Array<number | null>;
seriesIdx: number | null | undefined;
dataRef: React.MutableRefObject<HeatmapData>;
@ -65,11 +66,10 @@ const HeatmapHoverCell = ({
showHistogram,
isPinned,
canAnnotate,
panelData,
showColorScale = false,
scopedVars,
replaceVars,
dismiss,
mode,
}: Props) => {
const index = dataIdxs[1]!;
const data = dataRef.current;
@ -102,8 +102,6 @@ const HeatmapHoverCell = ({
const meta = readHeatmapRowsCustomMeta(data.heatmap);
const yDisp = yField?.display ? (v: string) => formattedValueToString(yField.display!(v)) : (v: string) => `${v}`;
const yValueIdx = index % data.yBucketCount! ?? 0;
let interval = xField?.config.interval;
let yBucketMin: string;
@ -114,9 +112,15 @@ const HeatmapHoverCell = ({
let nonNumericOrdinalDisplay: string | undefined = undefined;
if (isSparse) {
({ xBucketMin, xBucketMax, yBucketMin, yBucketMax } = getSparseCellMinMax(data!, index));
} else {
let contentLabelValue: LabelValue[] = [];
const getYValueIndex = (idx: number) => {
return idx % data.yBucketCount! ?? 0;
};
let yValueIdx = getYValueIndex(index);
const getData = (idx: number = index) => {
if (meta.yOrdinalDisplay) {
const yMinIdx = data.yLayout === HeatmapCellLayout.le ? yValueIdx - 1 : yValueIdx;
const yMaxIdx = data.yLayout === HeatmapCellLayout.le ? yValueIdx : yValueIdx + 1;
@ -154,15 +158,130 @@ const HeatmapHoverCell = ({
}
if (data.xLayout === HeatmapCellLayout.le) {
xBucketMax = xVals[index];
xBucketMax = xVals[idx];
xBucketMin = xBucketMax - data.xBucketSize!;
} else {
xBucketMin = xVals[index];
xBucketMin = xVals[idx];
xBucketMax = xBucketMin + data.xBucketSize!;
}
};
if (isSparse) {
({ xBucketMin, xBucketMax, yBucketMin, yBucketMax } = getSparseCellMinMax(data!, index));
} else {
getData();
}
const count = countVals?.[index];
const { cellColor, colorPalette } = getHoverCellColor(data, index);
const getDisplayData = (fromIdx: number, toIdx: number) => {
let vals = [];
for (let idx = fromIdx; idx <= toIdx; idx++) {
if (!countVals?.[idx]) {
continue;
}
const color = getHoverCellColor(data, idx).cellColor;
count = getCountValue(idx);
if (isSparse) {
({ xBucketMin, xBucketMax, yBucketMin, yBucketMax } = getSparseCellMinMax(data!, idx));
} else {
yValueIdx = getYValueIndex(idx);
getData(idx);
}
const { label, value } = getContentLabels()[0];
vals.push({
label,
value,
color: color ?? '#FFF',
isActive: index === idx,
});
}
return vals;
};
const getContentLabels = (): LabelValue[] => {
const isMulti = mode === TooltipDisplayMode.Multi && !isPinned;
if (nonNumericOrdinalDisplay) {
return isMulti
? [{ label: `Name ${nonNumericOrdinalDisplay}`, value: data.display!(count) }]
: [{ label: 'Name', value: nonNumericOrdinalDisplay }];
}
switch (data.yLayout) {
case HeatmapCellLayout.unknown:
return isMulti
? [{ label: yDisp(yBucketMin), value: data.display!(count) }]
: [{ label: '', value: yDisp(yBucketMin) }];
}
return isMulti
? [
{
label: `Bucket ${yDisp(yBucketMin)}` + '-' + `${yDisp(yBucketMax)}`,
value: data.display!(count),
},
]
: [
{
label: 'Bucket',
value: `${yDisp(yBucketMin)}` + '-' + `${yDisp(yBucketMax)}`,
},
];
};
const getCountValue = (idx: number) => {
return countVals?.[idx];
};
let count = getCountValue(index);
if (mode === TooltipDisplayMode.Single || isPinned) {
const fromToInt: LabelValue[] = interval ? [{ label: 'Duration', value: formatMilliseconds(interval) }] : [];
contentLabelValue = [
{
label: getFieldDisplayName(countField, data.heatmap),
value: data.display!(count),
color: cellColor ?? '#FFF',
colorPlacement: ColorPlacement.trailing,
colorIndicator: ColorIndicator.value,
},
...getContentLabels(),
...fromToInt,
];
}
if (mode === TooltipDisplayMode.Multi && !isPinned) {
let xVal = xField.values[index];
let fromIdx = index;
let toIdx = index;
while (xField.values[fromIdx - 1] === xVal) {
fromIdx--;
}
while (xField.values[toIdx + 1] === xVal) {
toIdx++;
}
const vals: LabelValue[] = getDisplayData(fromIdx, toIdx);
vals.forEach((val) => {
contentLabelValue.push({
label: val.label,
value: val.value,
color: val.color ?? '#FFF',
colorIndicator: ColorIndicator.value,
colorPlacement: ColorPlacement.trailing,
isActive: val.isActive,
});
});
}
const visibleFields = data.heatmap?.fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip));
const links: Array<LinkModel<Field>> = [];
@ -203,7 +322,7 @@ const HeatmapHoverCell = ({
useEffect(
() => {
if (showHistogram && xVals != null && countVals != null) {
if (showHistogram && xVals != null && countVals != null && mode === TooltipDisplayMode.Single) {
renderHistogram(can, histCanWidth, histCanHeight, xVals, countVals, index, data.yBucketCount!);
}
},
@ -211,26 +330,6 @@ const HeatmapHoverCell = ({
[index]
);
const { cellColor, colorPalette } = getHoverCellColor(data, index);
const getContentLabels = (): LabelValue[] => {
if (nonNumericOrdinalDisplay) {
return [{ label: 'Name', value: nonNumericOrdinalDisplay }];
}
switch (data.yLayout) {
case HeatmapCellLayout.unknown:
return [{ label: '', value: yDisp(yBucketMin) }];
}
return [
{
label: 'Bucket',
value: `${yDisp(yBucketMin)}` + '-' + `${yDisp(yBucketMax)}`,
},
];
};
const getHeaderLabel = (): LabelValue => {
return {
label: '',
@ -239,23 +338,15 @@ const HeatmapHoverCell = ({
};
const getContentLabelValue = (): LabelValue[] => {
const fromToInt: LabelValue[] = interval ? [{ label: 'Duration', value: formatMilliseconds(interval) }] : [];
return [
{
label: getFieldDisplayName(countField, data.heatmap),
value: data.display!(count),
color: cellColor ?? '#FFF',
colorPlacement: ColorPlacement.trailing,
colorIndicator: ColorIndicator.value,
},
...getContentLabels(),
...fromToInt,
];
return contentLabelValue;
};
const getCustomContent = () => {
let content: ReactElement[] = [];
if (mode !== TooltipDisplayMode.Single) {
return content;
}
// Histogram
if (showHistogram) {
content.push(

View File

@ -18,6 +18,7 @@ import {
Portal,
ScaleDistribution,
TooltipPlugin2,
TooltipDisplayMode,
ZoomPlugin,
UPlotChart,
usePanelContext,
@ -167,7 +168,7 @@ export const HeatmapPanel = ({
theme,
eventBus,
onhover: !showNewVizTooltips ? onhover : null,
onclick: !showNewVizTooltips && options.tooltip.show ? onclick : null,
onclick: !showNewVizTooltips && options.tooltip.mode !== TooltipDisplayMode.None ? onclick : null,
isToolTipOpen,
timeZone,
getTimeRange: () => timeRangeRef.current,
@ -232,7 +233,7 @@ export const HeatmapPanel = ({
<UPlotChart config={builder} data={facets as any} width={vizWidth} height={vizHeight}>
{/*children ? children(config, alignedFrame) : null*/}
{!showNewVizTooltips && <ZoomPlugin config={builder} onZoom={onChangeTimeRange} />}
{showNewVizTooltips && options.tooltip.show && (
{showNewVizTooltips && options.tooltip.mode !== TooltipDisplayMode.None && (
<TooltipPlugin2
config={builder}
hoverMode={TooltipHoverMode.xyOne}
@ -240,6 +241,7 @@ export const HeatmapPanel = ({
render={(u, dataIdxs, seriesIdx, isPinned, dismiss) => {
return (
<HeatmapHoverView
mode={options.tooltip.mode}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}
dataRef={dataRef}
@ -269,7 +271,7 @@ export const HeatmapPanel = ({
</VizLayout>
{!showNewVizTooltips && (
<Portal>
{hover && options.tooltip.show && (
{hover && options.tooltip.mode !== TooltipDisplayMode.None && (
<VizTooltipContainer
position={{ x: hover.pageX, y: hover.pageY }}
offset={{ x: 10, y: 10 }}

View File

@ -74,7 +74,7 @@ describe('Heatmap Migrations', () => {
},
"showValue": "never",
"tooltip": {
"show": true,
"mode": "single",
"yHistogram": true,
},
"yAxis": {
@ -131,7 +131,7 @@ describe('Heatmap Migrations', () => {
},
"showValue": "never",
"tooltip": {
"show": false,
"mode": "none",
"yHistogram": false,
},
"yAxis": {

View File

@ -7,6 +7,7 @@ import {
HeatmapCalculationMode,
HeatmapCalculationOptions,
} from '@grafana/schema';
import { TooltipDisplayMode } from '@grafana/ui';
import { colorSchemes } from './palettes';
import { Options, defaultOptions, HeatmapColorMode } from './types';
@ -17,6 +18,20 @@ export const heatmapMigrationHandler = (panel: PanelModel): Partial<Options> =>
if (Object.keys(panel.options ?? {}).length === 0) {
return heatmapChangedHandler(panel, 'heatmap', { angular: panel }, panel.fieldConfig);
}
// multi tooltip mode in 10.3+
let showTooltip = panel.options?.tooltip?.show;
if (showTooltip !== undefined) {
if (showTooltip === true) {
panel.options.tooltip.mode = TooltipDisplayMode.Single;
} else if (showTooltip === false) {
panel.options.tooltip.mode = TooltipDisplayMode.None;
}
// Remove old tooltip option
delete panel.options.tooltip?.show;
}
return panel.options;
};
@ -111,7 +126,7 @@ export function angularToReactHeatmap(angular: any): { fieldConfig: FieldConfigS
},
showValue: VisibilityMode.Never,
tooltip: {
show: Boolean(angular.tooltip?.show),
mode: Boolean(angular.tooltip?.show) ? TooltipDisplayMode.Single : TooltipDisplayMode.None,
yHistogram: Boolean(angular.tooltip?.showHistogram),
},
exemplars: {

View File

@ -9,6 +9,7 @@ import {
ScaleDistributionConfig,
HeatmapCellLayout,
} from '@grafana/schema';
import { TooltipDisplayMode } from '@grafana/ui';
import { addHideFrom, ScaleDistributionEditor } from '@grafana/ui/src/options/builder';
import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper';
@ -391,11 +392,18 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(HeatmapPanel)
category = ['Tooltip'];
builder.addBooleanSwitch({
path: 'tooltip.show',
name: 'Show tooltip',
defaultValue: defaultOptions.tooltip.show,
builder.addRadio({
path: 'tooltip.mode',
name: 'Tooltip mode',
category,
defaultValue: TooltipDisplayMode.Single,
settings: {
options: [
{ value: TooltipDisplayMode.Single, label: 'Single' },
{ value: TooltipDisplayMode.Multi, label: 'All' },
{ value: TooltipDisplayMode.None, label: 'Hidden' },
],
},
});
builder.addBooleanSwitch({
@ -403,7 +411,7 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(HeatmapPanel)
name: 'Show histogram (Y axis)',
defaultValue: defaultOptions.tooltip.yHistogram,
category,
showIf: (opts) => opts.tooltip.show,
showIf: (opts) => opts.tooltip.mode !== TooltipDisplayMode.None,
});
builder.addBooleanSwitch({
@ -411,7 +419,7 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(HeatmapPanel)
name: 'Show color scale',
defaultValue: defaultOptions.tooltip.showColorScale,
category,
showIf: (opts) => opts.tooltip.show && config.featureToggles.newVizTooltips,
showIf: (opts) => opts.tooltip.mode !== TooltipDisplayMode.None && config.featureToggles.newVizTooltips,
});
category = ['Legend'];

View File

@ -78,8 +78,8 @@ composableKinds: PanelCfg: lineage: {
} @cuetsy(kind="interface")
// Controls tooltip options
HeatmapTooltip: {
// Controls if the tooltip is shown
show: bool
// Controls how the tooltip is shown
mode: ui.TooltipDisplayMode
// Controls if the tooltip shows a histogram of the y-axis values
yHistogram?: bool
// Controls if the tooltip shows a color scale in header
@ -145,7 +145,7 @@ composableKinds: PanelCfg: lineage: {
}
// Controls tooltip options
tooltip: HeatmapTooltip | *{
show: true
mode: ui.TooltipDisplayMode & (*"single" | _)
yHistogram: false
showColorScale: false
}

View File

@ -127,9 +127,9 @@ export interface FilterValueRange {
*/
export interface HeatmapTooltip {
/**
* Controls if the tooltip is shown
* Controls how the tooltip is shown
*/
show: boolean;
mode: ui.TooltipDisplayMode;
/**
* Controls if the tooltip shows a color scale in header
*/
@ -263,7 +263,7 @@ export const defaultOptions: Partial<Options> = {
},
showValue: ui.VisibilityMode.Auto,
tooltip: {
show: true,
mode: ui.TooltipDisplayMode.Single,
yHistogram: false,
showColorScale: false,
},