mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tooltip: Improved Trend tooltip (#77251)
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
parent
5015b5b2b0
commit
4aea1107b3
@ -3,7 +3,8 @@ import React, { useMemo } from 'react';
|
|||||||
import { DataFrame, FieldMatcherID, fieldMatchers, FieldType, PanelProps, TimeRange } from '@grafana/data';
|
import { DataFrame, FieldMatcherID, fieldMatchers, FieldType, PanelProps, TimeRange } from '@grafana/data';
|
||||||
import { isLikelyAscendingVector } from '@grafana/data/src/transformations/transformers/joinDataFrames';
|
import { isLikelyAscendingVector } from '@grafana/data/src/transformations/transformers/joinDataFrames';
|
||||||
import { config, PanelDataErrorView } from '@grafana/runtime';
|
import { config, PanelDataErrorView } from '@grafana/runtime';
|
||||||
import { KeyboardPlugin, TooltipDisplayMode, TooltipPlugin, usePanelContext } from '@grafana/ui';
|
import { KeyboardPlugin, TooltipDisplayMode, usePanelContext, TooltipPlugin, TooltipPlugin2 } from '@grafana/ui';
|
||||||
|
import { TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
|
||||||
import { XYFieldMatchers } from 'app/core/components/GraphNG/types';
|
import { XYFieldMatchers } from 'app/core/components/GraphNG/types';
|
||||||
import { preparePlotFrame } from 'app/core/components/GraphNG/utils';
|
import { preparePlotFrame } from 'app/core/components/GraphNG/utils';
|
||||||
import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries';
|
import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries';
|
||||||
@ -11,6 +12,7 @@ import { findFieldIndex } from 'app/features/dimensions';
|
|||||||
|
|
||||||
import { prepareGraphableFields, regenerateLinksSupplier } from '../timeseries/utils';
|
import { prepareGraphableFields, regenerateLinksSupplier } from '../timeseries/utils';
|
||||||
|
|
||||||
|
import { TrendTooltip } from './TrendTooltip';
|
||||||
import { Options } from './panelcfg.gen';
|
import { Options } from './panelcfg.gen';
|
||||||
|
|
||||||
export const TrendPanel = ({
|
export const TrendPanel = ({
|
||||||
@ -106,7 +108,7 @@ export const TrendPanel = ({
|
|||||||
options={options}
|
options={options}
|
||||||
preparePlotFrame={preparePlotFrameTimeless}
|
preparePlotFrame={preparePlotFrameTimeless}
|
||||||
>
|
>
|
||||||
{(config, alignedDataFrame) => {
|
{(uPlotConfig, alignedDataFrame) => {
|
||||||
if (alignedDataFrame.fields.some((f) => Boolean(f.config.links?.length))) {
|
if (alignedDataFrame.fields.some((f) => Boolean(f.config.links?.length))) {
|
||||||
alignedDataFrame = regenerateLinksSupplier(
|
alignedDataFrame = regenerateLinksSupplier(
|
||||||
alignedDataFrame,
|
alignedDataFrame,
|
||||||
@ -119,17 +121,42 @@ export const TrendPanel = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<KeyboardPlugin config={config} />
|
<KeyboardPlugin config={uPlotConfig} />
|
||||||
{options.tooltip.mode === TooltipDisplayMode.None || (
|
{options.tooltip.mode !== TooltipDisplayMode.None && (
|
||||||
<TooltipPlugin
|
<>
|
||||||
frames={info.frames!}
|
{config.featureToggles.newVizTooltips ? (
|
||||||
data={alignedDataFrame}
|
<TooltipPlugin2
|
||||||
config={config}
|
config={uPlotConfig}
|
||||||
mode={options.tooltip.mode}
|
hoverMode={
|
||||||
sortOrder={options.tooltip.sort}
|
options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll
|
||||||
sync={sync}
|
}
|
||||||
timeZone={timeZone}
|
render={(u, dataIdxs, seriesIdx, isPinned = false) => {
|
||||||
/>
|
return (
|
||||||
|
<TrendTooltip
|
||||||
|
frames={info.frames!}
|
||||||
|
data={alignedDataFrame}
|
||||||
|
mode={options.tooltip.mode}
|
||||||
|
sortOrder={options.tooltip.sort}
|
||||||
|
sync={sync}
|
||||||
|
dataIdxs={dataIdxs}
|
||||||
|
seriesIdx={seriesIdx}
|
||||||
|
isPinned={isPinned}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TooltipPlugin
|
||||||
|
frames={info.frames!}
|
||||||
|
data={alignedDataFrame}
|
||||||
|
config={uPlotConfig}
|
||||||
|
mode={options.tooltip.mode}
|
||||||
|
sortOrder={options.tooltip.sort}
|
||||||
|
sync={sync}
|
||||||
|
timeZone={timeZone}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
173
public/app/plugins/panel/trend/TrendTooltip.tsx
Normal file
173
public/app/plugins/panel/trend/TrendTooltip.tsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
arrayUtils,
|
||||||
|
DashboardCursorSync,
|
||||||
|
DataFrame,
|
||||||
|
FALLBACK_COLOR,
|
||||||
|
Field,
|
||||||
|
FieldType,
|
||||||
|
formattedValueToString,
|
||||||
|
getDisplayProcessor,
|
||||||
|
getFieldDisplayName,
|
||||||
|
GrafanaTheme2,
|
||||||
|
LinkModel,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { TooltipDisplayMode, SortOrder } from '@grafana/schema';
|
||||||
|
import { SeriesTableRowProps, useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
|
import { SeriesList } from '@grafana/ui/src/components/VizTooltip/SeriesList';
|
||||||
|
import { VizTooltipFooter } from '@grafana/ui/src/components/VizTooltip/VizTooltipFooter';
|
||||||
|
import { VizTooltipHeader } from '@grafana/ui/src/components/VizTooltip/VizTooltipHeader';
|
||||||
|
import { LabelValue } from '@grafana/ui/src/components/VizTooltip/types';
|
||||||
|
|
||||||
|
interface TrendTooltipProps {
|
||||||
|
frames?: DataFrame[];
|
||||||
|
// aligned data frame
|
||||||
|
data: DataFrame;
|
||||||
|
// config: UPlotConfigBuilder;
|
||||||
|
mode?: TooltipDisplayMode;
|
||||||
|
sortOrder?: SortOrder;
|
||||||
|
sync?: () => DashboardCursorSync;
|
||||||
|
|
||||||
|
// hovered points
|
||||||
|
dataIdxs: Array<number | null>;
|
||||||
|
// closest/hovered series
|
||||||
|
seriesIdx: number | null;
|
||||||
|
isPinned: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TrendTooltip = ({
|
||||||
|
frames,
|
||||||
|
data,
|
||||||
|
mode = TooltipDisplayMode.Single,
|
||||||
|
sortOrder = SortOrder.None,
|
||||||
|
sync,
|
||||||
|
dataIdxs,
|
||||||
|
seriesIdx,
|
||||||
|
isPinned,
|
||||||
|
}: TrendTooltipProps) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
const xField = data.fields[0];
|
||||||
|
if (!xField) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, theme });
|
||||||
|
let xVal = xFieldFmt(xField!.values[dataIdxs[0]!]).text;
|
||||||
|
let tooltip: React.ReactNode = null;
|
||||||
|
|
||||||
|
const links: Array<LinkModel<Field>> = [];
|
||||||
|
const linkLookup = new Set<string>();
|
||||||
|
|
||||||
|
// Single mode
|
||||||
|
if (mode === TooltipDisplayMode.Single || isPinned) {
|
||||||
|
const field = data.fields[seriesIdx!];
|
||||||
|
|
||||||
|
if (!field) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataIdx = dataIdxs[seriesIdx!]!;
|
||||||
|
xVal = xFieldFmt(xField!.values[dataIdx]).text;
|
||||||
|
const fieldFmt = field.display || getDisplayProcessor({ field, theme });
|
||||||
|
const display = fieldFmt(field.values[dataIdx]);
|
||||||
|
|
||||||
|
if (field.getLinks) {
|
||||||
|
const v = field.values[dataIdx];
|
||||||
|
const disp = field.display ? field.display(v) : { text: `${v}`, numeric: +v };
|
||||||
|
field.getLinks({ calculatedValue: disp, valueRowIndex: dataIdx }).forEach((link) => {
|
||||||
|
const key = `${link.title}/${link.href}`;
|
||||||
|
if (!linkLookup.has(key)) {
|
||||||
|
links.push(link);
|
||||||
|
linkLookup.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip = (
|
||||||
|
<SeriesList
|
||||||
|
series={[
|
||||||
|
{
|
||||||
|
color: display.color || FALLBACK_COLOR,
|
||||||
|
label: getFieldDisplayName(field, data, frames),
|
||||||
|
value: display ? formattedValueToString(display) : null,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === TooltipDisplayMode.Multi && !isPinned) {
|
||||||
|
let series: SeriesTableRowProps[] = [];
|
||||||
|
const frame = data;
|
||||||
|
const fields = frame.fields;
|
||||||
|
const sortIdx: unknown[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
const field = frame.fields[i];
|
||||||
|
if (
|
||||||
|
!field ||
|
||||||
|
field === xField ||
|
||||||
|
field.type === FieldType.time ||
|
||||||
|
field.type !== FieldType.number ||
|
||||||
|
field.config.custom?.hideFrom?.tooltip ||
|
||||||
|
field.config.custom?.hideFrom?.viz
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const v = data.fields[i].values[dataIdxs[i]!];
|
||||||
|
const display = field.display!(v);
|
||||||
|
|
||||||
|
sortIdx.push(v);
|
||||||
|
series.push({
|
||||||
|
color: display.color || FALLBACK_COLOR,
|
||||||
|
label: field.state?.displayName ?? field.name,
|
||||||
|
value: display ? formattedValueToString(display) : null,
|
||||||
|
isActive: seriesIdx === i,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortOrder !== SortOrder.None) {
|
||||||
|
// create sort reference series array, as Array.sort() mutates the original array
|
||||||
|
const sortRef = [...series];
|
||||||
|
const sortFn = arrayUtils.sortValues(sortOrder);
|
||||||
|
|
||||||
|
series.sort((a, b) => {
|
||||||
|
// get compared values indices to retrieve raw values from sortIdx
|
||||||
|
const aIdx = sortRef.indexOf(a);
|
||||||
|
const bIdx = sortRef.indexOf(b);
|
||||||
|
return sortFn(sortIdx[aIdx], sortIdx[bIdx]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip = <SeriesList series={series} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHeaderLabel = (): LabelValue => {
|
||||||
|
return {
|
||||||
|
label: getFieldDisplayName(xField, data),
|
||||||
|
value: xVal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<VizTooltipHeader headerLabel={getHeaderLabel()} customValueDisplay={tooltip} />
|
||||||
|
{isPinned && <VizTooltipFooter dataLinks={links} canAnnotate={false} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
wrapper: css({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '280px',
|
||||||
|
}),
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user