Candlestick: exclude unmapped fields by default (#42011)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
Ryan McKinley 2021-11-19 16:39:21 -08:00 committed by GitHub
parent 8a06e89d2f
commit 403222e14e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 11 deletions

View File

@ -320,11 +320,14 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{ sync: DashboardCursor
// hook up custom/composite renderers
renderers?.forEach((r) => {
if (!indexByName) {
indexByName = getNamesToFieldIndex(frame, allFrames);
}
let fieldIndices: Record<string, number> = {};
for (let key in r.fieldMap) {
let dispName = r.fieldMap[key];
fieldIndices[key] = indexByName!.get(dispName)!;
fieldIndices[key] = indexByName.get(dispName)!;
}
r.init(builder, fieldIndices);

View File

@ -21,7 +21,7 @@ import uPlot from 'uplot';
interface CandlestickPanelProps extends PanelProps<CandlestickOptions> {}
export const MarketTrendPanel: React.FC<CandlestickPanelProps> = ({
export const CandlestickPanel: React.FC<CandlestickPanelProps> = ({
data,
timeRange,
timeZone,

View File

@ -9,7 +9,7 @@ import {
} from '@grafana/data';
import { findField } from 'app/features/dimensions';
import { prepareGraphableFields } from '../timeseries/utils';
import { CandlestickOptions, CandlestickFieldMap } from './models.gen';
import { CandlestickOptions, CandlestickFieldMap, VizDisplayMode } from './models.gen';
export interface FieldPickerInfo {
/** property name */
@ -115,12 +115,18 @@ export function prepareCandlestickFields(
if (norm.warn || norm.noTimeField || !norm.frames?.length) {
return norm as CandlestickData;
}
data.frame = norm.frames[0];
const frame = (data.frame = norm.frames[0]);
const timeIndex = frame.fields.findIndex((f) => f.type === FieldType.time);
if (timeIndex < 0) {
data.warn = 'Missing time field';
data.noTimeField = true;
return data;
}
// Find the known fields
const used = new Set<Field>();
for (const info of Object.values(candlestickFieldsInfo)) {
const field = findFieldOrAuto(data.frame, info, fieldMap);
const field = findFieldOrAuto(frame, info, fieldMap);
if (field) {
data[info.key] = field;
used.add(field);
@ -129,7 +135,7 @@ export function prepareCandlestickFields(
// Use first numeric value as open
if (!data.open && !data.close) {
data.open = data.frame.fields.find((f) => f.type === FieldType.number);
data.open = frame.fields.find((f) => f.type === FieldType.number);
if (data.open) {
used.add(data.open);
}
@ -145,7 +151,8 @@ export function prepareCandlestickFields(
name: 'Next open',
state: undefined,
};
data.frame.fields.push(data.close);
used.add(data.close);
frame.fields.push(data.close);
data.autoOpenClose = true;
}
@ -153,14 +160,15 @@ export function prepareCandlestickFields(
if (data.close && !data.open && !fieldMap.open) {
const values = data.close.values.toArray().slice();
values.unshift(values[0]); // duplicate first value
values.length = data.frame.length;
values.length = frame.length;
data.open = {
...data.close,
values: new ArrayVector(values),
name: 'Previous close',
state: undefined,
};
data.frame.fields.push(data.open);
used.add(data.open);
frame.fields.push(data.open);
data.autoOpenClose = true;
}
@ -172,6 +180,24 @@ export function prepareCandlestickFields(
data.low = data.open;
}
// unmap low and high fields in volume-only mode, and volume field in candles-only mode
// so they fall through to unmapped fields and get appropriate includeAllFields treatment
if (options.mode === VizDisplayMode.Volume) {
if (data.high) {
used.delete(data.high);
data.high = undefined;
}
if (data.low) {
used.delete(data.low);
data.low = undefined;
}
} else if (options.mode === VizDisplayMode.Candles) {
if (data.volume) {
used.delete(data.volume);
data.volume = undefined;
}
}
// Register the name of each mapped field
for (const info of Object.values(candlestickFieldsInfo)) {
const f = data[info.key];
@ -180,5 +206,34 @@ export function prepareCandlestickFields(
}
}
const timeField = frame.fields[timeIndex];
// Make sure first field is time!
const fields: Field[] = [timeField];
if (!options.includeAllFields) {
fields.push(...used);
} else if (timeIndex > 0) {
fields.push(...frame.fields.filter((f) => f !== timeField));
}
data.frame = {
...data.frame,
fields,
};
// Force update all the indicies
for (let i = 0; i < data.frame.fields.length; i++) {
const field = data.frame.fields[i];
// time is unused (-1), y series enumerate from 0
field.state!.seriesIndex = i - 1;
field.state!.origin = {
fieldIndex: i,
frameIndex: 0,
};
}
return data;
}

View File

@ -53,6 +53,9 @@ export interface CandlestickOptions extends OptionsWithLegend {
colorStrategy: ColorStrategy;
fields: CandlestickFieldMap;
colors: CandlestickColors;
// When enabled, all fields will be sent to the graph
includeAllFields?: boolean;
}
export const defaultPanelOptions: CandlestickOptions = {
@ -66,4 +69,5 @@ export const defaultPanelOptions: CandlestickOptions = {
placement: 'bottom',
calcs: [],
},
includeAllFields: false,
};

View File

@ -8,7 +8,7 @@ import {
SelectableValue,
} from '@grafana/data';
import { commonOptionsBuilder } from '@grafana/ui';
import { MarketTrendPanel } from './CandlestickPanel';
import { CandlestickPanel } from './CandlestickPanel';
import {
defaultColors,
CandlestickOptions,
@ -68,7 +68,7 @@ function addFieldPicker(
});
}
export const plugin = new PanelPlugin<CandlestickOptions, GraphFieldConfig>(MarketTrendPanel)
export const plugin = new PanelPlugin<CandlestickOptions, GraphFieldConfig>(CandlestickPanel)
.useFieldConfig(getGraphFieldConfig(defaultGraphConfig))
.setPanelOptions((builder, context) => {
const opts = context.options ?? defaultPanelOptions;
@ -125,6 +125,19 @@ export const plugin = new PanelPlugin<CandlestickOptions, GraphFieldConfig>(Mark
addFieldPicker(builder, candlestickFieldsInfo.volume, info);
}
builder.addRadio({
path: 'includeAllFields',
name: 'Additional fields',
description: 'Use standard timeseries options to configure any fields not mapped above',
defaultValue: defaultPanelOptions.includeAllFields,
settings: {
options: [
{ label: 'Ignore', value: false },
{ label: 'Include', value: true },
],
},
});
// commonOptionsBuilder.addTooltipOptions(builder);
commonOptionsBuilder.addLegendOptions(builder);
})