points 그래프를 위한 데이터 준비

This commit is contained in:
devhong 2025-04-01 18:12:40 +09:00
parent 0f6332c6c2
commit f67359ba35
3 changed files with 203 additions and 107 deletions

View File

@ -1,21 +1,14 @@
import { useMemo, useState } from 'react'; import { useMemo } from 'react';
import { PanelProps, DataFrameType, DashboardCursorSync } from '@grafana/data'; import { PanelProps, DataFrameType, DashboardCursorSync } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime'; import { PanelDataErrorView } from '@grafana/runtime';
import { TooltipDisplayMode, VizOrientation } from '@grafana/schema'; import { DrawStyle, usePanelContext } from '@grafana/ui';
import { EventBusPlugin, KeyboardPlugin, TooltipPlugin2, usePanelContext } from '@grafana/ui';
import { TimeRange2, TooltipHoverMode } from '@grafana/ui/src/components/uPlot/plugins/TooltipPlugin2';
import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries'; import { TimeSeries } from 'app/core/components/TimeSeries/TimeSeries';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { TimeSeriesTooltip } from './TimeSeriesTooltip';
import { Options } from './panelcfg.gen'; import { Options } from './panelcfg.gen';
import { AnnotationsPlugin2 } from './plugins/AnnotationsPlugin2';
import { ExemplarsPlugin, getVisibleLabels } from './plugins/ExemplarsPlugin';
import { OutsideRangePlugin } from './plugins/OutsideRangePlugin';
import { ThresholdControlsPlugin } from './plugins/ThresholdControlsPlugin';
import { getPrepareTimeseriesSuggestion } from './suggestions'; import { getPrepareTimeseriesSuggestion } from './suggestions';
import { getTimezones, prepareGraphableFields } from './utils'; import { getTimezones, prepareGraphableFields, preparePlotFramePoints } from './utils';
interface TimeSeriesPanelProps extends PanelProps<Options> {} interface TimeSeriesPanelProps extends PanelProps<Options> {}
@ -27,23 +20,19 @@ export const TimeSeriesPanel = ({
height, height,
options, options,
fieldConfig, fieldConfig,
onChangeTimeRange,
replaceVariables, replaceVariables,
id, id,
}: TimeSeriesPanelProps) => { }: TimeSeriesPanelProps) => {
const { const { sync, dataLinkPostProcessor } = usePanelContext();
sync,
eventsScope, const preparePlotFrame =
canAddAnnotations, fieldConfig.defaults.custom.drawStyle === DrawStyle.Points ? preparePlotFramePoints : undefined;
onThresholdsChange, if (fieldConfig.defaults.custom.drawStyle === DrawStyle.Points) {
canEditThresholds, console.log('points');
showThresholds, }
dataLinkPostProcessor,
eventBus,
} = usePanelContext();
// Vertical orientation is not available for users through config. // Vertical orientation is not available for users through config.
// It is simplified version of horizontal time series panel and it does not support all plugins. // It is simplified version of horizontal time series panel and it does not support all plugins.
const isVerticallyOriented = options.orientation === VizOrientation.Vertical;
const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2, timeRange), [data.series, timeRange]); const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2, timeRange), [data.series, timeRange]);
const timezones = useMemo(() => getTimezones(options.timezone, timeZone), [options.timezone, timeZone]); const timezones = useMemo(() => getTimezones(options.timezone, timeZone), [options.timezone, timeZone]);
const suggestions = useMemo(() => { const suggestions = useMemo(() => {
@ -57,8 +46,6 @@ export const TimeSeriesPanel = ({
return undefined; return undefined;
}, [frames, id]); }, [frames, id]);
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
const cursorSync = sync?.() ?? DashboardCursorSync.Off; const cursorSync = sync?.() ?? DashboardCursorSync.Off;
if (!frames || suggestions) { if (!frames || suggestions) {
@ -88,91 +75,10 @@ export const TimeSeriesPanel = ({
replaceVariables={replaceVariables} replaceVariables={replaceVariables}
dataLinkPostProcessor={dataLinkPostProcessor} dataLinkPostProcessor={dataLinkPostProcessor}
cursorSync={cursorSync} cursorSync={cursorSync}
preparePlotFrame={preparePlotFrame}
> >
{(uplotConfig, alignedFrame) => { {(uplotConfig, alignedFrame) => {
return ( return <></>;
<>
<KeyboardPlugin config={uplotConfig} />
{cursorSync !== DashboardCursorSync.Off && (
<EventBusPlugin config={uplotConfig} eventBus={eventBus} frame={alignedFrame} />
)}
{options.tooltip.mode !== TooltipDisplayMode.None && (
<TooltipPlugin2
config={uplotConfig}
hoverMode={
options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll
}
queryZoom={onChangeTimeRange}
clientZoom={true}
syncMode={cursorSync}
syncScope={eventsScope}
getDataLinks={(seriesIdx, dataIdx) =>
alignedFrame.fields[seriesIdx].getLinks?.({ valueRowIndex: dataIdx }) ?? []
}
render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange2, viaSync, dataLinks) => {
if (enableAnnotationCreation && timeRange2 != null) {
setNewAnnotationRange(timeRange2);
dismiss();
return;
}
const annotate = () => {
let xVal = u.posToVal(u.cursor.left!, 'x');
setNewAnnotationRange({ from: xVal, to: xVal });
dismiss();
};
return (
// not sure it header time here works for annotations, since it's taken from nearest datapoint index
<TimeSeriesTooltip
series={alignedFrame}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}
mode={viaSync ? TooltipDisplayMode.Multi : options.tooltip.mode}
sortOrder={options.tooltip.sort}
hideZeros={options.tooltip.hideZeros}
isPinned={isPinned}
annotate={enableAnnotationCreation ? annotate : undefined}
maxHeight={options.tooltip.maxHeight}
replaceVariables={replaceVariables}
dataLinks={dataLinks}
/>
);
}}
maxWidth={options.tooltip.maxWidth}
/>
)}
{!isVerticallyOriented && (
<>
<AnnotationsPlugin2
annotations={data.annotations ?? []}
config={uplotConfig}
timeZone={timeZone}
newRange={newAnnotationRange}
setNewRange={setNewAnnotationRange}
/>
<OutsideRangePlugin config={uplotConfig} onChangeTimeRange={onChangeTimeRange} />
{data.annotations && (
<ExemplarsPlugin
visibleSeries={getVisibleLabels(uplotConfig, frames)}
config={uplotConfig}
exemplars={data.annotations}
timeZone={timeZone}
maxHeight={options.tooltip.maxHeight}
/>
)}
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
<ThresholdControlsPlugin
config={uplotConfig}
fieldConfig={fieldConfig}
onThresholdsChange={canEditThresholds ? onThresholdsChange : undefined}
/>
)}
</>
)}
</>
);
}} }}
</TimeSeries> </TimeSeries>
); );

View File

@ -0,0 +1,180 @@
import { useMemo, useState } from 'react';
import { PanelProps, DataFrameType, DashboardCursorSync } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
import { TooltipDisplayMode, VizOrientation } from '@grafana/schema';
import { EventBusPlugin, KeyboardPlugin, TooltipPlugin2, usePanelContext } from '@grafana/ui';
import { TimeRange2, 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 { AnnotationsPlugin2 } from './plugins/AnnotationsPlugin2';
import { ExemplarsPlugin, getVisibleLabels } from './plugins/ExemplarsPlugin';
import { OutsideRangePlugin } from './plugins/OutsideRangePlugin';
import { ThresholdControlsPlugin } from './plugins/ThresholdControlsPlugin';
import { getPrepareTimeseriesSuggestion } from './suggestions';
import { getTimezones, prepareGraphableFields } from './utils';
interface TimeSeriesPanelProps extends PanelProps<Options> {}
export const TimeSeriesPanel = ({
data,
timeRange,
timeZone,
width,
height,
options,
fieldConfig,
onChangeTimeRange,
replaceVariables,
id,
}: TimeSeriesPanelProps) => {
const {
sync,
eventsScope,
canAddAnnotations,
onThresholdsChange,
canEditThresholds,
showThresholds,
dataLinkPostProcessor,
eventBus,
} = usePanelContext();
// Vertical orientation is not available for users through config.
// It is simplified version of horizontal time series panel and it does not support all plugins.
const isVerticallyOriented = options.orientation === VizOrientation.Vertical;
const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2, timeRange), [data.series, timeRange]);
const timezones = useMemo(() => getTimezones(options.timezone, timeZone), [options.timezone, timeZone]);
const suggestions = useMemo(() => {
if (frames?.length && frames.every((df) => df.meta?.type === DataFrameType.TimeSeriesLong)) {
const s = getPrepareTimeseriesSuggestion(id);
return {
message: 'Long data must be converted to wide',
suggestions: s ? [s] : undefined,
};
}
return undefined;
}, [frames, id]);
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
const cursorSync = sync?.() ?? DashboardCursorSync.Off;
if (!frames || suggestions) {
return (
<PanelDataErrorView
panelId={id}
message={suggestions?.message}
fieldConfig={fieldConfig}
data={data}
needsTimeField={true}
needsNumberField={true}
suggestions={suggestions?.suggestions}
/>
);
}
return (
<TimeSeries
frames={frames}
structureRev={data.structureRev}
timeRange={timeRange}
timeZone={timezones}
width={width}
height={height}
legend={options.legend}
options={options}
replaceVariables={replaceVariables}
dataLinkPostProcessor={dataLinkPostProcessor}
cursorSync={cursorSync}
>
{(uplotConfig, alignedFrame) => {
return (
<>
<KeyboardPlugin config={uplotConfig} />
{cursorSync !== DashboardCursorSync.Off && (
<EventBusPlugin config={uplotConfig} eventBus={eventBus} frame={alignedFrame} />
)}
{options.tooltip.mode !== TooltipDisplayMode.None && (
<TooltipPlugin2
config={uplotConfig}
hoverMode={
options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll
}
queryZoom={onChangeTimeRange}
clientZoom={true}
syncMode={cursorSync}
syncScope={eventsScope}
getDataLinks={(seriesIdx, dataIdx) =>
alignedFrame.fields[seriesIdx].getLinks?.({ valueRowIndex: dataIdx }) ?? []
}
render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange2, viaSync, dataLinks) => {
if (enableAnnotationCreation && timeRange2 != null) {
setNewAnnotationRange(timeRange2);
dismiss();
return;
}
const annotate = () => {
let xVal = u.posToVal(u.cursor.left!, 'x');
setNewAnnotationRange({ from: xVal, to: xVal });
dismiss();
};
return (
// not sure it header time here works for annotations, since it's taken from nearest datapoint index
<TimeSeriesTooltip
series={alignedFrame}
dataIdxs={dataIdxs}
seriesIdx={seriesIdx}
mode={viaSync ? TooltipDisplayMode.Multi : options.tooltip.mode}
sortOrder={options.tooltip.sort}
hideZeros={options.tooltip.hideZeros}
isPinned={isPinned}
annotate={enableAnnotationCreation ? annotate : undefined}
maxHeight={options.tooltip.maxHeight}
replaceVariables={replaceVariables}
dataLinks={dataLinks}
/>
);
}}
maxWidth={options.tooltip.maxWidth}
/>
)}
{!isVerticallyOriented && (
<>
<AnnotationsPlugin2
annotations={data.annotations ?? []}
config={uplotConfig}
timeZone={timeZone}
newRange={newAnnotationRange}
setNewRange={setNewAnnotationRange}
/>
<OutsideRangePlugin config={uplotConfig} onChangeTimeRange={onChangeTimeRange} />
{data.annotations && (
<ExemplarsPlugin
visibleSeries={getVisibleLabels(uplotConfig, frames)}
config={uplotConfig}
exemplars={data.annotations}
timeZone={timeZone}
maxHeight={options.tooltip.maxHeight}
/>
)}
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
<ThresholdControlsPlugin
config={uplotConfig}
fieldConfig={fieldConfig}
onThresholdsChange={canEditThresholds ? onThresholdsChange : undefined}
/>
)}
</>
)}
</>
);
}}
</TimeSeries>
);
};

View File

@ -13,6 +13,7 @@ import { applyNullInsertThreshold } from '@grafana/data/src/transformations/tran
import { nullToValue } from '@grafana/data/src/transformations/transformers/nulls/nullToValue'; import { nullToValue } from '@grafana/data/src/transformations/transformers/nulls/nullToValue';
import { GraphFieldConfig, LineInterpolation, TooltipDisplayMode, VizTooltipOptions } from '@grafana/schema'; import { GraphFieldConfig, LineInterpolation, TooltipDisplayMode, VizTooltipOptions } from '@grafana/schema';
import { buildScaleKey } from '@grafana/ui/src/components/uPlot/internal'; import { buildScaleKey } from '@grafana/ui/src/components/uPlot/internal';
import { XYFieldMatchers } from 'app/core/components/GraphNG/types';
import { HeatmapTooltip } from '../heatmap/panelcfg.gen'; import { HeatmapTooltip } from '../heatmap/panelcfg.gen';
@ -67,6 +68,15 @@ function reEnumFields(frames: DataFrame[]): DataFrame[] {
return frames2; return frames2;
} }
export function preparePlotFramePoints(frames: DataFrame[], dimFields: XYFieldMatchers, timeRange?: TimeRange | null) {
const dataFrame: DataFrame = {
...frames[0],
fields: frames[0].fields.filter((f) => f.type === FieldType.number),
};
console.log(dataFrame);
return dataFrame;
}
/** /**
* Returns null if there are no graphable fields * Returns null if there are no graphable fields
*/ */