mirror of
https://github.com/grafana/grafana.git
synced 2025-12-20 19:44:55 +08:00
Compare commits
14 Commits
docs/add-d
...
leeoniya/g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b104c045db | ||
|
|
26025c1049 | ||
|
|
fed7ccc69b | ||
|
|
663614f3c6 | ||
|
|
af6be4cbf3 | ||
|
|
b79ba4dc07 | ||
|
|
72c8e13bf6 | ||
|
|
08f18b0cae | ||
|
|
87100e72d6 | ||
|
|
74badbf831 | ||
|
|
5b7af0a9ea | ||
|
|
2513594a95 | ||
|
|
3408ad436e | ||
|
|
bcabff77df |
@@ -62,11 +62,32 @@ export function cacheFieldDisplayNames(frames: DataFrame[]) {
|
||||
/**
|
||||
*
|
||||
* 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) {
|
||||
frames.forEach((frame) => {
|
||||
frame.fields.forEach((field) => {
|
||||
return frames.map((frame) => {
|
||||
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 = {
|
||||
legend: false,
|
||||
tooltip: false,
|
||||
@@ -75,7 +96,7 @@ export function decoupleHideFromState(frames: DataFrame[], fieldConfig: FieldCon
|
||||
};
|
||||
|
||||
// with ad hoc __system override applied
|
||||
const hideFromState = field.config.custom?.hideFrom;
|
||||
const hideFromState = fieldCopy.config.custom?.hideFrom;
|
||||
|
||||
fieldConfig.overrides.forEach((o) => {
|
||||
if ('__systemRef' in o) {
|
||||
@@ -93,16 +114,20 @@ export function decoupleHideFromState(frames: DataFrame[], fieldConfig: FieldCon
|
||||
}
|
||||
});
|
||||
|
||||
field.state = {
|
||||
...field.state,
|
||||
fieldCopy.state = {
|
||||
...fieldCopy.state,
|
||||
hideFrom: {
|
||||
...hideFromState,
|
||||
},
|
||||
};
|
||||
|
||||
// original with perm overrides
|
||||
field.config.custom.hideFrom = hideFrom;
|
||||
fieldCopy.config.custom.hideFrom = hideFrom;
|
||||
|
||||
return fieldCopy;
|
||||
});
|
||||
|
||||
return frameCopy;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import { DataFrame, getFieldDisplayName, getFieldSeriesColor } from '@grafana/data';
|
||||
import { DataFrame, getFieldSeriesColor } from '@grafana/data';
|
||||
import { VizLegendOptions, AxisPlacement } from '@grafana/schema';
|
||||
|
||||
import { useTheme2 } from '../../themes';
|
||||
@@ -12,7 +12,7 @@ import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
|
||||
import { getDisplayValuesForCalcs } from './utils';
|
||||
|
||||
interface PlotLegendProps extends VizLegendOptions, Omit<VizLayoutLegendProps, 'children'> {
|
||||
data: DataFrame[];
|
||||
frame: DataFrame;
|
||||
config: UPlotConfigBuilder;
|
||||
}
|
||||
|
||||
@@ -40,41 +40,60 @@ export function hasVisibleLegendSeries(config: UPlotConfigBuilder, data: DataFra
|
||||
}
|
||||
|
||||
export const PlotLegend = memo(
|
||||
({ data, config, placement, calcs, displayMode, ...vizLayoutLegendProps }: PlotLegendProps) => {
|
||||
({ frame, config, placement, calcs, displayMode, ...vizLayoutLegendProps }: PlotLegendProps) => {
|
||||
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;
|
||||
}
|
||||
|
||||
const field = data[fieldIndex.frameIndex]?.fields[fieldIndex.fieldIndex];
|
||||
const dataFrameFieldIndex = field.state?.origin!;
|
||||
|
||||
if (!field || field.config.custom?.hideFrom?.legend) {
|
||||
return undefined;
|
||||
const seriesConfig = cfgSeries.find(({ props }) => {
|
||||
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 seriesColor = scaleColor.color;
|
||||
|
||||
return {
|
||||
disabled: !(seriesConfig.show ?? true),
|
||||
fieldIndex,
|
||||
disabled: field.state?.hideFrom?.viz,
|
||||
fieldIndex: dataFrameFieldIndex,
|
||||
color: seriesColor,
|
||||
label,
|
||||
yAxis: axisPlacement === AxisPlacement.Left || axisPlacement === AxisPlacement.Bottom ? 1 : 2,
|
||||
getDisplayValues: () => getDisplayValuesForCalcs(calcs, field, theme),
|
||||
getItemKey: () => `${label}-${fieldIndex.frameIndex}-${fieldIndex.fieldIndex}`,
|
||||
lineStyle: seriesConfig.lineStyle,
|
||||
getItemKey: () => `${label}-${dataFrameFieldIndex.frameIndex}-${dataFrameFieldIndex.fieldIndex}`,
|
||||
lineStyle: field.config.custom.lineStyle,
|
||||
};
|
||||
})
|
||||
.filter((i): i is VizLegendItem => i !== undefined);
|
||||
.filter((item) => item !== undefined);
|
||||
|
||||
return (
|
||||
<VizLayout.Legend placement={placement} {...vizLayoutLegendProps}>
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as React from 'react';
|
||||
import { DataFrame, TimeRange } from '@grafana/data';
|
||||
|
||||
import { PanelContextRoot } from '../../components/PanelChrome/PanelContext';
|
||||
import { hasVisibleLegendSeries, PlotLegend } from '../../components/uPlot/PlotLegend';
|
||||
import { UPlotConfigBuilder } from '../../components/uPlot/config/UPlotConfigBuilder';
|
||||
import { withTheme2 } from '../../themes/ThemeContext';
|
||||
import { GraphNG, GraphNGProps, PropDiffFn } from '../GraphNG/GraphNG';
|
||||
@@ -37,13 +36,7 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
|
||||
};
|
||||
|
||||
renderLegend = (config: UPlotConfigBuilder) => {
|
||||
const { legend, frames } = this.props;
|
||||
|
||||
if (!config || (legend && !legend.showLegend) || !hasVisibleLegendSeries(config, frames)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <PlotLegend data={frames} config={config} {...legend} />;
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface GraphNGProps extends Themeable2 {
|
||||
prepConfig: (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => UPlotConfigBuilder;
|
||||
propsToDiff?: Array<string | PropDiffFn>;
|
||||
preparePlotFrame?: (frames: DataFrame[], dimFields: XYFieldMatchers) => DataFrame | null;
|
||||
renderLegend: (config: UPlotConfigBuilder) => React.ReactElement | null;
|
||||
renderLegend: (config: UPlotConfigBuilder, alignedFrame: DataFrame) => React.ReactElement | null;
|
||||
replaceVariables: InterpolateFunction;
|
||||
dataLinkPostProcessor?: DataLinkPostProcessor;
|
||||
cursorSync?: DashboardCursorSync;
|
||||
@@ -83,6 +83,9 @@ function sameProps<T extends Record<string, unknown>>(
|
||||
* @internal -- not a public API
|
||||
*/
|
||||
export interface GraphNGState {
|
||||
// includes fields hidden from viz
|
||||
alignedFrameLegend: DataFrame;
|
||||
// excludes fields hidden from viz
|
||||
alignedFrame: DataFrame;
|
||||
alignedData?: AlignedData;
|
||||
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;
|
||||
|
||||
if (withConfig) {
|
||||
@@ -184,6 +198,7 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
|
||||
|
||||
state = {
|
||||
alignedFrame: alignedFrameFinal,
|
||||
alignedFrameLegend,
|
||||
config,
|
||||
};
|
||||
|
||||
@@ -229,14 +244,14 @@ export class GraphNG extends Component<GraphNGProps, GraphNGState> {
|
||||
|
||||
render() {
|
||||
const { width, height, children, renderLegend } = this.props;
|
||||
const { config, alignedFrame, alignedData } = this.state;
|
||||
const { config, alignedFrame, alignedFrameLegend, alignedData } = this.state;
|
||||
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VizLayout width={width} height={height} legend={renderLegend(config)}>
|
||||
<VizLayout width={width} height={height} legend={renderLegend(config, alignedFrameLegend)}>
|
||||
{(vizWidth: number, vizHeight: number) => (
|
||||
<UPlotChart
|
||||
config={config}
|
||||
|
||||
@@ -31,14 +31,14 @@ export class UnthemedTimeSeries extends Component<TimeSeriesProps> {
|
||||
});
|
||||
};
|
||||
|
||||
renderLegend = (config: UPlotConfigBuilder) => {
|
||||
renderLegend = (config: UPlotConfigBuilder, alignedFrame: DataFrame) => {
|
||||
const { legend, frames } = this.props;
|
||||
|
||||
if (!config || (legend && !legend.showLegend) || !hasVisibleLegendSeries(config, frames)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <PlotLegend data={frames} config={config} {...legend} />;
|
||||
return <PlotLegend frame={alignedFrame} config={config} {...legend} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -210,7 +210,9 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -497,7 +499,6 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({
|
||||
barMaxWidth: customConfig.barMaxWidth,
|
||||
pointSize: customConfig.pointSize,
|
||||
spanNulls: customConfig.spanNulls || false,
|
||||
show: !customConfig.hideFrom?.viz,
|
||||
gradientMode: customConfig.gradientMode,
|
||||
thresholds: config.thresholds,
|
||||
hardMin: field.config.min,
|
||||
|
||||
@@ -375,9 +375,6 @@ export function prepareTimelineFields(
|
||||
|
||||
const fields: Field[] = [];
|
||||
for (let field of frame.fields) {
|
||||
if (field.config.custom?.hideFrom?.viz) {
|
||||
continue;
|
||||
}
|
||||
switch (field.type) {
|
||||
case FieldType.time:
|
||||
isTimeseries = true;
|
||||
|
||||
@@ -62,7 +62,7 @@ export function prepSeries(
|
||||
}
|
||||
|
||||
cacheFieldDisplayNames(frames);
|
||||
decoupleHideFromState(frames, fieldConfig);
|
||||
frames = decoupleHideFromState(frames, fieldConfig);
|
||||
|
||||
let frame: DataFrame | undefined = { ...frames[0] };
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ export const CandlestickPanel = ({
|
||||
const theme = useTheme2();
|
||||
|
||||
const info = useMemo(() => {
|
||||
return prepareCandlestickFields(data.series, options, theme, timeRange);
|
||||
}, [data.series, options, theme, timeRange]);
|
||||
return prepareCandlestickFields(data.series, fieldConfig, options, theme, timeRange);
|
||||
}, [data.series, fieldConfig, options, theme, timeRange]);
|
||||
|
||||
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
|
||||
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { createTheme, toDataFrame } from '@grafana/data';
|
||||
import { createTheme, FieldConfigSource, toDataFrame } from '@grafana/data';
|
||||
|
||||
import { prepareCandlestickFields } from './fields';
|
||||
import { Options, VizDisplayMode } from './types';
|
||||
|
||||
const theme = createTheme();
|
||||
const fieldConfig: FieldConfigSource = { defaults: {}, overrides: [] };
|
||||
|
||||
describe('Candlestick data', () => {
|
||||
const options = {} as Options;
|
||||
@@ -21,6 +22,7 @@ describe('Candlestick data', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
fieldConfig,
|
||||
options,
|
||||
theme
|
||||
);
|
||||
@@ -40,6 +42,7 @@ describe('Candlestick data', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
fieldConfig,
|
||||
options,
|
||||
theme
|
||||
);
|
||||
@@ -70,6 +73,7 @@ describe('Candlestick data', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
fieldConfig,
|
||||
options,
|
||||
theme
|
||||
)!;
|
||||
@@ -113,6 +117,7 @@ describe('Candlestick data', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
fieldConfig,
|
||||
options,
|
||||
theme
|
||||
)!;
|
||||
@@ -162,6 +167,7 @@ describe('Candlestick data', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
fieldConfig,
|
||||
options,
|
||||
theme
|
||||
)!;
|
||||
@@ -224,6 +230,7 @@ describe('Candlestick data', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
fieldConfig,
|
||||
options,
|
||||
theme
|
||||
)!;
|
||||
@@ -278,6 +285,7 @@ describe('Candlestick data', () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
fieldConfig,
|
||||
options,
|
||||
theme
|
||||
)!;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldConfigSource,
|
||||
FieldType,
|
||||
getFieldDisplayName,
|
||||
GrafanaTheme2,
|
||||
@@ -96,6 +97,7 @@ function findFieldOrAuto(frame: DataFrame, info: FieldPickerInfo, options: Candl
|
||||
|
||||
export function prepareCandlestickFields(
|
||||
series: DataFrame[] | undefined,
|
||||
fieldConfig: FieldConfigSource,
|
||||
options: Partial<Options>,
|
||||
theme: GrafanaTheme2,
|
||||
timeRange?: TimeRange
|
||||
@@ -120,7 +122,7 @@ export function prepareCandlestickFields(
|
||||
const data: CandlestickData = { aligned, frame: aligned, names: {} };
|
||||
|
||||
// Apply same filter as everything else in timeseries
|
||||
const timeSeriesFrames = prepareGraphableFields([aligned], theme, timeRange);
|
||||
const timeSeriesFrames = prepareGraphableFields([aligned], fieldConfig, theme, timeRange);
|
||||
if (!timeSeriesFrames) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const plugin = new PanelPlugin<Options, GraphFieldConfig>(CandlestickPane
|
||||
.useFieldConfig(getGraphFieldConfig(defaultGraphConfig))
|
||||
.setPanelOptions((builder, context) => {
|
||||
const opts = context.options ?? defaultOptions;
|
||||
const info = prepareCandlestickFields(context.data, opts, config.theme2);
|
||||
const info = prepareCandlestickFields(context.data, { defaults: {}, overrides: [] }, opts, config.theme2);
|
||||
|
||||
builder
|
||||
.addRadio({
|
||||
|
||||
@@ -19,7 +19,12 @@ export class CandlestickSuggestionsSupplier {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = prepareCandlestickFields(builder.data.series, defaultOptions, config.theme2);
|
||||
const info = prepareCandlestickFields(
|
||||
builder.data.series,
|
||||
{ defaults: {}, overrides: [] },
|
||||
defaultOptions,
|
||||
config.theme2
|
||||
);
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ export interface HistogramProps extends Themeable2 {
|
||||
height: number;
|
||||
structureRev?: number; // a number that will change when the frames[] structure changes
|
||||
legend: VizLegendOptions;
|
||||
rawSeries?: DataFrame[];
|
||||
children?: (builder: UPlotConfigBuilder, frame: DataFrame, xMinOnlyFrame: DataFrame) => React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -280,8 +279,11 @@ const preparePlotData = (builder: UPlotConfigBuilder, xMinOnlyFrame: DataFrame)
|
||||
};
|
||||
|
||||
interface State {
|
||||
alignedData: AlignedData;
|
||||
// includes fields hidden from viz, but excludes xMin/xMax
|
||||
alignedFrameLegend: DataFrame;
|
||||
// excludes fields hidden from viz
|
||||
alignedFrame: DataFrame;
|
||||
alignedData: AlignedData;
|
||||
config?: UPlotConfigBuilder;
|
||||
xMinOnlyFrame: DataFrame;
|
||||
}
|
||||
@@ -299,24 +301,30 @@ export class Histogram extends React.Component<HistogramProps, State> {
|
||||
const xMinOnly = xMinOnlyFrame(alignedFrame);
|
||||
const alignedData = preparePlotData(config, xMinOnly);
|
||||
|
||||
let alignedFrameLegend = {
|
||||
...alignedFrame,
|
||||
fields: alignedFrame.fields.filter((field) => field.name !== 'xMin' && field.name !== 'xMax'),
|
||||
};
|
||||
|
||||
// console.log(alignedFrame.fields);
|
||||
|
||||
return {
|
||||
alignedFrame,
|
||||
alignedFrameLegend,
|
||||
alignedData,
|
||||
config,
|
||||
xMinOnlyFrame: xMinOnly,
|
||||
};
|
||||
}
|
||||
|
||||
renderLegend(config: UPlotConfigBuilder) {
|
||||
renderLegend(config: UPlotConfigBuilder, alignedFrame: DataFrame) {
|
||||
const { legend } = this.props;
|
||||
|
||||
if (!config || legend.showLegend === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const frames = this.props.options.combine ? [this.props.alignedFrame] : this.props.rawSeries!;
|
||||
|
||||
return <PlotLegend data={frames} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
|
||||
return <PlotLegend frame={alignedFrame} config={config} maxHeight="35%" maxWidth="60%" {...legend} />;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: HistogramProps) {
|
||||
@@ -339,15 +347,15 @@ export class Histogram extends React.Component<HistogramProps, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { width, height, children, alignedFrame } = this.props;
|
||||
const { config } = this.state;
|
||||
const { width, height, children } = this.props;
|
||||
const { config, alignedFrame, alignedFrameLegend } = this.state;
|
||||
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VizLayout width={width} height={height} legend={this.renderLegend(config)}>
|
||||
<VizLayout width={width} height={height} legend={this.renderLegend(config, alignedFrameLegend)}>
|
||||
{(vizWidth: number, vizHeight: number) => (
|
||||
<UPlotChart config={this.state.config!} data={this.state.alignedData} width={vizWidth} height={vizHeight}>
|
||||
{children ? children(config, alignedFrame, this.state.xMinOnlyFrame) : null}
|
||||
|
||||
@@ -63,7 +63,6 @@ export const HistogramPanel = ({ data, options, width, height }: Props) => {
|
||||
options={options}
|
||||
theme={theme}
|
||||
legend={options.legend}
|
||||
rawSeries={data.series}
|
||||
structureRev={data.structureRev}
|
||||
width={width}
|
||||
height={height}
|
||||
|
||||
@@ -44,7 +44,11 @@ export const TimeSeriesPanel = ({
|
||||
// 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 frames = useMemo(
|
||||
() => prepareGraphableFields(data.series, fieldConfig, config.theme2, timeRange),
|
||||
[data.series, fieldConfig, 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)) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
const fieldConfig: FieldConfigSource = { defaults: {}, overrides: [] };
|
||||
|
||||
describe('prepare timeseries graph', () => {
|
||||
it('errors with no time fields', () => {
|
||||
const input = [
|
||||
@@ -12,7 +14,7 @@ describe('prepare timeseries graph', () => {
|
||||
],
|
||||
}),
|
||||
];
|
||||
const frames = prepareGraphableFields(input, createTheme());
|
||||
const frames = prepareGraphableFields(input, fieldConfig, createTheme());
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
|
||||
@@ -56,7 +58,7 @@ describe('prepare timeseries graph', () => {
|
||||
],
|
||||
}),
|
||||
];
|
||||
const frames = prepareGraphableFields(input, createTheme());
|
||||
const frames = prepareGraphableFields(input, fieldConfig, createTheme());
|
||||
const out = frames![0];
|
||||
|
||||
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] },
|
||||
],
|
||||
});
|
||||
const frames = prepareGraphableFields([df], createTheme());
|
||||
const frames = prepareGraphableFields([df], fieldConfig, createTheme());
|
||||
|
||||
const field = frames![0].fields.find((f) => f.name === 'a');
|
||||
expect(field!.values).toMatchInlineSnapshot(`
|
||||
@@ -103,7 +105,7 @@ describe('prepare timeseries graph', () => {
|
||||
{ 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');
|
||||
expect(field!.values).toMatchInlineSnapshot(`
|
||||
@@ -127,7 +129,7 @@ describe('prepare timeseries graph', () => {
|
||||
{ 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');
|
||||
expect(field!.values).toMatchInlineSnapshot(`
|
||||
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
isBooleanUnit,
|
||||
TimeRange,
|
||||
cacheFieldDisplayNames,
|
||||
FieldConfigSource,
|
||||
} from '@grafana/data';
|
||||
import { decoupleHideFromState } from '@grafana/data/src/field/fieldState';
|
||||
import { convertFieldType } from '@grafana/data/src/transformations/transformers/convertFieldType';
|
||||
import { applyNullInsertThreshold } from '@grafana/data/src/transformations/transformers/nulls/nullInsertThreshold';
|
||||
import { nullToValue } from '@grafana/data/src/transformations/transformers/nulls/nullToValue';
|
||||
@@ -72,6 +74,7 @@ function reEnumFields(frames: DataFrame[]): DataFrame[] {
|
||||
*/
|
||||
export function prepareGraphableFields(
|
||||
series: DataFrame[],
|
||||
fieldConfig: FieldConfigSource,
|
||||
theme: GrafanaTheme2,
|
||||
timeRange?: TimeRange,
|
||||
// numeric X requires a single frame where the first field is numeric
|
||||
@@ -82,6 +85,7 @@ export function prepareGraphableFields(
|
||||
}
|
||||
|
||||
cacheFieldDisplayNames(series);
|
||||
series = decoupleHideFromState(series, fieldConfig);
|
||||
|
||||
let useNumericX = xNumFieldIdx != null;
|
||||
|
||||
|
||||
@@ -80,8 +80,8 @@ export const TrendPanel = ({
|
||||
}
|
||||
}
|
||||
|
||||
return { frames: prepareGraphableFields(frames, config.theme2, undefined, xFieldIdx) };
|
||||
}, [data.series, options.xField]);
|
||||
return { frames: prepareGraphableFields(frames, fieldConfig, config.theme2, undefined, xFieldIdx) };
|
||||
}, [data.series, fieldConfig, options.xField]);
|
||||
|
||||
if (info.warning || !info.frames) {
|
||||
return (
|
||||
|
||||
@@ -43,7 +43,7 @@ export function prepSeries(
|
||||
fieldConfig: FieldConfigSource
|
||||
) {
|
||||
cacheFieldDisplayNames(frames);
|
||||
decoupleHideFromState(frames, fieldConfig);
|
||||
frames = decoupleHideFromState(frames, fieldConfig);
|
||||
|
||||
let series: XYSeries[] = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user