XYChart: Add data filter to manual mode (#81115)

* XYChart: Add data filter to manual mode

* Add onChange to data filter for manual editor

* Update placeholder for auto editor for consistency

* Filter x y fields based on frame

* Update frame calc for truthy

* Use display name instead for frame filter

* Update placeholders

* Apply frame filter to series prep

* Re run make gen cue

* Remove old TODO

* Force data filter to be selected

* minor cleanup

---------

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
Drew Slobodnjak 2024-01-30 15:09:15 -08:00 committed by GitHub
parent be6efd9518
commit 6fc1a6a54f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 87 additions and 25 deletions

View File

@ -214,6 +214,7 @@ It extends [FieldConfig](#fieldconfig).
| `axisSoftMax` | number | No | | *(Inherited from [FieldConfig](#fieldconfig))* | | `axisSoftMax` | number | No | | *(Inherited from [FieldConfig](#fieldconfig))* |
| `axisSoftMin` | number | No | | *(Inherited from [FieldConfig](#fieldconfig))* | | `axisSoftMin` | number | No | | *(Inherited from [FieldConfig](#fieldconfig))* |
| `axisWidth` | number | No | | *(Inherited from [FieldConfig](#fieldconfig))* | | `axisWidth` | number | No | | *(Inherited from [FieldConfig](#fieldconfig))* |
| `frame` | number | No | | |
| `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [FieldConfig](#fieldconfig))*<br/>TODO docs | | `hideFrom` | [HideSeriesConfig](#hideseriesconfig) | No | | *(Inherited from [FieldConfig](#fieldconfig))*<br/>TODO docs |
| `labelValue` | [TextDimensionConfig](#textdimensionconfig) | No | | *(Inherited from [FieldConfig](#fieldconfig))* | | `labelValue` | [TextDimensionConfig](#textdimensionconfig) | No | | *(Inherited from [FieldConfig](#fieldconfig))* |
| `label` | string | No | | *(Inherited from [FieldConfig](#fieldconfig))*<br/>TODO docs<br/>Possible values are: `auto`, `never`, `always`. | | `label` | string | No | | *(Inherited from [FieldConfig](#fieldconfig))*<br/>TODO docs<br/>Possible values are: `auto`, `never`, `always`. |

View File

@ -57,6 +57,7 @@ export const defaultFieldConfig: Partial<FieldConfig> = {
}; };
export interface ScatterSeriesConfig extends FieldConfig { export interface ScatterSeriesConfig extends FieldConfig {
frame?: number;
name?: string; name?: string;
x?: string; x?: string;
y?: string; y?: string;

View File

@ -85,7 +85,7 @@ export const AutoEditor = ({ value, onChange, context }: StandardEditorProps<XYD
<Select <Select
isClearable={true} isClearable={true}
options={frameNames} options={frameNames}
placeholder={frameNames[0].label} placeholder={'Change filter'}
value={frameNames.find((v) => v.value === value?.frame)} value={frameNames.find((v) => v.value === value?.frame)}
onChange={(v) => { onChange={(v) => {
onChange({ onChange({

View File

@ -1,13 +1,14 @@
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
import { import {
GrafanaTheme2, GrafanaTheme2,
StandardEditorProps, StandardEditorProps,
FieldNamePickerBaseNameMode, FieldNamePickerBaseNameMode,
StandardEditorsRegistryItem, StandardEditorsRegistryItem,
getFrameDisplayName,
} from '@grafana/data'; } from '@grafana/data';
import { Button, IconButton, useStyles2 } from '@grafana/ui'; import { Button, Field, IconButton, Select, useStyles2 } from '@grafana/ui';
import { LayerName } from 'app/core/components/Layers/LayerName'; import { LayerName } from 'app/core/components/Layers/LayerName';
import { ScatterSeriesEditor } from './ScatterSeriesEditor'; import { ScatterSeriesEditor } from './ScatterSeriesEditor';
@ -18,6 +19,16 @@ export const ManualEditor = ({
onChange, onChange,
context, context,
}: StandardEditorProps<ScatterSeriesConfig[], unknown, Options>) => { }: StandardEditorProps<ScatterSeriesConfig[], unknown, Options>) => {
const frameNames = useMemo(() => {
if (context?.data?.length) {
return context.data.map((frame, index) => ({
value: index,
label: `${getFrameDisplayName(frame, index)} (index: ${index}, rows: ${frame.length})`,
}));
}
return [{ value: 0, label: 'First result' }];
}, [context.data]);
const [selected, setSelected] = useState(0); const [selected, setSelected] = useState(0);
const style = useStyles2(getStyles); const style = useStyles2(getStyles);
@ -101,23 +112,53 @@ export const ManualEditor = ({
</div> </div>
{selected >= 0 && value[selected] && ( {selected >= 0 && value[selected] && (
<ScatterSeriesEditor <>
key={`series/${selected}`} {frameNames.length > 1 && (
baseNameMode={FieldNamePickerBaseNameMode.ExcludeBaseNames} <Field label={'Data'}>
item={{} as StandardEditorsRegistryItem} <Select
context={context} isClearable={false}
value={value[selected]} options={frameNames}
onChange={(v) => { placeholder={'Change filter'}
onChange( value={
value.map((obj, i) => { frameNames.find((v) => {
if (i === selected) { return v.value === value[selected].frame;
return v!; }) ?? 0
} }
return obj; onChange={(val) => {
}) onChange(
); value.map((obj, i) => {
}} if (i === selected) {
/> if (val === null) {
return { ...value[i], frame: undefined };
}
return { ...value[i], frame: val?.value!, x: undefined, y: undefined };
}
return obj;
})
);
}}
/>
</Field>
)}
<ScatterSeriesEditor
key={`series/${selected}`}
baseNameMode={FieldNamePickerBaseNameMode.ExcludeBaseNames}
item={{} as StandardEditorsRegistryItem}
context={context}
value={value[selected]}
onChange={(val) => {
onChange(
value.map((obj, i) => {
if (i === selected) {
return val!;
}
return obj;
})
);
}}
frameFilter={value[selected].frame ?? undefined}
/>
</>
)} )}
</> </>
); );

View File

@ -9,13 +9,16 @@ import { Options, ScatterSeriesConfig } from './panelcfg.gen';
export interface Props extends StandardEditorProps<ScatterSeriesConfig, unknown, Options> { export interface Props extends StandardEditorProps<ScatterSeriesConfig, unknown, Options> {
baseNameMode: FieldNamePickerBaseNameMode; baseNameMode: FieldNamePickerBaseNameMode;
frameFilter?: number;
} }
export const ScatterSeriesEditor = ({ value, onChange, context, baseNameMode }: Props) => { export const ScatterSeriesEditor = ({ value, onChange, context, baseNameMode, frameFilter = -1 }: Props) => {
const onFieldChange = (val: unknown | undefined, field: string) => { const onFieldChange = (val: unknown | undefined, field: string) => {
onChange({ ...value, [field]: val }); onChange({ ...value, [field]: val });
}; };
const frame = context.data && frameFilter > -1 ? context.data[frameFilter] : undefined;
return ( return (
<div> <div>
<Field label={'X Field'}> <Field label={'X Field'}>
@ -27,7 +30,10 @@ export const ScatterSeriesEditor = ({ value, onChange, context, baseNameMode }:
id: 'x', id: 'x',
name: 'x', name: 'x',
settings: { settings: {
filter: (field) =>
frame?.fields.some((obj) => obj.state?.displayName === field.state?.displayName) ?? true,
baseNameMode, baseNameMode,
placeholderText: 'select X field',
}, },
}} }}
/> />
@ -38,10 +44,13 @@ export const ScatterSeriesEditor = ({ value, onChange, context, baseNameMode }:
context={context} context={context}
onChange={(field) => onFieldChange(field, 'y')} onChange={(field) => onFieldChange(field, 'y')}
item={{ item={{
id: 'x', id: 'y',
name: 'x', name: 'y',
settings: { settings: {
filter: (field) =>
frame?.fields.some((obj) => obj.state?.displayName === field.state?.displayName) ?? true,
baseNameMode, baseNameMode,
placeholderText: 'select Y field',
}, },
}} }}
/> />

View File

@ -57,9 +57,10 @@ composableKinds: PanelCfg: {
ScatterSeriesConfig: { ScatterSeriesConfig: {
FieldConfig FieldConfig
x?: string x?: string
y?: string y?: string
name?: string name?: string
frame?: number
} @cuetsy(kind="interface") } @cuetsy(kind="interface")
Options: { Options: {

View File

@ -54,6 +54,7 @@ export const defaultFieldConfig: Partial<FieldConfig> = {
}; };
export interface ScatterSeriesConfig extends FieldConfig { export interface ScatterSeriesConfig extends FieldConfig {
frame?: number;
name?: string; name?: string;
x?: string; x?: string;
y?: string; y?: string;

View File

@ -224,6 +224,10 @@ function prepSeries(options: Options, frames: DataFrame[]): ScatterSeries[] {
} }
for (let frameIndex = 0; frameIndex < frames.length; frameIndex++) { for (let frameIndex = 0; frameIndex < frames.length; frameIndex++) {
// When a frame filter is applied, only include matching frame index
if (series.frame !== undefined && series.frame !== frameIndex) {
continue;
}
const frame = frames[frameIndex]; const frame = frames[frameIndex];
const xIndex = findFieldIndex(series.x, frame, frames); const xIndex = findFieldIndex(series.x, frame, frames);

View File

@ -64,3 +64,7 @@ export interface ExtraFacets {
colorFacetValue: number; colorFacetValue: number;
sizeFacetValue: number; sizeFacetValue: number;
} }
export interface DataFilterBySeries {
frame: number;
}