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 { isLikelyAscendingVector } from '@grafana/data/src/transformations/transformers/joinDataFrames';
|
||||
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 { preparePlotFrame } from 'app/core/components/GraphNG/utils';
|
||||
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 { TrendTooltip } from './TrendTooltip';
|
||||
import { Options } from './panelcfg.gen';
|
||||
|
||||
export const TrendPanel = ({
|
||||
@ -106,7 +108,7 @@ export const TrendPanel = ({
|
||||
options={options}
|
||||
preparePlotFrame={preparePlotFrameTimeless}
|
||||
>
|
||||
{(config, alignedDataFrame) => {
|
||||
{(uPlotConfig, alignedDataFrame) => {
|
||||
if (alignedDataFrame.fields.some((f) => Boolean(f.config.links?.length))) {
|
||||
alignedDataFrame = regenerateLinksSupplier(
|
||||
alignedDataFrame,
|
||||
@ -119,12 +121,35 @@ export const TrendPanel = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<KeyboardPlugin config={config} />
|
||||
{options.tooltip.mode === TooltipDisplayMode.None || (
|
||||
<KeyboardPlugin config={uPlotConfig} />
|
||||
{options.tooltip.mode !== TooltipDisplayMode.None && (
|
||||
<>
|
||||
{config.featureToggles.newVizTooltips ? (
|
||||
<TooltipPlugin2
|
||||
config={uPlotConfig}
|
||||
hoverMode={
|
||||
options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll
|
||||
}
|
||||
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={config}
|
||||
config={uPlotConfig}
|
||||
mode={options.tooltip.mode}
|
||||
sortOrder={options.tooltip.sort}
|
||||
sync={sync}
|
||||
@ -132,6 +157,8 @@ export const TrendPanel = ({
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TimeSeries>
|
||||
|
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