Compare commits

...

14 Commits

Author SHA1 Message Date
Leon Sorokin
b104c045db cremate graveyard legend 2024-11-25 14:07:17 -06:00
Leon Sorokin
26025c1049 fix candlestick suggestions 2024-11-25 13:57:55 -06:00
Leon Sorokin
fed7ccc69b fix candlestick 2024-11-25 13:53:07 -06:00
Leon Sorokin
663614f3c6 fix test 2024-11-25 13:40:57 -06:00
Leon Sorokin
af6be4cbf3 clarify comment 2024-11-25 13:29:18 -06:00
Leon Sorokin
b79ba4dc07 fix Histogram 2024-11-25 13:26:47 -06:00
Leon Sorokin
72c8e13bf6 fixes 2024-11-25 12:52:50 -06:00
Leon Sorokin
08f18b0cae wip 2024-11-25 11:46:18 -06:00
Leon Sorokin
87100e72d6 better 2024-11-25 09:34:41 -06:00
Leon Sorokin
74badbf831 progress 2024-11-25 07:34:33 -06:00
Leon Sorokin
5b7af0a9ea Merge branch 'main' into kristina/hidden-fields-links 2024-11-23 00:49:13 -06:00
Kristina Durivage
2513594a95 remove unneeded decoupling, filter fields when there are no links 2024-10-21 20:26:01 -05:00
Kristina Durivage
3408ad436e make hidefrom logic more robust 2024-10-21 17:00:36 -05:00
Kristina Durivage
bcabff77df Use hideFrom in GraphNG to hide fields if value is defined. Remove other filtering from timeline chart 2024-10-21 17:00:36 -05:00
20 changed files with 155 additions and 73 deletions

View File

@@ -62,11 +62,32 @@ export function cacheFieldDisplayNames(frames: DataFrame[]) {
/** /**
* *
* moves each field's config.custom.hideFrom to field.state.hideFrom * moves each field's config.custom.hideFrom to field.state.hideFrom
* and mutates orgiginal field.config.custom.hideFrom to one with explicit overrides only, (without the ad-hoc stateful __system override from legend toggle) * and sets field.config.custom.hideFrom to one with explicit overrides only, (without the ad-hoc stateful __system override from legend toggle)
*/ */
export function decoupleHideFromState(frames: DataFrame[], fieldConfig: FieldConfigSource) { export function decoupleHideFromState(frames: DataFrame[], fieldConfig: FieldConfigSource) {
frames.forEach((frame) => { return frames.map((frame) => {
frame.fields.forEach((field) => { const frameCopy: DataFrame = { ...frame };
frameCopy.fields = frame.fields.map((field) => {
const fieldCopy: Field = {
...field,
state: {
...field.state,
hideFrom: {
...(field.state?.hideFrom ?? { legend: false, tooltip: false, viz: false }),
},
},
config: {
...field.config,
custom: {
...field.config.custom,
hideFrom: {
...field.config.custom?.hideFrom,
},
},
},
};
const hideFrom = { const hideFrom = {
legend: false, legend: false,
tooltip: false, tooltip: false,
@@ -75,7 +96,7 @@ export function decoupleHideFromState(frames: DataFrame[], fieldConfig: FieldCon
}; };
// with ad hoc __system override applied // with ad hoc __system override applied
const hideFromState = field.config.custom?.hideFrom; const hideFromState = fieldCopy.config.custom?.hideFrom;
fieldConfig.overrides.forEach((o) => { fieldConfig.overrides.forEach((o) => {
if ('__systemRef' in o) { if ('__systemRef' in o) {
@@ -93,16 +114,20 @@ export function decoupleHideFromState(frames: DataFrame[], fieldConfig: FieldCon
} }
}); });
field.state = { fieldCopy.state = {
...field.state, ...fieldCopy.state,
hideFrom: { hideFrom: {
...hideFromState, ...hideFromState,
}, },
}; };
// original with perm overrides // original with perm overrides
field.config.custom.hideFrom = hideFrom; fieldCopy.config.custom.hideFrom = hideFrom;
return fieldCopy;
}); });
return frameCopy;
}); });
} }

View File

@@ -1,6 +1,6 @@
import { memo } from 'react'; import { memo } from 'react';
import { DataFrame, getFieldDisplayName, getFieldSeriesColor } from '@grafana/data'; import { DataFrame, getFieldSeriesColor } from '@grafana/data';
import { VizLegendOptions, AxisPlacement } from '@grafana/schema'; import { VizLegendOptions, AxisPlacement } from '@grafana/schema';
import { useTheme2 } from '../../themes'; import { useTheme2 } from '../../themes';
@@ -12,7 +12,7 @@ import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
import { getDisplayValuesForCalcs } from './utils'; import { getDisplayValuesForCalcs } from './utils';
interface PlotLegendProps extends VizLegendOptions, Omit<VizLayoutLegendProps, 'children'> { interface PlotLegendProps extends VizLegendOptions, Omit<VizLayoutLegendProps, 'children'> {
data: DataFrame[]; frame: DataFrame;
config: UPlotConfigBuilder; config: UPlotConfigBuilder;
} }
@@ -40,41 +40,60 @@ export function hasVisibleLegendSeries(config: UPlotConfigBuilder, data: DataFra
} }
export const PlotLegend = memo( export const PlotLegend = memo(
({ data, config, placement, calcs, displayMode, ...vizLayoutLegendProps }: PlotLegendProps) => { ({ frame, config, placement, calcs, displayMode, ...vizLayoutLegendProps }: PlotLegendProps) => {
const theme = useTheme2(); const theme = useTheme2();
const legendItems = config
.getSeries()
.map<VizLegendItem | undefined>((s) => {
const seriesConfig = s.props;
const fieldIndex = seriesConfig.dataFrameFieldIndex;
const axisPlacement = config.getAxisPlacement(s.props.scaleKey);
if (!fieldIndex) { const cfgSeries = config.getSeries();
const legendItems: VizLegendItem[] = frame.fields
.map((field, i) => {
if (i === 0 || field.config.custom?.hideFrom.legend) {
return undefined; return undefined;
} }
const field = data[fieldIndex.frameIndex]?.fields[fieldIndex.fieldIndex]; const dataFrameFieldIndex = field.state?.origin!;
if (!field || field.config.custom?.hideFrom?.legend) { const seriesConfig = cfgSeries.find(({ props }) => {
return undefined; const { dataFrameFieldIndex: dataFrameFieldIndexCfg } = props;
return (
dataFrameFieldIndexCfg?.frameIndex === dataFrameFieldIndex.frameIndex &&
dataFrameFieldIndexCfg?.fieldIndex === dataFrameFieldIndex.fieldIndex
);
});
let axisPlacement = AxisPlacement.Left;
// there is a bit of a bug here. since we no longer add hidden fields to the uplot config
// we cannot determine "auto" axis placement of hidden series
// we can fix this in future by decoupling some things
if (seriesConfig != null) {
axisPlacement = config.getAxisPlacement(seriesConfig.props.scaleKey);
} else {
let fieldAxisPlacement = field.config.custom?.axisPlacement;
// respect explicit non-auto placement
if (fieldAxisPlacement !== AxisPlacement.Auto) {
fieldAxisPlacement = fieldAxisPlacement;
}
} }
const label = getFieldDisplayName(field, data[fieldIndex.frameIndex]!, data); const label = field.state?.displayName ?? field.name;
const scaleColor = getFieldSeriesColor(field, theme); const scaleColor = getFieldSeriesColor(field, theme);
const seriesColor = scaleColor.color; const seriesColor = scaleColor.color;
return { return {
disabled: !(seriesConfig.show ?? true), disabled: field.state?.hideFrom?.viz,
fieldIndex, fieldIndex: dataFrameFieldIndex,
color: seriesColor, color: seriesColor,
label, label,
yAxis: axisPlacement === AxisPlacement.Left || axisPlacement === AxisPlacement.Bottom ? 1 : 2, yAxis: axisPlacement === AxisPlacement.Left || axisPlacement === AxisPlacement.Bottom ? 1 : 2,
getDisplayValues: () => getDisplayValuesForCalcs(calcs, field, theme), getDisplayValues: () => getDisplayValuesForCalcs(calcs, field, theme),
getItemKey: () => `${label}-${fieldIndex.frameIndex}-${fieldIndex.fieldIndex}`, getItemKey: () => `${label}-${dataFrameFieldIndex.frameIndex}-${dataFrameFieldIndex.fieldIndex}`,
lineStyle: seriesConfig.lineStyle, lineStyle: field.config.custom.lineStyle,
}; };
}) })
.filter((i): i is VizLegendItem => i !== undefined); .filter((item) => item !== undefined);
return ( return (
<VizLayout.Legend placement={placement} {...vizLayoutLegendProps}> <VizLayout.Legend placement={placement} {...vizLayoutLegendProps}>

View File

@@ -4,7 +4,6 @@ import * as React from 'react';
import { DataFrame, TimeRange } from '@grafana/data'; import { DataFrame, TimeRange } from '@grafana/data';
import { PanelContextRoot } from '../../components/PanelChrome/PanelContext'; import { PanelContextRoot } from '../../components/PanelChrome/PanelContext';
import { hasVisibleLegendSeries, PlotLegend } from '../../components/uPlot/PlotLegend';
import { UPlotConfigBuilder } from '../../components/uPlot/config/UPlotConfigBuilder'; import { UPlotConfigBuilder } from '../../components/uPlot/config/UPlotConfigBuilder';
import { withTheme2 } from '../../themes/ThemeContext'; import { withTheme2 } from '../../themes/ThemeContext';
import { GraphNG, GraphNGProps, PropDiffFn } from '../GraphNG/GraphNG'; import { GraphNG, GraphNGProps, PropDiffFn } from '../GraphNG/GraphNG';
@@ -37,13 +36,7 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
}; };
renderLegend = (config: UPlotConfigBuilder) => { renderLegend = (config: UPlotConfigBuilder) => {
const { legend, frames } = this.props; return null;
if (!config || (legend && !legend.showLegend) || !hasVisibleLegendSeries(config, frames)) {
return null;
}
return <PlotLegend data={frames} config={config} {...legend} />;
}; };
render() { render() {

View File

@@ -47,7 +47,7 @@ export interface GraphNGProps extends Themeable2 {
prepConfig: (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => UPlotConfigBuilder; prepConfig: (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => UPlotConfigBuilder;
propsToDiff?: Array<string | PropDiffFn>; propsToDiff?: Array<string | PropDiffFn>;
preparePlotFrame?: (frames: DataFrame[], dimFields: XYFieldMatchers) => DataFrame | null; preparePlotFrame?: (frames: DataFrame[], dimFields: XYFieldMatchers) => DataFrame | null;
renderLegend: (config: UPlotConfigBuilder) => React.ReactElement | null; renderLegend: (config: UPlotConfigBuilder, alignedFrame: DataFrame) => React.ReactElement | null;
replaceVariables: InterpolateFunction; replaceVariables: InterpolateFunction;
dataLinkPostProcessor?: DataLinkPostProcessor; dataLinkPostProcessor?: DataLinkPostProcessor;
cursorSync?: DashboardCursorSync; cursorSync?: DashboardCursorSync;
@@ -83,6 +83,9 @@ function sameProps<T extends Record<string, unknown>>(
* @internal -- not a public API * @internal -- not a public API
*/ */
export interface GraphNGState { export interface GraphNGState {
// includes fields hidden from viz
alignedFrameLegend: DataFrame;
// excludes fields hidden from viz
alignedFrame: DataFrame; alignedFrame: DataFrame;
alignedData?: AlignedData; alignedData?: AlignedData;
config?: UPlotConfigBuilder; config?: UPlotConfigBuilder;
@@ -175,6 +178,17 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
}; };
} }
const alignedFrameLegend = alignedFrameFinal;
const nonHiddenFields = alignedFrameFinal.fields.filter(
(field, i) => i === 0 || (!field.config.custom?.hideFrom?.viz && !field.state?.hideFrom?.viz)
);
alignedFrameFinal = {
...alignedFrameFinal,
fields: nonHiddenFields,
length: nonHiddenFields.length,
};
let config = this.state?.config; let config = this.state?.config;
if (withConfig) { if (withConfig) {
@@ -184,6 +198,7 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
state = { state = {
alignedFrame: alignedFrameFinal, alignedFrame: alignedFrameFinal,
alignedFrameLegend,
config, config,
}; };
@@ -229,14 +244,14 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
render() { render() {
const { width, height, children, renderLegend } = this.props; const { width, height, children, renderLegend } = this.props;
const { config, alignedFrame, alignedData } = this.state; const { config, alignedFrame, alignedFrameLegend, alignedData } = this.state;
if (!config) { if (!config) {
return null; return null;
} }
return ( return (
<VizLayout width={width} height={height} legend={renderLegend(config)}> <VizLayout width={width} height={height} legend={renderLegend(config, alignedFrameLegend)}>
{(vizWidth: number, vizHeight: number) => ( {(vizWidth: number, vizHeight: number) => (
<UPlotChart <UPlotChart
config={config} config={config}

View File

@@ -31,14 +31,14 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
}); });
}; };
renderLegend = (config: UPlotConfigBuilder) => { renderLegend = (config: UPlotConfigBuilder, alignedFrame: DataFrame) => {
const { legend, frames } = this.props; const { legend, frames } = this.props;
if (!config || (legend && !legend.showLegend) || !hasVisibleLegendSeries(config, frames)) { if (!config || (legend && !legend.showLegend) || !hasVisibleLegendSeries(config, frames)) {
return null; return null;
} }
return <PlotLegend data={frames} config={config} {...legend} />; return <PlotLegend frame={alignedFrame} config={config} {...legend} />;
}; };
render() { render() {

View File

@@ -210,7 +210,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({
const customConfig: GraphFieldConfig = config.custom!; const customConfig: GraphFieldConfig = config.custom!;
if (field === xField || (field.type !== FieldType.number && field.type !== FieldType.enum)) { const isHidden = config.custom?.hideFrom?.viz || field.state?.hideFrom?.viz;
if (field === xField || isHidden || (field.type !== FieldType.number && field.type !== FieldType.enum)) {
continue; continue;
} }
@@ -497,7 +499,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({
barMaxWidth: customConfig.barMaxWidth, barMaxWidth: customConfig.barMaxWidth,
pointSize: customConfig.pointSize, pointSize: customConfig.pointSize,
spanNulls: customConfig.spanNulls || false, spanNulls: customConfig.spanNulls || false,
show: !customConfig.hideFrom?.viz,
gradientMode: customConfig.gradientMode, gradientMode: customConfig.gradientMode,
thresholds: config.thresholds, thresholds: config.thresholds,
hardMin: field.config.min, hardMin: field.config.min,

View File

@@ -375,9 +375,6 @@ export function prepareTimelineFields(
const fields: Field[] = []; const fields: Field[] = [];
for (let field of frame.fields) { for (let field of frame.fields) {
if (field.config.custom?.hideFrom?.viz) {
continue;
}
switch (field.type) { switch (field.type) {
case FieldType.time: case FieldType.time:
isTimeseries = true; isTimeseries = true;

View File

@@ -62,7 +62,7 @@ export function prepSeries(
} }
cacheFieldDisplayNames(frames); cacheFieldDisplayNames(frames);
decoupleHideFromState(frames, fieldConfig); frames = decoupleHideFromState(frames, fieldConfig);
let frame: DataFrame | undefined = { ...frames[0] }; let frame: DataFrame | undefined = { ...frames[0] };

View File

@@ -59,8 +59,8 @@ export const CandlestickPanel = ({
const theme = useTheme2(); const theme = useTheme2();
const info = useMemo(() => { const info = useMemo(() => {
return prepareCandlestickFields(data.series, options, theme, timeRange); return prepareCandlestickFields(data.series, fieldConfig, options, theme, timeRange);
}, [data.series, options, theme, timeRange]); }, [data.series, fieldConfig, options, theme, timeRange]);
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2 // temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null); const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);

View File

@@ -1,9 +1,10 @@
import { createTheme, toDataFrame } from '@grafana/data'; import { createTheme, FieldConfigSource, toDataFrame } from '@grafana/data';
import { prepareCandlestickFields } from './fields'; import { prepareCandlestickFields } from './fields';
import { Options, VizDisplayMode } from './types'; import { Options, VizDisplayMode } from './types';
const theme = createTheme(); const theme = createTheme();
const fieldConfig: FieldConfigSource = { defaults: {}, overrides: [] };
describe('Candlestick data', () => { describe('Candlestick data', () => {
const options = {} as Options; const options = {} as Options;
@@ -21,6 +22,7 @@ describe('Candlestick data', () => {
], ],
}), }),
], ],
fieldConfig,
options, options,
theme theme
); );
@@ -40,6 +42,7 @@ describe('Candlestick data', () => {
], ],
}), }),
], ],
fieldConfig,
options, options,
theme theme
); );
@@ -70,6 +73,7 @@ describe('Candlestick data', () => {
], ],
}), }),
], ],
fieldConfig,
options, options,
theme theme
)!; )!;
@@ -113,6 +117,7 @@ describe('Candlestick data', () => {
], ],
}), }),
], ],
fieldConfig,
options, options,
theme theme
)!; )!;
@@ -162,6 +167,7 @@ describe('Candlestick data', () => {
], ],
}), }),
], ],
fieldConfig,
options, options,
theme theme
)!; )!;
@@ -224,6 +230,7 @@ describe('Candlestick data', () => {
], ],
}), }),
], ],
fieldConfig,
options, options,
theme theme
)!; )!;
@@ -278,6 +285,7 @@ describe('Candlestick data', () => {
], ],
}), }),
], ],
fieldConfig,
options, options,
theme theme
)!; )!;

View File

@@ -1,6 +1,7 @@
import { import {
DataFrame, DataFrame,
Field, Field,
FieldConfigSource,
FieldType, FieldType,
getFieldDisplayName, getFieldDisplayName,
GrafanaTheme2, GrafanaTheme2,
@@ -96,6 +97,7 @@ function findFieldOrAuto(frame: DataFrame, info: FieldPickerInfo, options: Candl
export function prepareCandlestickFields( export function prepareCandlestickFields(
series: DataFrame[] | undefined, series: DataFrame[] | undefined,
fieldConfig: FieldConfigSource,
options: Partial<Options>, options: Partial<Options>,
theme: GrafanaTheme2, theme: GrafanaTheme2,
timeRange?: TimeRange timeRange?: TimeRange
@@ -120,7 +122,7 @@ export function prepareCandlestickFields(
const data: CandlestickData = { aligned, frame: aligned, names: {} }; const data: CandlestickData = { aligned, frame: aligned, names: {} };
// Apply same filter as everything else in timeseries // Apply same filter as everything else in timeseries
const timeSeriesFrames = prepareGraphableFields([aligned], theme, timeRange); const timeSeriesFrames = prepareGraphableFields([aligned], fieldConfig, theme, timeRange);
if (!timeSeriesFrames) { if (!timeSeriesFrames) {
return null; return null;
} }

View File

@@ -71,7 +71,7 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(CandlestickPane
.useFieldConfig(getGraphFieldConfig(defaultGraphConfig)) .useFieldConfig(getGraphFieldConfig(defaultGraphConfig))
.setPanelOptions((builder, context) => { .setPanelOptions((builder, context) => {
const opts = context.options ?? defaultOptions; const opts = context.options ?? defaultOptions;
const info = prepareCandlestickFields(context.data, opts, config.theme2); const info = prepareCandlestickFields(context.data, { defaults: {}, overrides: [] }, opts, config.theme2);
builder builder
.addRadio({ .addRadio({

View File

@@ -19,7 +19,12 @@ export class CandlestickSuggestionsSupplier {
return; return;
} }
const info = prepareCandlestickFields(builder.data.series, defaultOptions, config.theme2); const info = prepareCandlestickFields(
builder.data.series,
{ defaults: {}, overrides: [] },
defaultOptions,
config.theme2
);
if (!info) { if (!info) {
return; return;
} }

View File

@@ -45,7 +45,6 @@ export interface HistogramProps extends Themeable2 {
height: number; height: number;
structureRev?: number; // a number that will change when the frames[] structure changes structureRev?: number; // a number that will change when the frames[] structure changes
legend: VizLegendOptions; legend: VizLegendOptions;
rawSeries?: DataFrame[];
children?: (builder: UPlotConfigBuilder, frame: DataFrame, xMinOnlyFrame: DataFrame) => React.ReactNode; children?: (builder: UPlotConfigBuilder, frame: DataFrame, xMinOnlyFrame: DataFrame) => React.ReactNode;
} }
@@ -280,8 +279,11 @@ const preparePlotData = (builder: UPlotConfigBuilder, xMinOnlyFrame: DataFrame)
}; };
interface State { interface State {
alignedData: AlignedData; // includes fields hidden from viz, but excludes xMin/xMax
alignedFrameLegend: DataFrame;
// excludes fields hidden from viz
alignedFrame: DataFrame; alignedFrame: DataFrame;
alignedData: AlignedData;
config?: UPlotConfigBuilder; config?: UPlotConfigBuilder;
xMinOnlyFrame: DataFrame; xMinOnlyFrame: DataFrame;
} }
@@ -299,24 +301,30 @@ export class Histogram extends React.Component<HistogramProps, State> {
const xMinOnly = xMinOnlyFrame(alignedFrame); const xMinOnly = xMinOnlyFrame(alignedFrame);
const alignedData = preparePlotData(config, xMinOnly); const alignedData = preparePlotData(config, xMinOnly);
let alignedFrameLegend = {
...alignedFrame,
fields: alignedFrame.fields.filter((field) => field.name !== 'xMin' && field.name !== 'xMax'),
};
// console.log(alignedFrame.fields);
return { return {
alignedFrame, alignedFrame,
alignedFrameLegend,
alignedData, alignedData,
config, config,
xMinOnlyFrame: xMinOnly, xMinOnlyFrame: xMinOnly,
}; };
} }
renderLegend(config: UPlotConfigBuilder) { renderLegend(config: UPlotConfigBuilder, alignedFrame: DataFrame) {
const { legend } = this.props; const { legend } = this.props;
if (!config || legend.showLegend === false) { if (!config || legend.showLegend === false) {
return null; return null;
} }
const frames = this.props.options.combine ? [this.props.alignedFrame] : this.props.rawSeries!; return <PlotLegend frame={alignedFrame} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
return <PlotLegend data={frames} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
} }
componentDidUpdate(prevProps: HistogramProps) { componentDidUpdate(prevProps: HistogramProps) {
@@ -339,15 +347,15 @@ export class Histogram extends React.Component<HistogramProps, State> {
} }
render() { render() {
const { width, height, children, alignedFrame } = this.props; const { width, height, children } = this.props;
const { config } = this.state; const { config, alignedFrame, alignedFrameLegend } = this.state;
if (!config) { if (!config) {
return null; return null;
} }
return ( return (
<VizLayout width={width} height={height} legend={this.renderLegend(config)}> <VizLayout width={width} height={height} legend={this.renderLegend(config, alignedFrameLegend)}>
{(vizWidth: number, vizHeight: number) => ( {(vizWidth: number, vizHeight: number) => (
<UPlotChart config={this.state.config!} data={this.state.alignedData} width={vizWidth} height={vizHeight}> <UPlotChart config={this.state.config!} data={this.state.alignedData} width={vizWidth} height={vizHeight}>
{children ? children(config, alignedFrame, this.state.xMinOnlyFrame) : null} {children ? children(config, alignedFrame, this.state.xMinOnlyFrame) : null}

View File

@@ -63,7 +63,6 @@ export const HistogramPanel = ({ data, options, width, height }: Props) => {
options={options} options={options}
theme={theme} theme={theme}
legend={options.legend} legend={options.legend}
rawSeries={data.series}
structureRev={data.structureRev} structureRev={data.structureRev}
width={width} width={width}
height={height} height={height}

View File

@@ -44,7 +44,11 @@ export const TimeSeriesPanel = ({
// 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 isVerticallyOriented = options.orientation === VizOrientation.Vertical;
const frames = useMemo(() => prepareGraphableFields(data.series, config.theme2, timeRange), [data.series, timeRange]); const frames = useMemo(
() => prepareGraphableFields(data.series, fieldConfig, config.theme2, timeRange),
[data.series, fieldConfig, 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(() => {
if (frames?.length && frames.every((df) => df.meta?.type === DataFrameType.TimeSeriesLong)) { if (frames?.length && frames.every((df) => df.meta?.type === DataFrameType.TimeSeriesLong)) {

View File

@@ -1,7 +1,9 @@
import { createTheme, FieldType, createDataFrame, toDataFrame } from '@grafana/data'; import { createTheme, FieldType, createDataFrame, toDataFrame, FieldConfigSource } from '@grafana/data';
import { prepareGraphableFields } from './utils'; import { prepareGraphableFields } from './utils';
const fieldConfig: FieldConfigSource = { defaults: {}, overrides: [] };
describe('prepare timeseries graph', () => { describe('prepare timeseries graph', () => {
it('errors with no time fields', () => { it('errors with no time fields', () => {
const input = [ const input = [
@@ -12,7 +14,7 @@ describe('prepare timeseries graph', () => {
], ],
}), }),
]; ];
const frames = prepareGraphableFields(input, createTheme()); const frames = prepareGraphableFields(input, fieldConfig, createTheme());
expect(frames).toBeNull(); expect(frames).toBeNull();
}); });
@@ -25,7 +27,7 @@ describe('prepare timeseries graph', () => {
], ],
}), }),
]; ];
const frames = prepareGraphableFields(input, createTheme()); const frames = prepareGraphableFields(input, fieldConfig, createTheme());
expect(frames).toBeNull(); expect(frames).toBeNull();
}); });
@@ -41,7 +43,7 @@ describe('prepare timeseries graph', () => {
], ],
}), }),
]; ];
const frames = prepareGraphableFields(input, createTheme()); const frames = prepareGraphableFields(input, fieldConfig, createTheme());
expect(frames![0].fields.map((f) => f.state?.seriesIndex)).toEqual([undefined, undefined, 0, undefined, 1]); expect(frames![0].fields.map((f) => f.state?.seriesIndex)).toEqual([undefined, undefined, 0, undefined, 1]);
}); });
@@ -56,7 +58,7 @@ describe('prepare timeseries graph', () => {
], ],
}), }),
]; ];
const frames = prepareGraphableFields(input, createTheme()); const frames = prepareGraphableFields(input, fieldConfig, createTheme());
const out = frames![0]; const out = frames![0];
expect(out.fields.map((f) => f.name)).toEqual(['a', 'b', 'c', 'd']); expect(out.fields.map((f) => f.name)).toEqual(['a', 'b', 'c', 'd']);
@@ -82,7 +84,7 @@ describe('prepare timeseries graph', () => {
{ name: 'a', values: [-10, NaN, 10, -Infinity, +Infinity] }, { name: 'a', values: [-10, NaN, 10, -Infinity, +Infinity] },
], ],
}); });
const frames = prepareGraphableFields([df], createTheme()); const frames = prepareGraphableFields([df], fieldConfig, createTheme());
const field = frames![0].fields.find((f) => f.name === 'a'); const field = frames![0].fields.find((f) => f.name === 'a');
expect(field!.values).toMatchInlineSnapshot(` expect(field!.values).toMatchInlineSnapshot(`
@@ -103,7 +105,7 @@ describe('prepare timeseries graph', () => {
{ name: 'a', values: [1, 2, 3] }, { name: 'a', values: [1, 2, 3] },
], ],
}); });
const frames = prepareGraphableFields([df], createTheme()); const frames = prepareGraphableFields([df], fieldConfig, createTheme());
const field = frames![0].fields.find((f) => f.name === 'a'); const field = frames![0].fields.find((f) => f.name === 'a');
expect(field!.values).toMatchInlineSnapshot(` expect(field!.values).toMatchInlineSnapshot(`
@@ -127,7 +129,7 @@ describe('prepare timeseries graph', () => {
{ name: 'a', config: { noValue: '20' }, values: [1, 2, 3] }, { name: 'a', config: { noValue: '20' }, values: [1, 2, 3] },
], ],
}); });
const frames = prepareGraphableFields([df], createTheme()); const frames = prepareGraphableFields([df], fieldConfig, createTheme());
const field = frames![0].fields.find((f) => f.name === 'a'); const field = frames![0].fields.find((f) => f.name === 'a');
expect(field!.values).toMatchInlineSnapshot(` expect(field!.values).toMatchInlineSnapshot(`

View File

@@ -7,7 +7,9 @@ import {
isBooleanUnit, isBooleanUnit,
TimeRange, TimeRange,
cacheFieldDisplayNames, cacheFieldDisplayNames,
FieldConfigSource,
} from '@grafana/data'; } from '@grafana/data';
import { decoupleHideFromState } from '@grafana/data/src/field/fieldState';
import { convertFieldType } from '@grafana/data/src/transformations/transformers/convertFieldType'; import { convertFieldType } from '@grafana/data/src/transformations/transformers/convertFieldType';
import { applyNullInsertThreshold } from '@grafana/data/src/transformations/transformers/nulls/nullInsertThreshold'; import { applyNullInsertThreshold } from '@grafana/data/src/transformations/transformers/nulls/nullInsertThreshold';
import { nullToValue } from '@grafana/data/src/transformations/transformers/nulls/nullToValue'; import { nullToValue } from '@grafana/data/src/transformations/transformers/nulls/nullToValue';
@@ -72,6 +74,7 @@ function reEnumFields(frames: DataFrame[]): DataFrame[] {
*/ */
export function prepareGraphableFields( export function prepareGraphableFields(
series: DataFrame[], series: DataFrame[],
fieldConfig: FieldConfigSource,
theme: GrafanaTheme2, theme: GrafanaTheme2,
timeRange?: TimeRange, timeRange?: TimeRange,
// numeric X requires a single frame where the first field is numeric // numeric X requires a single frame where the first field is numeric
@@ -82,6 +85,7 @@ export function prepareGraphableFields(
} }
cacheFieldDisplayNames(series); cacheFieldDisplayNames(series);
series = decoupleHideFromState(series, fieldConfig);
let useNumericX = xNumFieldIdx != null; let useNumericX = xNumFieldIdx != null;

View File

@@ -80,8 +80,8 @@ export const TrendPanel = ({
} }
} }
return { frames: prepareGraphableFields(frames, config.theme2, undefined, xFieldIdx) }; return { frames: prepareGraphableFields(frames, fieldConfig, config.theme2, undefined, xFieldIdx) };
}, [data.series, options.xField]); }, [data.series, fieldConfig, options.xField]);
if (info.warning || !info.frames) { if (info.warning || !info.frames) {
return ( return (

View File

@@ -43,7 +43,7 @@ export function prepSeries(
fieldConfig: FieldConfigSource fieldConfig: FieldConfigSource
) { ) {
cacheFieldDisplayNames(frames); cacheFieldDisplayNames(frames);
decoupleHideFromState(frames, fieldConfig); frames = decoupleHideFromState(frames, fieldConfig);
let series: XYSeries[] = []; let series: XYSeries[] = [];