Compare commits
14 Commits
sriram/SQL
...
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
|
* 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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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] };
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
)!;
|
)!;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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(`
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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[] = [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user