mirror of
https://github.com/grafana/grafana.git
synced 2025-01-15 19:22:34 -06:00
VizTooltips: Optimize performance (#80102)
This commit is contained in:
parent
90fb6a0122
commit
200c71f5d6
@ -5,12 +5,20 @@ import { LabelValue } from './types';
|
||||
|
||||
interface Props {
|
||||
headerLabel: LabelValue;
|
||||
isPinned: boolean;
|
||||
}
|
||||
|
||||
export const HeaderLabel = ({ headerLabel }: Props) => {
|
||||
export const HeaderLabel = ({ headerLabel, isPinned }: Props) => {
|
||||
const { label, value, color, colorIndicator } = headerLabel;
|
||||
|
||||
return (
|
||||
<VizTooltipRow label={label} value={value} color={color} colorIndicator={colorIndicator} marginRight={'22px'} />
|
||||
<VizTooltipRow
|
||||
label={label}
|
||||
value={value}
|
||||
color={color}
|
||||
colorIndicator={colorIndicator}
|
||||
marginRight={'22px'}
|
||||
isPinned={isPinned}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { GraphSeriesValue } from '@grafana/data';
|
||||
|
||||
import { VizTooltipRow } from './VizTooltipRow';
|
||||
import { ColorIndicator } from './types';
|
||||
|
||||
export interface SeriesListProps {
|
||||
series: SingleSeriesProps[];
|
||||
}
|
||||
|
||||
// Based on SeriesTable, with new styling
|
||||
export const SeriesList = ({ series }: SeriesListProps) => {
|
||||
return (
|
||||
<>
|
||||
{series.map((series, index) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const label = series.label as string;
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const value = series.value as string;
|
||||
return (
|
||||
<VizTooltipRow
|
||||
key={`${series.label}-${index}`}
|
||||
label={label}
|
||||
value={value}
|
||||
color={series.color}
|
||||
colorIndicator={ColorIndicator.series}
|
||||
isActive={series.isActive}
|
||||
justify={'space-between'}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SingleSeriesProps {
|
||||
color?: string;
|
||||
label?: React.ReactNode;
|
||||
value?: string | GraphSeriesValue;
|
||||
isActive?: boolean;
|
||||
colorIndicator?: ColorIndicator;
|
||||
}
|
@ -11,9 +11,10 @@ import { LabelValue } from './types';
|
||||
interface Props {
|
||||
contentLabelValue: LabelValue[];
|
||||
customContent?: ReactElement[];
|
||||
isPinned: boolean;
|
||||
}
|
||||
|
||||
export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) => {
|
||||
export const VizTooltipContent = ({ contentLabelValue, customContent, isPinned }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
@ -31,6 +32,7 @@ export const VizTooltipContent = ({ contentLabelValue, customContent }: Props) =
|
||||
colorPlacement={colorPlacement}
|
||||
isActive={isActive}
|
||||
justify={'space-between'}
|
||||
isPinned={isPinned}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -13,14 +13,15 @@ interface Props {
|
||||
headerLabel: LabelValue;
|
||||
keyValuePairs?: LabelValue[];
|
||||
customValueDisplay?: ReactElement | null;
|
||||
isPinned: boolean;
|
||||
}
|
||||
export const VizTooltipHeader = ({ headerLabel, keyValuePairs, customValueDisplay }: Props) => {
|
||||
export const VizTooltipHeader = ({ headerLabel, keyValuePairs, customValueDisplay, isPinned }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<HeaderLabel headerLabel={headerLabel} />
|
||||
{customValueDisplay || <VizTooltipHeaderLabelValue keyValuePairs={keyValuePairs} />}
|
||||
<HeaderLabel headerLabel={headerLabel} isPinned={isPinned} />
|
||||
{customValueDisplay || <VizTooltipHeaderLabelValue keyValuePairs={keyValuePairs} isPinned={isPinned} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -5,9 +5,10 @@ import { LabelValue } from './types';
|
||||
|
||||
interface Props {
|
||||
keyValuePairs?: LabelValue[];
|
||||
isPinned: boolean;
|
||||
}
|
||||
|
||||
export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => (
|
||||
export const VizTooltipHeaderLabelValue = ({ keyValuePairs, isPinned }: Props) => (
|
||||
<>
|
||||
{keyValuePairs?.map((keyValuePair, i) => (
|
||||
<VizTooltipRow
|
||||
@ -17,6 +18,7 @@ export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => (
|
||||
color={keyValuePair.color}
|
||||
colorIndicator={keyValuePair.colorIndicator!}
|
||||
justify={'space-between'}
|
||||
isPinned={isPinned}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -13,6 +13,7 @@ interface Props extends LabelValue {
|
||||
justify?: string;
|
||||
isActive?: boolean; // for series list
|
||||
marginRight?: string;
|
||||
isPinned: boolean;
|
||||
}
|
||||
|
||||
export const VizTooltipRow = ({
|
||||
@ -24,6 +25,7 @@ export const VizTooltipRow = ({
|
||||
justify = 'flex-start',
|
||||
isActive = false,
|
||||
marginRight = '0px',
|
||||
isPinned,
|
||||
}: Props) => {
|
||||
const styles = useStyles2(getStyles, justify, marginRight);
|
||||
|
||||
@ -53,15 +55,19 @@ export const VizTooltipRow = ({
|
||||
{color && colorPlacement === ColorPlacement.first && (
|
||||
<VizTooltipColorIndicator color={color} colorIndicator={colorIndicator} />
|
||||
)}
|
||||
<Tooltip content={label} interactive={false} show={showLabelTooltip}>
|
||||
<div
|
||||
className={cx(styles.label, isActive && styles.activeSeries)}
|
||||
onMouseEnter={onMouseEnterLabel}
|
||||
onMouseLeave={onMouseLeaveLabel}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{!isPinned ? (
|
||||
<div className={cx(styles.label, isActive && styles.activeSeries)}>{label}</div>
|
||||
) : (
|
||||
<Tooltip content={label} interactive={false} show={showLabelTooltip}>
|
||||
<div
|
||||
className={cx(styles.label, isActive && styles.activeSeries)}
|
||||
onMouseEnter={onMouseEnterLabel}
|
||||
onMouseLeave={onMouseLeaveLabel}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -69,11 +75,20 @@ export const VizTooltipRow = ({
|
||||
{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>
|
||||
{!isPinned ? (
|
||||
<div className={cx(styles.value, isActive)}>{value}</div>
|
||||
) : (
|
||||
<Tooltip content={value ? value.toString() : ''} interactive={false} show={showValueTooltip}>
|
||||
<div
|
||||
className={cx(styles.value, isActive)}
|
||||
onMouseEnter={onMouseEnterValue}
|
||||
onMouseLeave={onMouseLeaveValue}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{color && colorPlacement === ColorPlacement.trailing && (
|
||||
<>
|
||||
|
||||
@ -86,14 +101,6 @@ export const VizTooltipRow = ({
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, justify: string, marginRight: string) => ({
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
gap: 4,
|
||||
borderTop: `1px solid ${theme.colors.border.medium}`,
|
||||
padding: theme.spacing(1),
|
||||
}),
|
||||
contentWrapper: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@ -101,9 +108,6 @@ const getStyles = (theme: GrafanaTheme2, justify: string, marginRight: string) =
|
||||
flexWrap: 'wrap',
|
||||
marginRight: marginRight,
|
||||
}),
|
||||
customContentPadding: css({
|
||||
padding: `${theme.spacing(1)} 0`,
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontWeight: 400,
|
||||
|
@ -382,8 +382,12 @@ const HeatmapHoverCell = ({
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabelValue()} customContent={getCustomContent()} />
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} isPinned={isPinned} />
|
||||
<VizTooltipContent
|
||||
contentLabelValue={getContentLabelValue()}
|
||||
customContent={getCustomContent()}
|
||||
isPinned={isPinned}
|
||||
/>
|
||||
{isPinned && <VizTooltipFooter dataLinks={links} canAnnotate={canAnnotate} />}
|
||||
</div>
|
||||
);
|
||||
|
@ -180,8 +180,8 @@ export const StateTimelineTooltip2 = ({
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabelValue()} />
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} isPinned={isPinned} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabelValue()} isPinned={isPinned} />
|
||||
{isPinned && <VizTooltipFooter dataLinks={links} canAnnotate={false} />}
|
||||
</div>
|
||||
);
|
||||
|
@ -142,8 +142,8 @@ export const StatusHistoryTooltip2 = ({
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabelValue()} />
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} isPinned={isPinned} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabelValue()} isPinned={isPinned} />
|
||||
{isPinned && <VizTooltipFooter dataLinks={links} canAnnotate={false} />}
|
||||
</div>
|
||||
);
|
||||
|
@ -133,7 +133,7 @@ export const TimeSeriesTooltip = ({
|
||||
|
||||
const getHeaderLabel = (): LabelValue => {
|
||||
return {
|
||||
label: '',
|
||||
label: xField.type === FieldType.time ? '' : getFieldDisplayName(xField, seriesFrame, frames),
|
||||
value: xVal,
|
||||
};
|
||||
};
|
||||
@ -145,8 +145,8 @@ export const TimeSeriesTooltip = ({
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.wrapper}>
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabelValue()} />
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} isPinned={isPinned} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabelValue()} isPinned={isPinned} />
|
||||
{isPinned && <VizTooltipFooter dataLinks={links} canAnnotate={false} />}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,9 +10,9 @@ import { preparePlotFrame } from 'app/core/components/GraphNG/utils';
|
||||
import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries';
|
||||
import { findFieldIndex } from 'app/features/dimensions';
|
||||
|
||||
import { TimeSeriesTooltip } from '../timeseries/TimeSeriesTooltip';
|
||||
import { prepareGraphableFields, regenerateLinksSupplier } from '../timeseries/utils';
|
||||
|
||||
import { TrendTooltip } from './TrendTooltip';
|
||||
import { Options } from './panelcfg.gen';
|
||||
|
||||
export const TrendPanel = ({
|
||||
@ -132,13 +132,13 @@ export const TrendPanel = ({
|
||||
}
|
||||
render={(u, dataIdxs, seriesIdx, isPinned = false) => {
|
||||
return (
|
||||
<TrendTooltip
|
||||
<TimeSeriesTooltip
|
||||
frames={info.frames!}
|
||||
data={alignedDataFrame}
|
||||
mode={options.tooltip.mode}
|
||||
sortOrder={options.tooltip.sort}
|
||||
seriesFrame={alignedDataFrame}
|
||||
dataIdxs={dataIdxs}
|
||||
seriesIdx={seriesIdx}
|
||||
mode={options.tooltip.mode}
|
||||
sortOrder={options.tooltip.sort}
|
||||
isPinned={isPinned}
|
||||
/>
|
||||
);
|
||||
|
@ -1,173 +0,0 @@
|
||||
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';
|
||||
import { DEFAULT_TOOLTIP_WIDTH } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
|
||||
|
||||
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,
|
||||
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: DEFAULT_TOOLTIP_WIDTH,
|
||||
}),
|
||||
});
|
@ -120,8 +120,8 @@ export const XYChartTooltip = ({ dataIdxs, seriesIdx, data, allSeries, dismiss,
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabel()} />
|
||||
<VizTooltipHeader headerLabel={getHeaderLabel()} isPinned={isPinned} />
|
||||
<VizTooltipContent contentLabelValue={getContentLabel()} isPinned={isPinned} />
|
||||
{isPinned && <VizTooltipFooter dataLinks={getLinks()} canAnnotate={false} />}
|
||||
</div>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user