mirror of
https://github.com/grafana/grafana.git
synced 2024-12-25 16:31:28 -06:00
Tooltip: Add tooltip support to Histogram (#89196)
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
parent
f5468542ba
commit
c3b772618a
@ -341,7 +341,7 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
@ -436,7 +436,7 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
@ -523,7 +523,7 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 20,
|
||||
@ -608,7 +608,7 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 20,
|
||||
@ -693,7 +693,7 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 20,
|
||||
@ -778,7 +778,7 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 80,
|
||||
@ -845,7 +845,7 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 80,
|
||||
|
@ -28,7 +28,7 @@ export const VizTooltipContent = ({
|
||||
const scrollableStyle: CSSProperties = scrollable
|
||||
? {
|
||||
maxHeight: maxHeight,
|
||||
overflowY: 'scroll',
|
||||
overflowY: 'auto',
|
||||
}
|
||||
: {};
|
||||
|
||||
|
@ -47,7 +47,7 @@ export const VizTooltipRow = ({
|
||||
maxHeight: 55,
|
||||
whiteSpace: 'wrap',
|
||||
wordBreak: 'break-word',
|
||||
overflowY: 'scroll',
|
||||
overflowY: 'auto',
|
||||
}
|
||||
: {
|
||||
whiteSpace: 'wrap',
|
||||
|
@ -46,7 +46,7 @@ export interface HistogramProps extends Themeable2 {
|
||||
structureRev?: number; // a number that will change when the frames[] structure changes
|
||||
legend: VizLegendOptions;
|
||||
rawSeries?: DataFrame[];
|
||||
children?: (builder: UPlotConfigBuilder, frame: DataFrame) => React.ReactNode;
|
||||
children?: (builder: UPlotConfigBuilder, frame: DataFrame, xMinOnlyFrame: DataFrame) => React.ReactNode;
|
||||
}
|
||||
|
||||
export function getBucketSize(frame: DataFrame) {
|
||||
@ -283,6 +283,7 @@ interface State {
|
||||
alignedData: AlignedData;
|
||||
alignedFrame: DataFrame;
|
||||
config?: UPlotConfigBuilder;
|
||||
xMinOnlyFrame: DataFrame;
|
||||
}
|
||||
|
||||
export class Histogram extends React.Component<HistogramProps, State> {
|
||||
@ -295,12 +296,14 @@ export class Histogram extends React.Component<HistogramProps, State> {
|
||||
const { alignedFrame } = props;
|
||||
|
||||
const config = withConfig ? prepConfig(alignedFrame, this.props.theme) : this.state.config!;
|
||||
const alignedData = preparePlotData(config, xMinOnlyFrame(alignedFrame));
|
||||
const xMinOnly = xMinOnlyFrame(alignedFrame);
|
||||
const alignedData = preparePlotData(config, xMinOnly);
|
||||
|
||||
return {
|
||||
alignedFrame,
|
||||
alignedData,
|
||||
config,
|
||||
xMinOnlyFrame: xMinOnly,
|
||||
};
|
||||
}
|
||||
|
||||
@ -347,7 +350,7 @@ export class Histogram extends React.Component<HistogramProps, State> {
|
||||
<VizLayout width={width} height={height} legend={this.renderLegend(config)}>
|
||||
{(vizWidth: number, vizHeight: number) => (
|
||||
<UPlotChart config={this.state.config!} data={this.state.alignedData} width={vizWidth} height={vizHeight}>
|
||||
{children ? children(config, alignedFrame) : null}
|
||||
{children ? children(config, alignedFrame, this.state.xMinOnlyFrame) : null}
|
||||
</UPlotChart>
|
||||
)}
|
||||
</VizLayout>
|
||||
|
@ -2,9 +2,11 @@ import React, { useMemo } from 'react';
|
||||
|
||||
import { PanelProps, buildHistogram, getHistogramFields } from '@grafana/data';
|
||||
import { histogramFieldsToFrame } from '@grafana/data/src/transformations/transformers/histogram';
|
||||
import { useTheme2 } from '@grafana/ui';
|
||||
import { TooltipDisplayMode, TooltipPlugin2, useTheme2 } from '@grafana/ui';
|
||||
import { TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
|
||||
|
||||
import { Histogram, getBucketSize } from './Histogram';
|
||||
import { HistogramTooltip } from './HistogramTooltip';
|
||||
import { Options } from './panelcfg.gen';
|
||||
|
||||
type Props = PanelProps<Options>;
|
||||
@ -67,8 +69,34 @@ export const HistogramPanel = ({ data, options, width, height }: Props) => {
|
||||
bucketSize={bucketSize}
|
||||
bucketCount={options.bucketCount}
|
||||
>
|
||||
{(config, alignedFrame) => {
|
||||
return null; // <TooltipPlugin data={alignedFrame} config={config} mode={options.tooltip.mode} timeZone={timeZone} />;
|
||||
{(builder, alignedFrame, xMinOnlyFrame) => {
|
||||
return (
|
||||
<>
|
||||
{options.tooltip.mode !== TooltipDisplayMode.None && (
|
||||
<TooltipPlugin2
|
||||
config={builder}
|
||||
hoverMode={
|
||||
options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll
|
||||
}
|
||||
render={(u, dataIdxs, seriesIdx, isPinned = false) => {
|
||||
return (
|
||||
<HistogramTooltip
|
||||
series={histogram}
|
||||
xMinOnlyFrame={xMinOnlyFrame}
|
||||
dataIdxs={dataIdxs}
|
||||
seriesIdx={seriesIdx}
|
||||
mode={options.tooltip.mode}
|
||||
sortOrder={options.tooltip.sort}
|
||||
isPinned={isPinned}
|
||||
maxHeight={options.tooltip.maxHeight}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
maxWidth={options.tooltip.maxWidth}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Histogram>
|
||||
);
|
||||
|
95
public/app/plugins/panel/histogram/HistogramTooltip.tsx
Normal file
95
public/app/plugins/panel/histogram/HistogramTooltip.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { ReactNode, useMemo } from 'react';
|
||||
|
||||
import { DataFrame, formattedValueToString } from '@grafana/data';
|
||||
import { SortOrder, TooltipDisplayMode } from '@grafana/schema/dist/esm/common/common.gen';
|
||||
import { useStyles2 } 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 { VizTooltipItem } from '@grafana/ui/src/components/VizTooltip/types';
|
||||
import { getContentItems } from '@grafana/ui/src/components/VizTooltip/utils';
|
||||
|
||||
import { getDataLinks } from '../status-history/utils';
|
||||
import { isTooltipScrollable } from '../timeseries/utils';
|
||||
|
||||
export interface HistogramTooltipProps {
|
||||
// aligned series frame
|
||||
series: DataFrame;
|
||||
xMinOnlyFrame: DataFrame;
|
||||
|
||||
// hovered points
|
||||
dataIdxs: Array<number | null>;
|
||||
// closest/hovered series
|
||||
seriesIdx?: number | null;
|
||||
mode?: TooltipDisplayMode;
|
||||
sortOrder?: SortOrder;
|
||||
|
||||
isPinned: boolean;
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
export const HistogramTooltip = ({
|
||||
series,
|
||||
xMinOnlyFrame,
|
||||
dataIdxs,
|
||||
seriesIdx,
|
||||
mode = TooltipDisplayMode.Single,
|
||||
sortOrder = SortOrder.None,
|
||||
isPinned,
|
||||
maxHeight,
|
||||
}: HistogramTooltipProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const xMinField = series.fields[0];
|
||||
const xMaxField = series.fields[1];
|
||||
|
||||
// use the formatter from other bucket bound if none is defined
|
||||
const { display: xMinDisp } = xMinField.config.unit != null ? xMinField : xMaxField;
|
||||
const { display: xMaxDisp } = xMaxField.config.unit != null ? xMaxField : xMinField;
|
||||
|
||||
const xMinVal = formattedValueToString(xMinDisp!(xMinField.values[dataIdxs[0]!]));
|
||||
const xMaxVal = formattedValueToString(xMaxDisp!(xMaxField.values[dataIdxs[1]!]));
|
||||
|
||||
const headerItem: VizTooltipItem | null = xMinField.config.custom?.hideFrom?.tooltip
|
||||
? null
|
||||
: {
|
||||
label: 'Bucket',
|
||||
value: `${xMinVal} - ${xMaxVal}`,
|
||||
};
|
||||
|
||||
const contentItems = useMemo(
|
||||
() => getContentItems(xMinOnlyFrame.fields, xMinField, dataIdxs, seriesIdx, mode, sortOrder),
|
||||
[xMinOnlyFrame.fields, xMinField, dataIdxs, seriesIdx, mode, sortOrder]
|
||||
);
|
||||
|
||||
let footer: ReactNode;
|
||||
|
||||
if (isPinned && seriesIdx != null) {
|
||||
const field = series.fields[seriesIdx];
|
||||
const dataIdx = dataIdxs[seriesIdx]!;
|
||||
const links = getDataLinks(field, dataIdx);
|
||||
|
||||
footer = <VizTooltipFooter dataLinks={links} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{headerItem != null && <VizTooltipHeader item={headerItem} isPinned={isPinned} />}
|
||||
<VizTooltipContent
|
||||
items={contentItems}
|
||||
isPinned={isPinned}
|
||||
scrollable={isTooltipScrollable({ mode, maxHeight })}
|
||||
maxHeight={maxHeight}
|
||||
/>
|
||||
{footer}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStyles = () => ({
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}),
|
||||
});
|
@ -66,7 +66,7 @@ export const plugin = new PanelPlugin<Options, FieldConfig>(HistogramPanel)
|
||||
showIf: (opts, data) => !originalDataHasHistogram(data),
|
||||
});
|
||||
|
||||
// commonOptionsBuilder.addTooltipOptions(builder);
|
||||
commonOptionsBuilder.addTooltipOptions(builder);
|
||||
commonOptionsBuilder.addLegendOptions(builder);
|
||||
})
|
||||
.useFieldConfig({
|
||||
|
Loading…
Reference in New Issue
Block a user