Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VizTooltips: Heatmap fixes and improvements #83876

Merged
merged 11 commits into from
Mar 7, 2024
2 changes: 1 addition & 1 deletion packages/grafana-ui/src/components/VizTooltip/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const getContentItems = (
): VizTooltipItem[] => {
let rows: VizTooltipItem[] = [];

let allNumeric = false;
let allNumeric = true;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorting fix


for (let i = 0; i < fields.length; i++) {
const field = fields[i];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar' }:
);
})}
</div>
{links && (
{links && links.length > 0 && (
<div className={styles.exemplarFooter}>
{links.map((link, i) => (
<LinkButton key={i} href={link.href} className={styles.linkButton}>
Expand Down
41 changes: 12 additions & 29 deletions public/app/plugins/panel/heatmap/HeatmapHoverViewOld.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import {
getFieldDisplayName,
LinkModel,
TimeRange,
getLinksSupplier,
InterpolateFunction,
ScopedVars,
} from '@grafana/data';
import { HeatmapCellLayout } from '@grafana/schema';
import { LinkButton, VerticalGroup } from '@grafana/ui';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { DataHoverView } from 'app/features/visualization/data-hover/DataHoverView';

import { getDataLinks } from '../status-history/utils';

import { HeatmapData } from './fields';
import { renderHistogram } from './renderHistogram';
import { HeatmapHoverEvent } from './utils';
Expand All @@ -29,7 +29,6 @@ type Props = {
showHistogram?: boolean;
timeRange: TimeRange;
replaceVars: InterpolateFunction;
scopedVars: ScopedVars[];
};

export const HeatmapHoverView = (props: Props) => {
Expand All @@ -39,7 +38,7 @@ export const HeatmapHoverView = (props: Props) => {
return <HeatmapHoverCell {...props} />;
};

const HeatmapHoverCell = ({ data, hover, showHistogram = false, scopedVars, replaceVars }: Props) => {
const HeatmapHoverCell = ({ data, hover, showHistogram = false }: Props) => {
const index = hover.dataIdx;

const [isSparse] = useState(
Expand Down Expand Up @@ -70,7 +69,8 @@ const HeatmapHoverCell = ({ data, hover, showHistogram = false, scopedVars, repl
const meta = readHeatmapRowsCustomMeta(data.heatmap);
const yDisp = yField?.display ? (v: string) => formattedValueToString(yField.display!(v)) : (v: string) => `${v}`;

const yValueIdx = index % data.yBucketCount! ?? 0;
const yValueIdx = index % (data.yBucketCount ?? 1);
const xValueIdx = Math.floor(index / (data.yBucketCount ?? 1));

let yBucketMin: string;
let yBucketMax: string;
Expand Down Expand Up @@ -126,33 +126,16 @@ const HeatmapHoverCell = ({ data, hover, showHistogram = false, scopedVars, repl

const count = countVals?.[index];

const visibleFields = data.heatmap?.fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip));
const links: Array<LinkModel<Field>> = [];
const linkLookup = new Set<string>();

for (const field of visibleFields ?? []) {
const hasLinks = field.config.links && field.config.links.length > 0;
let links: Array<LinkModel<Field>> = [];

if (hasLinks && data.heatmap) {
const appropriateScopedVars = scopedVars.find(
(scopedVar) =>
scopedVar && scopedVar.__dataContext && scopedVar.__dataContext.value.field.name === nonNumericOrdinalDisplay
);
const linksField = data.series?.fields[yValueIdx + 1];

field.getLinks = getLinksSupplier(data.heatmap, field, appropriateScopedVars || {}, replaceVars);
}
if (linksField != null) {
const visible = !Boolean(linksField.config.custom?.hideFrom?.tooltip);
const hasLinks = (linksField.config.links?.length ?? 0) > 0;

if (field.getLinks) {
const value = field.values[index];
const display = field.display ? field.display(value) : { text: `${value}`, numeric: +value };

field.getLinks({ calculatedValue: display, valueRowIndex: index }).forEach((link) => {
const key = `${link.title}/${link.href}`;
if (!linkLookup.has(key)) {
links.push(link);
linkLookup.add(key);
}
});
if (visible && hasLinks) {
links = getDataLinks(linksField, xValueIdx);
}
}

Expand Down
53 changes: 8 additions & 45 deletions public/app/plugins/panel/heatmap/HeatmapPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import { css } from '@emotion/css';
import React, { useCallback, useMemo, useRef, useState } from 'react';

import {
DashboardCursorSync,
DataFrame,
DataFrameType,
Field,
getLinksSupplier,
GrafanaTheme2,
PanelProps,
ScopedVars,
TimeRange,
} from '@grafana/data';
import { DashboardCursorSync, DataFrameType, GrafanaTheme2, PanelProps, TimeRange } from '@grafana/data';
import { config, PanelDataErrorView } from '@grafana/runtime';
import { ScaleDistributionConfig } from '@grafana/schema';
import {
Expand All @@ -34,8 +24,8 @@ import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/tra
import { AnnotationsPlugin2 } from '../timeseries/plugins/AnnotationsPlugin2';

import { ExemplarModalHeader } from './ExemplarModalHeader';
import { HeatmapHoverView } from './HeatmapHoverView';
import { HeatmapHoverView as HeatmapHoverViewOld } from './HeatmapHoverViewOld';
import { HeatmapHoverView } from './HeatmapHoverViewOld';
import { HeatmapTooltip } from './HeatmapTooltip';
import { prepareHeatmapData } from './fields';
import { quantizeScheme } from './palettes';
import { Options } from './types';
Expand Down Expand Up @@ -70,50 +60,26 @@ export const HeatmapPanel = ({
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);

// necessary for enabling datalinks in hover view
let scopedVarsFromRawData: ScopedVars[] = [];
for (const series of data.series) {
for (const field of series.fields) {
if (field.state?.scopedVars) {
scopedVarsFromRawData.push(field.state.scopedVars);
}
}
}

// ugh
let timeRangeRef = useRef<TimeRange>(timeRange);
timeRangeRef.current = timeRange;

const getFieldLinksSupplier = useCallback(
(exemplars: DataFrame, field: Field) => {
return getLinksSupplier(exemplars, field, field.state?.scopedVars ?? {}, replaceVariables);
},
[replaceVariables]
);

const palette = useMemo(() => quantizeScheme(options.color, theme), [options.color, theme]);

const info = useMemo(() => {
try {
return prepareHeatmapData(
data.series,
data.annotations,
options,
palette,
theme,
getFieldLinksSupplier,
replaceVariables
);
return prepareHeatmapData(data.series, data.annotations, options, palette, theme, replaceVariables);
} catch (ex) {
return { warning: `${ex}` };
}
}, [data.series, data.annotations, options, palette, theme, getFieldLinksSupplier, replaceVariables]);
}, [data.series, data.annotations, options, palette, theme, replaceVariables]);

const facets = useMemo(() => {
let exemplarsXFacet: number[] | undefined = []; // "Time" field
let exemplarsYFacet: Array<number | undefined> = [];

const meta = readHeatmapRowsCustomMeta(info.heatmap);

if (info.exemplars?.length) {
exemplarsXFacet = info.exemplars?.fields[0].values;

Expand Down Expand Up @@ -265,7 +231,7 @@ export const HeatmapPanel = ({
};

return (
<HeatmapHoverView
<HeatmapTooltip
mode={viaSync ? TooltipDisplayMode.Multi : options.tooltip.mode}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}
Expand All @@ -275,8 +241,6 @@ export const HeatmapPanel = ({
showHistogram={options.tooltip.yHistogram}
showColorScale={options.tooltip.showColorScale}
panelData={data}
replaceVars={replaceVariables}
scopedVars={scopedVarsFromRawData}
annotate={enableAnnotationCreation ? annotate : undefined}
/>
);
Expand Down Expand Up @@ -308,13 +272,12 @@ export const HeatmapPanel = ({
allowPointerEvents={isToolTipOpen.current}
>
{shouldDisplayCloseButton && <ExemplarModalHeader onClick={onCloseToolTip} />}
<HeatmapHoverViewOld
<HeatmapHoverView
timeRange={timeRange}
data={info}
hover={hover}
showHistogram={options.tooltip.yHistogram}
replaceVars={replaceVariables}
scopedVars={scopedVarsFromRawData}
/>
</VizTooltipContainer>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactElement, useEffect, useRef, useState } from 'react';
import React, { ReactElement, useEffect, useRef, useState, ReactNode } from 'react';
import uPlot from 'uplot';

import {
Expand All @@ -7,11 +7,8 @@ import {
FieldType,
formattedValueToString,
getFieldDisplayName,
getLinksSupplier,
InterpolateFunction,
LinkModel,
PanelData,
ScopedVars,
} from '@grafana/data';
import { HeatmapCellLayout } from '@grafana/schema';
import { TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui';
Expand All @@ -24,13 +21,14 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
import { DataHoverView } from 'app/features/visualization/data-hover/DataHoverView';

import { getDataLinks } from '../status-history/utils';
import { getStyles } from '../timeseries/TimeSeriesTooltip';

import { HeatmapData } from './fields';
import { renderHistogram } from './renderHistogram';
import { formatMilliseconds, getFieldFromData, getHoverCellColor, getSparseCellMinMax } from './tooltip/utils';

interface Props {
interface HeatmapTooltipProps {
mode: TooltipDisplayMode;
dataIdxs: Array<number | null>;
seriesIdx: number | null | undefined;
Expand All @@ -40,12 +38,10 @@ interface Props {
isPinned: boolean;
dismiss: () => void;
panelData: PanelData;
replaceVars: InterpolateFunction;
scopedVars: ScopedVars[];
annotate?: () => void;
}

export const HeatmapHoverView = (props: Props) => {
export const HeatmapTooltip = (props: HeatmapTooltipProps) => {
if (props.seriesIdx === 2) {
return (
<DataHoverView
Expand All @@ -66,11 +62,9 @@ const HeatmapHoverCell = ({
showHistogram,
isPinned,
showColorScale = false,
scopedVars,
replaceVars,
mode,
annotate,
}: Props) => {
}: HeatmapTooltipProps) => {
const index = dataIdxs[1]!;
const data = dataRef.current;

Expand Down Expand Up @@ -114,11 +108,8 @@ const HeatmapHoverCell = ({

let contentItems: VizTooltipItem[] = [];

const getYValueIndex = (idx: number) => {
return idx % data.yBucketCount! ?? 0;
};

let yValueIdx = getYValueIndex(index);
const yValueIdx = index % (data.yBucketCount ?? 1);
const xValueIdx = Math.floor(index / (data.yBucketCount ?? 1));

const getData = (idx: number = index) => {
if (meta.yOrdinalDisplay) {
Expand Down Expand Up @@ -187,7 +178,6 @@ const HeatmapHoverCell = ({
if (isSparse) {
({ xBucketMin, xBucketMax, yBucketMin, yBucketMax } = getSparseCellMinMax(data!, idx));
} else {
yValueIdx = getYValueIndex(idx);
getData(idx);
}

Expand Down Expand Up @@ -283,34 +273,23 @@ const HeatmapHoverCell = ({
});
}

const visibleFields = data.heatmap?.fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip));
const links: Array<LinkModel<Field>> = [];
const linkLookup = new Set<string>();
let footer: ReactNode;

for (const field of visibleFields ?? []) {
const hasLinks = field.config.links && field.config.links.length > 0;
if (isPinned) {
let links: Array<LinkModel<Field>> = [];

if (hasLinks && data.heatmap) {
const appropriateScopedVars = scopedVars.find(
(scopedVar) =>
scopedVar && scopedVar.__dataContext && scopedVar.__dataContext.value.field.name === nonNumericOrdinalDisplay
);

field.getLinks = getLinksSupplier(data.heatmap, field, appropriateScopedVars || {}, replaceVars);
}
const linksField = data.series?.fields[yValueIdx + 1];

if (field.getLinks) {
const value = field.values[index];
const display = field.display ? field.display(value) : { text: `${value}`, numeric: +value };
if (linksField != null) {
const visible = !Boolean(linksField.config.custom?.hideFrom?.tooltip);
const hasLinks = (linksField.config.links?.length ?? 0) > 0;

field.getLinks({ calculatedValue: display, valueRowIndex: index }).forEach((link) => {
const key = `${link.title}/${link.href}`;
if (!linkLookup.has(key)) {
links.push(link);
linkLookup.add(key);
}
});
if (visible && hasLinks) {
links = getDataLinks(linksField, xValueIdx);
}
}

footer = <VizTooltipFooter dataLinks={links} annotate={annotate} />;
}

let can = useRef<HTMLCanvasElement>(null);
Expand Down Expand Up @@ -377,9 +356,7 @@ const HeatmapHoverCell = ({
</div>
))}
</VizTooltipContent>
{(links.length > 0 || isPinned) && (
<VizTooltipFooter dataLinks={links} annotate={isPinned ? annotate : undefined} />
)}
{footer}
</div>
);
};