mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
Tooltip: Improved Timeseries and Candlestick tooltips (#75841)
This commit is contained in:
parent
23b4568597
commit
d4b75928ca
@ -1,21 +1,24 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { FALLBACK_COLOR, GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
|
||||
import { ColorIndicator } from './types';
|
||||
import { ColorIndicator, DEFAULT_COLOR_INDICATOR } from './types';
|
||||
import { getColorIndicatorClass } from './utils';
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
colorIndicator: ColorIndicator;
|
||||
color?: string;
|
||||
colorIndicator?: ColorIndicator;
|
||||
}
|
||||
|
||||
export type ColorIndicatorStyles = ReturnType<typeof getStyles>;
|
||||
|
||||
export const VizTooltipColorIndicator = ({ color, colorIndicator = ColorIndicator.value }: Props) => {
|
||||
export const VizTooltipColorIndicator = ({
|
||||
color = FALLBACK_COLOR,
|
||||
colorIndicator = DEFAULT_COLOR_INDICATOR,
|
||||
}: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
|
@ -19,7 +19,7 @@ export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) =
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div>
|
||||
{contentLabelValue?.map((labelValue, i) => {
|
||||
{contentLabelValue.map((labelValue, i) => {
|
||||
const { label, value, color, colorIndicator, colorPlacement, isActive } = labelValue;
|
||||
return (
|
||||
<VizTooltipRow
|
||||
@ -29,7 +29,6 @@ export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) =
|
||||
color={color}
|
||||
colorIndicator={colorIndicator}
|
||||
colorPlacement={colorPlacement}
|
||||
colorFirst={false}
|
||||
isActive={isActive}
|
||||
justify={'space-between'}
|
||||
/>
|
||||
|
@ -16,7 +16,6 @@ export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => (
|
||||
value={keyValuePair.value}
|
||||
color={keyValuePair.color}
|
||||
colorIndicator={keyValuePair.colorIndicator!}
|
||||
colorFirst={false}
|
||||
justify={'space-between'}
|
||||
/>
|
||||
))}
|
||||
|
@ -11,7 +11,6 @@ import { ColorPlacement, LabelValue } from './types';
|
||||
|
||||
interface Props extends LabelValue {
|
||||
justify?: string;
|
||||
colorFirst?: boolean;
|
||||
isActive?: boolean; // for series list
|
||||
marginRight?: string;
|
||||
}
|
||||
@ -21,9 +20,8 @@ export const VizTooltipRow = ({
|
||||
value,
|
||||
color,
|
||||
colorIndicator,
|
||||
colorPlacement = ColorPlacement.leading,
|
||||
colorPlacement = ColorPlacement.first,
|
||||
justify = 'flex-start',
|
||||
colorFirst = true,
|
||||
isActive = false,
|
||||
marginRight = '0px',
|
||||
}: Props) => {
|
||||
@ -52,7 +50,9 @@ export const VizTooltipRow = ({
|
||||
<div className={styles.contentWrapper}>
|
||||
{(color || label) && (
|
||||
<div className={styles.valueWrapper}>
|
||||
{color && colorFirst && <VizTooltipColorIndicator color={color} colorIndicator={colorIndicator!} />}
|
||||
{color && colorPlacement === ColorPlacement.first && (
|
||||
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator} />
|
||||
)}
|
||||
<Tooltip content={label} interactive={false} show={showLabelTooltip}>
|
||||
<div
|
||||
className={cx(styles.label, isActive && styles.activeSeries)}
|
||||
@ -66,18 +66,18 @@ export const VizTooltipRow = ({
|
||||
)}
|
||||
|
||||
<div className={styles.valueWrapper}>
|
||||
{color && !colorFirst && colorPlacement === ColorPlacement.leading && (
|
||||
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator!} />
|
||||
{color && colorPlacement === ColorPlacement.leading && (
|
||||
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator} />
|
||||
)}
|
||||
<Tooltip content={value ? value.toString() : ''} interactive={false} show={showValueTooltip}>
|
||||
<div className={cx(styles.value, isActive)} onMouseEnter={onMouseEnterValue} onMouseLeave={onMouseLeaveValue}>
|
||||
{value}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{color && !colorFirst && colorPlacement === ColorPlacement.trailing && (
|
||||
{color && colorPlacement === ColorPlacement.trailing && (
|
||||
<>
|
||||
|
||||
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator!} />
|
||||
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -12,6 +12,7 @@ export enum ColorIndicator {
|
||||
|
||||
export enum ColorPlacement {
|
||||
hidden = 'hidden',
|
||||
first = 'first',
|
||||
leading = 'leading',
|
||||
trailing = 'trailing',
|
||||
}
|
||||
@ -24,3 +25,5 @@ export interface LabelValue {
|
||||
colorPlacement?: ColorPlacement;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_COLOR_INDICATOR = ColorIndicator.series;
|
||||
|
@ -10,6 +10,8 @@ import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
|
||||
|
||||
import { CloseButton } from './CloseButton';
|
||||
|
||||
export const DEFAULT_TOOLTIP_WIDTH = 280;
|
||||
|
||||
// todo: barchart? histogram?
|
||||
export const enum TooltipHoverMode {
|
||||
// Single mode in TimeSeries, Candlestick, Trend, StateTimeline, Heatmap?
|
||||
|
@ -7,12 +7,14 @@ import uPlot from 'uplot';
|
||||
import { Field, getDisplayProcessor, getLinksSupplier, PanelProps } from '@grafana/data';
|
||||
import { PanelDataErrorView } from '@grafana/runtime';
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
import { TooltipPlugin, UPlotConfigBuilder, usePanelContext, useTheme2, ZoomPlugin } from '@grafana/ui';
|
||||
import { TooltipPlugin, TooltipPlugin2, UPlotConfigBuilder, usePanelContext, useTheme2, ZoomPlugin } from '@grafana/ui';
|
||||
import { AxisProps } from '@grafana/ui/src/components/uPlot/config/UPlotAxisBuilder';
|
||||
import { ScaleProps } from '@grafana/ui/src/components/uPlot/config/UPlotScaleBuilder';
|
||||
import { TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
|
||||
import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
import { TimeSeriesTooltip } from '../timeseries/TimeSeriesTooltip';
|
||||
import { AnnotationEditorPlugin } from '../timeseries/plugins/AnnotationEditorPlugin';
|
||||
import { AnnotationsPlugin } from '../timeseries/plugins/AnnotationsPlugin';
|
||||
import { ContextMenuPlugin } from '../timeseries/plugins/ContextMenuPlugin';
|
||||
@ -242,7 +244,7 @@ export const CandlestickPanel = ({
|
||||
tweakScale={tweakScale}
|
||||
options={options}
|
||||
>
|
||||
{(config, alignedDataFrame) => {
|
||||
{(uplotConfig, alignedDataFrame) => {
|
||||
alignedDataFrame.fields.forEach((field) => {
|
||||
field.getLinks = getLinksSupplier(
|
||||
alignedDataFrame,
|
||||
@ -255,73 +257,100 @@ export const CandlestickPanel = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<ZoomPlugin config={config} onZoom={onChangeTimeRange} withZoomY={true} />
|
||||
<TooltipPlugin
|
||||
data={alignedDataFrame}
|
||||
config={config}
|
||||
mode={TooltipDisplayMode.Multi}
|
||||
sync={sync}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
{/* Renders annotation markers*/}
|
||||
{data.annotations && (
|
||||
<AnnotationsPlugin annotations={data.annotations} config={config} timeZone={timeZone} />
|
||||
)}
|
||||
{/* Enables annotations creation*/}
|
||||
{enableAnnotationCreation ? (
|
||||
<AnnotationEditorPlugin data={alignedDataFrame} timeZone={timeZone} config={config}>
|
||||
{({ startAnnotating }) => {
|
||||
{config.featureToggles.newVizTooltips ? (
|
||||
<TooltipPlugin2
|
||||
config={uplotConfig}
|
||||
hoverMode={TooltipHoverMode.xAll}
|
||||
queryZoom={onChangeTimeRange}
|
||||
clientZoom={true}
|
||||
render={(u, dataIdxs, seriesIdx, isPinned = false) => {
|
||||
return (
|
||||
<ContextMenuPlugin
|
||||
data={alignedDataFrame}
|
||||
config={config}
|
||||
timeZone={timeZone}
|
||||
replaceVariables={replaceVariables}
|
||||
defaultItems={
|
||||
enableAnnotationCreation
|
||||
? [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'Add annotation',
|
||||
ariaLabel: 'Add annotation',
|
||||
icon: 'comment-alt',
|
||||
onClick: (e, p) => {
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
startAnnotating({ coords: p.coords });
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
<TimeSeriesTooltip
|
||||
frames={[info.frame]}
|
||||
seriesFrame={alignedDataFrame}
|
||||
dataIdxs={dataIdxs}
|
||||
seriesIdx={seriesIdx}
|
||||
mode={TooltipDisplayMode.Multi}
|
||||
isPinned={isPinned}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</AnnotationEditorPlugin>
|
||||
) : (
|
||||
<ContextMenuPlugin
|
||||
data={alignedDataFrame}
|
||||
config={config}
|
||||
timeZone={timeZone}
|
||||
replaceVariables={replaceVariables}
|
||||
defaultItems={[]}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<ZoomPlugin config={uplotConfig} onZoom={onChangeTimeRange} withZoomY={true} />
|
||||
<TooltipPlugin
|
||||
data={alignedDataFrame}
|
||||
config={uplotConfig}
|
||||
mode={TooltipDisplayMode.Multi}
|
||||
sync={sync}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/* Renders annotation markers*/}
|
||||
{data.annotations && (
|
||||
<AnnotationsPlugin annotations={data.annotations} config={uplotConfig} timeZone={timeZone} />
|
||||
)}
|
||||
{/* Enables annotations creation*/}
|
||||
{!config.featureToggles.newVizTooltips ? (
|
||||
enableAnnotationCreation ? (
|
||||
<AnnotationEditorPlugin data={alignedDataFrame} timeZone={timeZone} config={uplotConfig}>
|
||||
{({ startAnnotating }) => {
|
||||
return (
|
||||
<ContextMenuPlugin
|
||||
data={alignedDataFrame}
|
||||
config={uplotConfig}
|
||||
timeZone={timeZone}
|
||||
replaceVariables={replaceVariables}
|
||||
defaultItems={
|
||||
enableAnnotationCreation
|
||||
? [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'Add annotation',
|
||||
ariaLabel: 'Add annotation',
|
||||
icon: 'comment-alt',
|
||||
onClick: (e, p) => {
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
startAnnotating({ coords: p.coords });
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</AnnotationEditorPlugin>
|
||||
) : (
|
||||
<ContextMenuPlugin
|
||||
data={alignedDataFrame}
|
||||
config={uplotConfig}
|
||||
timeZone={timeZone}
|
||||
replaceVariables={replaceVariables}
|
||||
defaultItems={[]}
|
||||
/>
|
||||
)
|
||||
) : undefined}
|
||||
{data.annotations && (
|
||||
<ExemplarsPlugin config={uplotConfig} exemplars={data.annotations} timeZone={timeZone} />
|
||||
)}
|
||||
{data.annotations && <ExemplarsPlugin config={config} exemplars={data.annotations} timeZone={timeZone} />}
|
||||
|
||||
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
|
||||
<ThresholdControlsPlugin
|
||||
config={config}
|
||||
config={uplotConfig}
|
||||
fieldConfig={fieldConfig}
|
||||
onThresholdsChange={canEditThresholds ? onThresholdsChange : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
<OutsideRangePlugin config={config} onChangeTimeRange={onChangeTimeRange} />
|
||||
<OutsideRangePlugin config={uplotConfig} onChangeTimeRange={onChangeTimeRange} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
@ -3,10 +3,12 @@ import React, { useMemo } from 'react';
|
||||
import { PanelProps, DataFrameType } from '@grafana/data';
|
||||
import { PanelDataErrorView } from '@grafana/runtime';
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
import { KeyboardPlugin, TooltipPlugin, usePanelContext, ZoomPlugin } from '@grafana/ui';
|
||||
import { KeyboardPlugin, TooltipPlugin, TooltipPlugin2, usePanelContext, ZoomPlugin } from '@grafana/ui';
|
||||
import { TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
|
||||
import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
import { TimeSeriesTooltip } from './TimeSeriesTooltip';
|
||||
import { Options } from './panelcfg.gen';
|
||||
import { AnnotationEditorPlugin } from './plugins/AnnotationEditorPlugin';
|
||||
import { AnnotationsPlugin } from './plugins/AnnotationsPlugin';
|
||||
@ -74,7 +76,7 @@ export const TimeSeriesPanel = ({
|
||||
legend={options.legend}
|
||||
options={options}
|
||||
>
|
||||
{(config, alignedDataFrame) => {
|
||||
{(uplotConfig, alignedDataFrame) => {
|
||||
if (alignedDataFrame.fields.some((f) => Boolean(f.config.links?.length))) {
|
||||
alignedDataFrame = regenerateLinksSupplier(
|
||||
alignedDataFrame,
|
||||
@ -87,68 +89,98 @@ export const TimeSeriesPanel = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<KeyboardPlugin config={config} />
|
||||
<ZoomPlugin config={config} onZoom={onChangeTimeRange} withZoomY={true} />
|
||||
<KeyboardPlugin config={uplotConfig} />
|
||||
{options.tooltip.mode === TooltipDisplayMode.None || (
|
||||
<TooltipPlugin
|
||||
frames={frames}
|
||||
data={alignedDataFrame}
|
||||
config={config}
|
||||
mode={options.tooltip.mode}
|
||||
sortOrder={options.tooltip.sort}
|
||||
sync={sync}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
<>
|
||||
{config.featureToggles.newVizTooltips ? (
|
||||
<TooltipPlugin2
|
||||
config={uplotConfig}
|
||||
hoverMode={
|
||||
options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll
|
||||
}
|
||||
queryZoom={onChangeTimeRange}
|
||||
clientZoom={true}
|
||||
render={(u, dataIdxs, seriesIdx, isPinned = false) => {
|
||||
return (
|
||||
<TimeSeriesTooltip
|
||||
frames={frames}
|
||||
seriesFrame={alignedDataFrame}
|
||||
dataIdxs={dataIdxs}
|
||||
seriesIdx={seriesIdx}
|
||||
mode={options.tooltip.mode}
|
||||
sortOrder={options.tooltip.sort}
|
||||
isPinned={isPinned}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<ZoomPlugin config={uplotConfig} onZoom={onChangeTimeRange} withZoomY={true} />
|
||||
<TooltipPlugin
|
||||
frames={frames}
|
||||
data={alignedDataFrame}
|
||||
config={uplotConfig}
|
||||
mode={options.tooltip.mode}
|
||||
sortOrder={options.tooltip.sort}
|
||||
sync={sync}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* Renders annotation markers*/}
|
||||
{data.annotations && (
|
||||
<AnnotationsPlugin annotations={data.annotations} config={config} timeZone={timeZone} />
|
||||
<AnnotationsPlugin annotations={data.annotations} config={uplotConfig} timeZone={timeZone} />
|
||||
)}
|
||||
{/* Enables annotations creation*/}
|
||||
{enableAnnotationCreation ? (
|
||||
<AnnotationEditorPlugin data={alignedDataFrame} timeZone={timeZone} config={config}>
|
||||
{({ startAnnotating }) => {
|
||||
return (
|
||||
<ContextMenuPlugin
|
||||
data={alignedDataFrame}
|
||||
config={config}
|
||||
timeZone={timeZone}
|
||||
replaceVariables={replaceVariables}
|
||||
defaultItems={[
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'Add annotation',
|
||||
ariaLabel: 'Add annotation',
|
||||
icon: 'comment-alt',
|
||||
onClick: (e, p) => {
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
startAnnotating({ coords: p.coords });
|
||||
{/*Enables annotations creation*/}
|
||||
{!config.featureToggles.newVizTooltips ? (
|
||||
enableAnnotationCreation ? (
|
||||
<AnnotationEditorPlugin data={alignedDataFrame} timeZone={timeZone} config={uplotConfig}>
|
||||
{({ startAnnotating }) => {
|
||||
return (
|
||||
<ContextMenuPlugin
|
||||
data={alignedDataFrame}
|
||||
config={uplotConfig}
|
||||
timeZone={timeZone}
|
||||
replaceVariables={replaceVariables}
|
||||
defaultItems={[
|
||||
{
|
||||
items: [
|
||||
{
|
||||
label: 'Add annotation',
|
||||
ariaLabel: 'Add annotation',
|
||||
icon: 'comment-alt',
|
||||
onClick: (e, p) => {
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
startAnnotating({ coords: p.coords });
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</AnnotationEditorPlugin>
|
||||
) : (
|
||||
<ContextMenuPlugin
|
||||
data={alignedDataFrame}
|
||||
frames={frames}
|
||||
config={config}
|
||||
timeZone={timeZone}
|
||||
replaceVariables={replaceVariables}
|
||||
defaultItems={[]}
|
||||
/>
|
||||
)}
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</AnnotationEditorPlugin>
|
||||
) : (
|
||||
<ContextMenuPlugin
|
||||
data={alignedDataFrame}
|
||||
frames={frames}
|
||||
config={uplotConfig}
|
||||
timeZone={timeZone}
|
||||
replaceVariables={replaceVariables}
|
||||
defaultItems={[]}
|
||||
/>
|
||||
)
|
||||
) : undefined}
|
||||
{data.annotations && (
|
||||
<ExemplarsPlugin
|
||||
visibleSeries={getVisibleLabels(config, frames)}
|
||||
config={config}
|
||||
visibleSeries={getVisibleLabels(uplotConfig, frames)}
|
||||
config={uplotConfig}
|
||||
exemplars={data.annotations}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
@ -156,13 +188,13 @@ export const TimeSeriesPanel = ({
|
||||
|
||||
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
|
||||
<ThresholdControlsPlugin
|
||||
config={config}
|
||||
config={uplotConfig}
|
||||
fieldConfig={fieldConfig}
|
||||
onThresholdsChange={canEditThresholds ? onThresholdsChange : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
<OutsideRangePlugin config={config} onChangeTimeRange={onChangeTimeRange} />
|
||||
<OutsideRangePlugin config={uplotConfig} onChangeTimeRange={onChangeTimeRange} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
162
public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx
Normal file
162
public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
FALLBACK_COLOR,
|
||||
FieldType,
|
||||
GrafanaTheme2,
|
||||
formattedValueToString,
|
||||
getDisplayProcessor,
|
||||
LinkModel,
|
||||
Field,
|
||||
getFieldDisplayName,
|
||||
arrayUtils,
|
||||
} from '@grafana/data';
|
||||
import { SortOrder, TooltipDisplayMode } from '@grafana/schema/dist/esm/common/common.gen';
|
||||
import { useStyles2, useTheme2 } 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';
|
||||
import { ColorIndicator, ColorPlacement, LabelValue } from '@grafana/ui/src/components/VizTooltip/types';
|
||||
import { DEFAULT_TOOLTIP_WIDTH } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
|
||||
|
||||
import { getDataLinks } from '../status-history/utils';
|
||||
|
||||
// exemplar / annotation / time region hovering?
|
||||
// add annotation UI / alert dismiss UI?
|
||||
|
||||
interface TimeSeriesTooltipProps {
|
||||
frames?: DataFrame[];
|
||||
// aligned series frame
|
||||
seriesFrame: DataFrame;
|
||||
// hovered points
|
||||
dataIdxs: Array<number | null>;
|
||||
// closest/hovered series
|
||||
seriesIdx?: number | null;
|
||||
mode?: TooltipDisplayMode;
|
||||
sortOrder?: SortOrder;
|
||||
|
||||
isPinned: boolean;
|
||||
}
|
||||
|
||||
export const TimeSeriesTooltip = ({
|
||||
frames,
|
||||
seriesFrame,
|
||||
dataIdxs,
|
||||
seriesIdx,
|
||||
mode = TooltipDisplayMode.Single,
|
||||
sortOrder = SortOrder.None,
|
||||
isPinned,
|
||||
}: TimeSeriesTooltipProps) => {
|
||||
const theme = useTheme2();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const xField = seriesFrame.fields[0];
|
||||
if (!xField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, theme });
|
||||
let xVal = xFieldFmt(xField!.values[dataIdxs[0]!]).text;
|
||||
let links: Array<LinkModel<Field>> = [];
|
||||
let contentLabelValue: LabelValue[] = [];
|
||||
|
||||
// Single mode
|
||||
if (mode === TooltipDisplayMode.Single || isPinned) {
|
||||
const field = seriesFrame.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]);
|
||||
links = getDataLinks(field, dataIdx);
|
||||
|
||||
contentLabelValue = [
|
||||
{
|
||||
label: getFieldDisplayName(field, seriesFrame, frames),
|
||||
value: display ? formattedValueToString(display) : null,
|
||||
color: display.color || FALLBACK_COLOR,
|
||||
colorIndicator: ColorIndicator.series,
|
||||
colorPlacement: ColorPlacement.first,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (mode === TooltipDisplayMode.Multi && !isPinned) {
|
||||
const fields = seriesFrame.fields;
|
||||
const sortIdx: unknown[] = [];
|
||||
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const field = seriesFrame.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 = seriesFrame.fields[i].values[dataIdxs[i]!];
|
||||
const display = field.display!(v); // super expensive :(
|
||||
|
||||
sortIdx.push(v);
|
||||
contentLabelValue.push({
|
||||
label: field.state?.displayName ?? field.name,
|
||||
value: display ? formattedValueToString(display) : null,
|
||||
color: display.color || FALLBACK_COLOR,
|
||||
colorIndicator: ColorIndicator.series,
|
||||
colorPlacement: ColorPlacement.first,
|
||||
isActive: seriesIdx === i,
|
||||
});
|
||||
|
||||
if (sortOrder !== SortOrder.None) {
|
||||
// create sort reference series array, as Array.sort() mutates the original array
|
||||
const sortRef = [...contentLabelValue];
|
||||
const sortFn = arrayUtils.sortValues(sortOrder);
|
||||
|
||||
contentLabelValue.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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getHeaderLabel = (): LabelValue => {
|
||||
return {
|
||||
label: '',
|
||||
value: xVal,
|
||||
};
|
||||
};
|
||||
|
||||
const getContentLabelValue = () => {
|
||||
return contentLabelValue;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.wrapper}>
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabelValue()} />
|
||||
{isPinned && <VizTooltipFooter dataLinks={links} canAnnotate={false} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: DEFAULT_TOOLTIP_WIDTH,
|
||||
}),
|
||||
});
|
@ -20,6 +20,7 @@ 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';
|
||||
import { DEFAULT_TOOLTIP_WIDTH } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
|
||||
|
||||
interface TrendTooltipProps {
|
||||
frames?: DataFrame[];
|
||||
@ -168,6 +169,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '280px',
|
||||
width: DEFAULT_TOOLTIP_WIDTH,
|
||||
}),
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user