diff --git a/public/app/plugins/panel/trend/TrendPanel.tsx b/public/app/plugins/panel/trend/TrendPanel.tsx
index 59aee22776d..fa3c6d355be 100644
--- a/public/app/plugins/panel/trend/TrendPanel.tsx
+++ b/public/app/plugins/panel/trend/TrendPanel.tsx
@@ -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,17 +121,42 @@ export const TrendPanel = ({
return (
<>
-
- {options.tooltip.mode === TooltipDisplayMode.None || (
-
+
+ {options.tooltip.mode !== TooltipDisplayMode.None && (
+ <>
+ {config.featureToggles.newVizTooltips ? (
+ {
+ return (
+
+ );
+ }}
+ />
+ ) : (
+
+ )}
+ >
)}
>
);
diff --git a/public/app/plugins/panel/trend/TrendTooltip.tsx b/public/app/plugins/panel/trend/TrendTooltip.tsx
new file mode 100644
index 00000000000..4da29cd4f06
--- /dev/null
+++ b/public/app/plugins/panel/trend/TrendTooltip.tsx
@@ -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;
+ // 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> = [];
+ const linkLookup = new Set();
+
+ // 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 = (
+
+ );
+ }
+
+ 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 = ;
+ }
+
+ const getHeaderLabel = (): LabelValue => {
+ return {
+ label: getFieldDisplayName(xField, data),
+ value: xVal,
+ };
+ };
+
+ return (
+
+ );
+};
+
+const getStyles = (theme: GrafanaTheme2) => ({
+ wrapper: css({
+ display: 'flex',
+ flexDirection: 'column',
+ width: '280px',
+ }),
+});