mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Timeline/Status grid panel: Add tooltip support (#35005)
* Timeline/Status grid tooltip support first pass * Tooltips workin * Use getValueFormat to get the duration * Separate boxes highlight from tooltip interpolation * Separate state timeline tooltip component, rely on field display color to retrieve color of series * create an onHover/onLeave API and optimize implementation Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
import uPlot, { Axis, Series } from 'uplot';
|
||||
import { pointWithin, Quadtree, Rect } from './quadtree';
|
||||
import { distribute, SPACE_BETWEEN } from './distribute';
|
||||
import { TooltipInterpolator } from '@grafana/ui/src/components/uPlot/types';
|
||||
import { BarValueVisibility, ScaleDirection, ScaleOrientation } from '@grafana/ui/src/components/uPlot/config';
|
||||
import { CartesianCoords2D, GrafanaTheme2 } from '@grafana/data';
|
||||
import { calculateFontSize, measureText } from '@grafana/ui';
|
||||
import { VizTextDisplayOptions } from '@grafana/ui/src/options/builder';
|
||||
import { calculateFontSize, measureText, PlotTooltipInterpolator, VizTextDisplayOptions } from '@grafana/ui';
|
||||
|
||||
const groupDistr = SPACE_BETWEEN;
|
||||
const barDistr = SPACE_BETWEEN;
|
||||
@@ -311,7 +309,7 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
|
||||
};
|
||||
|
||||
// handle hover interaction with quadtree probing
|
||||
const interpolateBarChartTooltip: TooltipInterpolator = (
|
||||
const interpolateTooltip: PlotTooltipInterpolator = (
|
||||
updateActiveSeriesIdx,
|
||||
updateActiveDatapointIdx,
|
||||
updateTooltipPosition
|
||||
@@ -368,7 +366,7 @@ export function getConfig(opts: BarsOptions, theme: GrafanaTheme2) {
|
||||
// hooks
|
||||
init,
|
||||
drawClear,
|
||||
interpolateBarChartTooltip,
|
||||
interpolateTooltip,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptions> = ({
|
||||
builder.addHook('drawClear', config.drawClear);
|
||||
builder.addHook('draw', config.draw);
|
||||
|
||||
builder.setTooltipInterpolator(config.interpolateBarChartTooltip);
|
||||
builder.setTooltipInterpolator(config.interpolateTooltip);
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { PanelProps } from '@grafana/data';
|
||||
import { useTheme2, ZoomPlugin } from '@grafana/ui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { DataFrame, PanelProps } from '@grafana/data';
|
||||
import { TooltipPlugin, useTheme2, ZoomPlugin } from '@grafana/ui';
|
||||
import { TimelineMode, TimelineOptions } from './types';
|
||||
import { TimelineChart } from './TimelineChart';
|
||||
import { prepareTimelineFields, prepareTimelineLegendItems } from './utils';
|
||||
import { StateTimelineTooltip } from './StateTimelineTooltip';
|
||||
|
||||
interface TimelinePanelProps extends PanelProps<TimelineOptions> {}
|
||||
|
||||
@@ -32,6 +33,26 @@ export const StateTimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
theme,
|
||||
]);
|
||||
|
||||
const renderCustomTooltip = useCallback(
|
||||
(alignedData: DataFrame, seriesIdx: number | null, datapointIdx: number | null) => {
|
||||
// Not caring about multi mode in StateTimeline
|
||||
if (seriesIdx === null || datapointIdx === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StateTimelineTooltip
|
||||
data={data.series}
|
||||
alignedData={alignedData}
|
||||
seriesIdx={seriesIdx}
|
||||
datapointIdx={datapointIdx}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[timeZone, data]
|
||||
);
|
||||
|
||||
if (!frames || warn) {
|
||||
return (
|
||||
<div className="panel-empty">
|
||||
@@ -51,10 +72,22 @@ export const StateTimelinePanel: React.FC<TimelinePanelProps> = ({
|
||||
height={height}
|
||||
legendItems={legendItems}
|
||||
{...options}
|
||||
// hardcoded
|
||||
mode={TimelineMode.Changes}
|
||||
>
|
||||
{(config) => <ZoomPlugin config={config} onZoom={onChangeTimeRange} />}
|
||||
{(config, alignedFrame) => {
|
||||
return (
|
||||
<>
|
||||
<ZoomPlugin config={config} onZoom={onChangeTimeRange} />
|
||||
<TooltipPlugin
|
||||
data={alignedFrame}
|
||||
config={config}
|
||||
mode={options.tooltip.mode}
|
||||
timeZone={timeZone}
|
||||
renderTooltip={renderCustomTooltip}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TimelineChart>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
DataFrame,
|
||||
FALLBACK_COLOR,
|
||||
formattedValueToString,
|
||||
getDisplayProcessor,
|
||||
getFieldDisplayName,
|
||||
getValueFormat,
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { SeriesTableRow, useTheme2 } from '@grafana/ui';
|
||||
import { findNextStateIndex } from './utils';
|
||||
|
||||
interface StateTimelineTooltipProps {
|
||||
data: DataFrame[];
|
||||
alignedData: DataFrame;
|
||||
seriesIdx: number;
|
||||
datapointIdx: number;
|
||||
timeZone: TimeZone;
|
||||
}
|
||||
|
||||
export const StateTimelineTooltip: React.FC<StateTimelineTooltipProps> = ({
|
||||
data,
|
||||
alignedData,
|
||||
seriesIdx,
|
||||
datapointIdx,
|
||||
timeZone,
|
||||
}) => {
|
||||
const theme = useTheme2();
|
||||
|
||||
const xField = alignedData.fields[0];
|
||||
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, timeZone, theme });
|
||||
|
||||
const field = alignedData.fields[seriesIdx!];
|
||||
const dataFrameFieldIndex = field.state?.origin;
|
||||
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone, theme });
|
||||
const value = field.values.get(datapointIdx!);
|
||||
const display = fieldFmt(value);
|
||||
const fieldDisplayName = dataFrameFieldIndex
|
||||
? getFieldDisplayName(
|
||||
data[dataFrameFieldIndex.frameIndex].fields[dataFrameFieldIndex.fieldIndex],
|
||||
data[dataFrameFieldIndex.frameIndex],
|
||||
data
|
||||
)
|
||||
: null;
|
||||
|
||||
const nextStateIdx = findNextStateIndex(field, datapointIdx!);
|
||||
let nextStateTs;
|
||||
if (nextStateIdx) {
|
||||
nextStateTs = xField.values.get(nextStateIdx!);
|
||||
}
|
||||
|
||||
const stateTs = xField.values.get(datapointIdx!);
|
||||
|
||||
let toFragment = null;
|
||||
let durationFragment = null;
|
||||
|
||||
if (nextStateTs) {
|
||||
const duration = nextStateTs && formattedValueToString(getValueFormat('dtdurationms')(nextStateTs - stateTs, 0));
|
||||
durationFragment = (
|
||||
<>
|
||||
<br />
|
||||
<strong>Duration:</strong> {duration}
|
||||
</>
|
||||
);
|
||||
toFragment = (
|
||||
<>
|
||||
{' to'} <strong>{xFieldFmt(xField.values.get(nextStateIdx!)).text}</strong>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ fontSize: theme.typography.bodySmall.fontSize }}>
|
||||
{fieldDisplayName}
|
||||
<br />
|
||||
<SeriesTableRow label={display.text} color={display.color || FALLBACK_COLOR} isActive />
|
||||
From <strong>{xFieldFmt(xField.values.get(datapointIdx!)).text}</strong>
|
||||
{toFragment}
|
||||
{durationFragment}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StateTimelineTooltip.displayName = 'StateTimelineTooltip';
|
||||
@@ -13,12 +13,14 @@ import {
|
||||
} from '@grafana/ui';
|
||||
import { DataFrame, FieldType, TimeRange } from '@grafana/data';
|
||||
import { preparePlotConfigBuilder } from './utils';
|
||||
import { TimelineMode, TimelineValueAlignment } from './types';
|
||||
import { TimelineMode, TimelineOptions, TimelineValueAlignment } from './types';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface TimelineProps extends Omit<GraphNGProps, 'prepConfig' | 'propsToDiff' | 'renderLegend'> {
|
||||
export interface TimelineProps
|
||||
extends TimelineOptions,
|
||||
Omit<GraphNGProps, 'prepConfig' | 'propsToDiff' | 'renderLegend'> {
|
||||
mode: TimelineMode;
|
||||
rowHeight: number;
|
||||
showValue: BarValueVisibility;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { StateTimelinePanel } from './StateTimelinePanel';
|
||||
import { TimelineOptions, TimelineFieldConfig, defaultPanelOptions, defaultTimelineFieldConfig } from './types';
|
||||
import { BarValueVisibility } from '@grafana/ui';
|
||||
import { addLegendOptions } from '@grafana/ui/src/options/builder';
|
||||
import { BarValueVisibility, commonOptionsBuilder } from '@grafana/ui';
|
||||
import { timelinePanelChangedHandler } from './migrations';
|
||||
|
||||
export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(StateTimelinePanel)
|
||||
@@ -84,5 +83,6 @@ export const plugin = new PanelPlugin<TimelineOptions, TimelineFieldConfig>(Stat
|
||||
defaultValue: defaultPanelOptions.rowHeight,
|
||||
});
|
||||
|
||||
addLegendOptions(builder, false);
|
||||
commonOptionsBuilder.addLegendOptions(builder, false);
|
||||
commonOptionsBuilder.addTooltipOptions(builder, true);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import uPlot, { Series, Cursor } from 'uplot';
|
||||
import uPlot, { Cursor, Series } from 'uplot';
|
||||
import { FIXED_UNIT } from '@grafana/ui/src/components/GraphNG/GraphNG';
|
||||
import { Quadtree, Rect, pointWithin } from 'app/plugins/panel/barchart/quadtree';
|
||||
import { pointWithin, Quadtree, Rect } from 'app/plugins/panel/barchart/quadtree';
|
||||
import { distribute, SPACE_BETWEEN } from 'app/plugins/panel/barchart/distribute';
|
||||
import { TimelineFieldConfig, TimelineMode, TimelineValueAlignment } from './types';
|
||||
import { GrafanaTheme2, TimeRange } from '@grafana/data';
|
||||
@@ -47,8 +47,8 @@ export interface TimelineCoreOptions {
|
||||
getTimeRange: () => TimeRange;
|
||||
formatValue?: (seriesIdx: number, value: any) => string;
|
||||
getFieldConfig: (seriesIdx: number) => TimelineFieldConfig;
|
||||
onHover?: (seriesIdx: number, valueIdx: number) => void;
|
||||
onLeave?: (seriesIdx: number, valueIdx: number) => void;
|
||||
onHover?: (seriesIdx: number, valueIdx: number, rect: Rect) => void;
|
||||
onLeave?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,8 +69,8 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
getTimeRange,
|
||||
getValueColor,
|
||||
getFieldConfig,
|
||||
// onHover,
|
||||
// onLeave,
|
||||
onHover,
|
||||
onLeave,
|
||||
} = opts;
|
||||
|
||||
let qt: Quadtree;
|
||||
@@ -382,16 +382,24 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
hovered[i] = o;
|
||||
}
|
||||
|
||||
let hoveredAtCursor: Rect | null = null;
|
||||
|
||||
function hoverMulti(cx: number, cy: number) {
|
||||
let foundAtCursor: Rect | null = null;
|
||||
|
||||
for (let i = 0; i < numSeries; i++) {
|
||||
let found: Rect | null = null;
|
||||
|
||||
if (cx >= 0) {
|
||||
cy = yMids[i];
|
||||
let cy2 = yMids[i];
|
||||
|
||||
qt.get(cx, cy, 1, 1, (o) => {
|
||||
if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) {
|
||||
qt.get(cx, cy2, 1, 1, (o) => {
|
||||
if (pointWithin(cx, cy2, o.x, o.y, o.x + o.w, o.y + o.h)) {
|
||||
found = o;
|
||||
|
||||
if (Math.abs(cy - cy2) <= o.h / 2) {
|
||||
foundAtCursor = o;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -404,21 +412,40 @@ export function getConfig(opts: TimelineCoreOptions) {
|
||||
setHoverMark(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundAtCursor) {
|
||||
if (foundAtCursor !== hoveredAtCursor) {
|
||||
hoveredAtCursor = foundAtCursor;
|
||||
// @ts-ignore
|
||||
onHover && onHover(foundAtCursor.sidx, foundAtCursor.didx, foundAtCursor);
|
||||
}
|
||||
} else if (hoveredAtCursor) {
|
||||
hoveredAtCursor = null;
|
||||
onLeave && onLeave();
|
||||
}
|
||||
}
|
||||
|
||||
function hoverOne(cx: number, cy: number) {
|
||||
let found: Rect | null = null;
|
||||
let foundAtCursor: Rect | null = null;
|
||||
|
||||
qt.get(cx, cy, 1, 1, (o) => {
|
||||
if (pointWithin(cx, cy, o.x, o.y, o.x + o.w, o.y + o.h)) {
|
||||
found = o;
|
||||
foundAtCursor = o;
|
||||
}
|
||||
});
|
||||
|
||||
if (found) {
|
||||
setHoverMark(0, found);
|
||||
} else if (hovered[0] != null) {
|
||||
if (foundAtCursor) {
|
||||
setHoverMark(0, foundAtCursor);
|
||||
|
||||
if (foundAtCursor !== hoveredAtCursor) {
|
||||
hoveredAtCursor = foundAtCursor;
|
||||
// @ts-ignore
|
||||
onHover && onHover(foundAtCursor.sidx, foundAtCursor.didx, foundAtCursor);
|
||||
}
|
||||
} else if (hoveredAtCursor) {
|
||||
setHoverMark(0, null);
|
||||
hoveredAtCursor = null;
|
||||
onLeave && onLeave();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { HideableFieldConfig, BarValueVisibility, OptionsWithLegend } from '@grafana/ui';
|
||||
import { HideableFieldConfig, BarValueVisibility, OptionsWithLegend, OptionsWithTooltip } from '@grafana/ui';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface TimelineOptions extends OptionsWithLegend {
|
||||
export interface TimelineOptions extends OptionsWithLegend, OptionsWithTooltip {
|
||||
mode: TimelineMode; // not in the saved model!
|
||||
|
||||
showValue: BarValueVisibility;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FieldType, toDataFrame } from '@grafana/data';
|
||||
import { prepareTimelineFields } from './utils';
|
||||
import { ArrayVector, FieldType, toDataFrame } from '@grafana/data';
|
||||
import { findNextStateIndex, prepareTimelineFields } from './utils';
|
||||
|
||||
describe('prepare timeline graph', () => {
|
||||
it('errors with no time fields', () => {
|
||||
@@ -58,3 +58,79 @@ describe('prepare timeline graph', () => {
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findNextStateIndex', () => {
|
||||
it('handles leading datapoint index', () => {
|
||||
const field = {
|
||||
name: 'time',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector([1, undefined, undefined, 2, undefined, undefined]),
|
||||
} as any;
|
||||
const result = findNextStateIndex(field, 0);
|
||||
expect(result).toEqual(3);
|
||||
});
|
||||
|
||||
it('handles trailing datapoint index', () => {
|
||||
const field = {
|
||||
name: 'time',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector([1, undefined, undefined, 2, undefined, 3]),
|
||||
} as any;
|
||||
const result = findNextStateIndex(field, 5);
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it('handles trailing undefined', () => {
|
||||
const field = {
|
||||
name: 'time',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector([1, undefined, undefined, 2, undefined, 3, undefined]),
|
||||
} as any;
|
||||
const result = findNextStateIndex(field, 5);
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
it('handles datapoint index inside range', () => {
|
||||
const field = {
|
||||
name: 'time',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector([
|
||||
1,
|
||||
undefined,
|
||||
undefined,
|
||||
3,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
2,
|
||||
undefined,
|
||||
undefined,
|
||||
]),
|
||||
} as any;
|
||||
const result = findNextStateIndex(field, 3);
|
||||
expect(result).toEqual(8);
|
||||
});
|
||||
|
||||
describe('single data points', () => {
|
||||
const field = {
|
||||
name: 'time',
|
||||
type: FieldType.number,
|
||||
values: new ArrayVector([1, 3, 2]),
|
||||
} as any;
|
||||
|
||||
test('leading', () => {
|
||||
const result = findNextStateIndex(field, 0);
|
||||
expect(result).toEqual(1);
|
||||
});
|
||||
test('trailing', () => {
|
||||
const result = findNextStateIndex(field, 2);
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
test('inside', () => {
|
||||
const result = findNextStateIndex(field, 1);
|
||||
expect(result).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { TimelineCoreOptions, getConfig } from './timeline';
|
||||
import { AxisPlacement, ScaleDirection, ScaleOrientation } from '@grafana/ui/src/components/uPlot/config';
|
||||
import { TimelineFieldConfig, TimelineOptions } from './types';
|
||||
import { PlotTooltipInterpolator } from '@grafana/ui/src/components/uPlot/types';
|
||||
|
||||
const defaultConfig: TimelineFieldConfig = {
|
||||
lineWidth: 0,
|
||||
@@ -95,21 +96,46 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<TimelineOptions> = ({
|
||||
getTimeRange,
|
||||
// hardcoded formatter for state values
|
||||
formatValue: (seriesIdx, value) => formattedValueToString(frame.fields[seriesIdx].display!(value)),
|
||||
// TODO: unimplemeted for now
|
||||
onHover: (seriesIdx: number, valueIdx: number) => {
|
||||
console.log('hover', { seriesIdx, valueIdx });
|
||||
onHover: (seriesIndex, valueIndex) => {
|
||||
hoveredSeriesIdx = seriesIndex;
|
||||
hoveredDataIdx = valueIndex;
|
||||
},
|
||||
onLeave: (seriesIdx: number, valueIdx: number) => {
|
||||
console.log('leave', { seriesIdx, valueIdx });
|
||||
onLeave: () => {
|
||||
hoveredSeriesIdx = null;
|
||||
hoveredDataIdx = null;
|
||||
},
|
||||
};
|
||||
|
||||
let hoveredSeriesIdx: number | null = null;
|
||||
let hoveredDataIdx: number | null = null;
|
||||
|
||||
const coreConfig = getConfig(opts);
|
||||
|
||||
builder.addHook('init', coreConfig.init);
|
||||
builder.addHook('drawClear', coreConfig.drawClear);
|
||||
builder.addHook('setCursor', coreConfig.setCursor);
|
||||
|
||||
// in TooltipPlugin, this gets invoked and the result is bound to a setCursor hook
|
||||
// which fires after the above setCursor hook, so can take advantage of hoveringOver
|
||||
// already set by the above onHover/onLeave callbacks that fire from coreConfig.setCursor
|
||||
const interpolateTooltip: PlotTooltipInterpolator = (
|
||||
updateActiveSeriesIdx,
|
||||
updateActiveDatapointIdx,
|
||||
updateTooltipPosition
|
||||
) => (u: uPlot) => {
|
||||
if (hoveredSeriesIdx != null) {
|
||||
// @ts-ignore
|
||||
updateActiveSeriesIdx(hoveredSeriesIdx);
|
||||
// @ts-ignore
|
||||
updateActiveDatapointIdx(hoveredDataIdx);
|
||||
updateTooltipPosition();
|
||||
} else {
|
||||
updateTooltipPosition(true);
|
||||
}
|
||||
};
|
||||
|
||||
builder.setTooltipInterpolator(interpolateTooltip);
|
||||
|
||||
builder.setCursor(coreConfig.cursor);
|
||||
|
||||
builder.addScale({
|
||||
@@ -366,3 +392,27 @@ function allNonTimeFields(frames: DataFrame[]): Field[] {
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
export function findNextStateIndex(field: Field, datapointIdx: number) {
|
||||
let end;
|
||||
let rightPointer = datapointIdx + 1;
|
||||
|
||||
if (rightPointer === field.values.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (end === undefined) {
|
||||
if (rightPointer === field.values.length) {
|
||||
return null;
|
||||
}
|
||||
const rightValue = field.values.get(rightPointer);
|
||||
|
||||
if (rightValue !== undefined) {
|
||||
end = rightPointer;
|
||||
} else {
|
||||
rightPointer++;
|
||||
}
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { PanelProps } from '@grafana/data';
|
||||
import { useTheme2, ZoomPlugin } from '@grafana/ui';
|
||||
import { TooltipPlugin, useTheme2, ZoomPlugin } from '@grafana/ui';
|
||||
import { StatusPanelOptions } from './types';
|
||||
import { TimelineChart } from '../state-timeline/TimelineChart';
|
||||
import { TimelineMode } from '../state-timeline/types';
|
||||
@@ -64,7 +64,14 @@ export const StatusHistoryPanel: React.FC<TimelinePanelProps> = ({
|
||||
// hardcoded
|
||||
mode={TimelineMode.Samples}
|
||||
>
|
||||
{(config) => <ZoomPlugin config={config} onZoom={onChangeTimeRange} />}
|
||||
{(config, alignedFrame) => {
|
||||
return (
|
||||
<>
|
||||
<ZoomPlugin config={config} onZoom={onChangeTimeRange} />
|
||||
<TooltipPlugin data={alignedFrame} config={config} mode={options.tooltip.mode} timeZone={timeZone} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TimelineChart>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { FieldColorModeId, FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { StatusHistoryPanel } from './StatusHistoryPanel';
|
||||
import { StatusPanelOptions, StatusFieldConfig, defaultStatusFieldConfig } from './types';
|
||||
import { BarValueVisibility } from '@grafana/ui';
|
||||
import { addLegendOptions } from '@grafana/ui/src/options/builder';
|
||||
import { BarValueVisibility, commonOptionsBuilder } from '@grafana/ui';
|
||||
|
||||
export const plugin = new PanelPlugin<StatusPanelOptions, StatusFieldConfig>(StatusHistoryPanel)
|
||||
.useFieldConfig({
|
||||
@@ -75,5 +74,6 @@ export const plugin = new PanelPlugin<StatusPanelOptions, StatusFieldConfig>(Sta
|
||||
},
|
||||
});
|
||||
|
||||
addLegendOptions(builder, false);
|
||||
commonOptionsBuilder.addLegendOptions(builder, false);
|
||||
commonOptionsBuilder.addTooltipOptions(builder, true);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { VizLegendOptions, HideableFieldConfig, BarValueVisibility } from '@grafana/ui';
|
||||
import { HideableFieldConfig, BarValueVisibility, OptionsWithTooltip, OptionsWithLegend } from '@grafana/ui';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export interface StatusPanelOptions {
|
||||
legend: VizLegendOptions;
|
||||
export interface StatusPanelOptions extends OptionsWithTooltip, OptionsWithLegend {
|
||||
showValue: BarValueVisibility;
|
||||
rowHeight: number;
|
||||
colWidth?: number;
|
||||
|
||||
Reference in New Issue
Block a user