diff --git a/packages/grafana-ui/src/components/TimeSeries/utils.ts b/packages/grafana-ui/src/components/TimeSeries/utils.ts index 33f5381ee8e..531e4312f02 100644 --- a/packages/grafana-ui/src/components/TimeSeries/utils.ts +++ b/packages/grafana-ui/src/components/TimeSeries/utils.ts @@ -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 = {}; 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); diff --git a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx index 35ec51ba98c..f1b432e2945 100644 --- a/public/app/plugins/panel/candlestick/CandlestickPanel.tsx +++ b/public/app/plugins/panel/candlestick/CandlestickPanel.tsx @@ -21,7 +21,7 @@ import uPlot from 'uplot'; interface CandlestickPanelProps extends PanelProps {} -export const MarketTrendPanel: React.FC = ({ +export const CandlestickPanel: React.FC = ({ data, timeRange, timeZone, diff --git a/public/app/plugins/panel/candlestick/fields.ts b/public/app/plugins/panel/candlestick/fields.ts index 2f38e650c3c..47475e88845 100644 --- a/public/app/plugins/panel/candlestick/fields.ts +++ b/public/app/plugins/panel/candlestick/fields.ts @@ -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(); 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; } diff --git a/public/app/plugins/panel/candlestick/models.gen.ts b/public/app/plugins/panel/candlestick/models.gen.ts index f6830581763..892e420befa 100644 --- a/public/app/plugins/panel/candlestick/models.gen.ts +++ b/public/app/plugins/panel/candlestick/models.gen.ts @@ -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, }; diff --git a/public/app/plugins/panel/candlestick/module.tsx b/public/app/plugins/panel/candlestick/module.tsx index b173aa64b89..c1d88cf1c18 100644 --- a/public/app/plugins/panel/candlestick/module.tsx +++ b/public/app/plugins/panel/candlestick/module.tsx @@ -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(MarketTrendPanel) +export const plugin = new PanelPlugin(CandlestickPanel) .useFieldConfig(getGraphFieldConfig(defaultGraphConfig)) .setPanelOptions((builder, context) => { const opts = context.options ?? defaultPanelOptions; @@ -125,6 +125,19 @@ export const plugin = new PanelPlugin(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); })