Theme: Move displayProcessor & fieldOverrides & visualizations to use new theme model (#33502)

* WIP updating getColorForTheme

* Progress

* More fixes

* Updating more parts

* Fixing unit tests

* Fixing more tests

* Fixing storybook issues

* More refactoring

* Fixed test
This commit is contained in:
Torkel Ödegaard 2021-04-29 12:44:06 +02:00 committed by GitHub
parent 0836e7bde8
commit 017bcc73ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 1268 additions and 1285 deletions

View File

@ -3,6 +3,7 @@ import { DisplayProcessor, DisplayValue } from '../types/displayValue';
import { MappingType, ValueMapping } from '../types/valueMapping';
import { FieldConfig, FieldType, ThresholdsMode } from '../types';
import { systemDateFormats } from '../datetime';
import { createTheme } from '../themes';
function getDisplayProcessorFromConfig(config: FieldConfig) {
return getDisplayProcessor({
@ -10,6 +11,7 @@ function getDisplayProcessorFromConfig(config: FieldConfig) {
config,
type: FieldType.number,
},
theme: createTheme(),
});
}
@ -26,7 +28,7 @@ describe('Process simple display values', () => {
// Don't test float values here since the decimal formatting changes
const processors = [
// Without options, this shortcuts to a much easier implementation
getDisplayProcessor({ field: { config: {} } }),
getDisplayProcessor({ field: { config: {} }, theme: createTheme() }),
// Add a simple option that is not used (uses a different base class)
getDisplayProcessorFromConfig({ min: 0, max: 100 }),
@ -249,6 +251,7 @@ describe('Date display options', () => {
unit: 'xyz', // ignore non-date formats
},
},
theme: createTheme(),
});
expect(processor(0).text).toEqual('1970-01-01 00:00:00');
});
@ -262,6 +265,7 @@ describe('Date display options', () => {
unit: 'dateTimeAsUS', // ignore non-date formats
},
},
theme: createTheme(),
});
expect(processor(0).text).toEqual('01/01/1970 12:00:00 am');
});
@ -275,6 +279,7 @@ describe('Date display options', () => {
unit: 'time:YYYY', // ignore non-date formats
},
},
theme: createTheme(),
});
expect(processor(0).text).toEqual('1970');
});
@ -289,6 +294,7 @@ describe('Date display options', () => {
type: FieldType.time,
config: {},
},
theme: createTheme(),
});
expect(processor(0).text).toEqual('1970-01');
@ -303,6 +309,7 @@ describe('Date display options', () => {
type: FieldType.time,
config: {},
},
theme: createTheme(),
});
expect(processor('2020-08-01T08:48:43.783337Z').text).toEqual('2020-08-01 08:48:43');
@ -315,6 +322,7 @@ describe('Date display options', () => {
type: FieldType.string,
config: { unit: 'string' },
},
theme: createTheme(),
});
expect(processor('22.1122334455').text).toEqual('22.1122334455');
});
@ -325,6 +333,7 @@ describe('Date display options', () => {
type: FieldType.string,
config: { decimals: 2 },
},
theme: createTheme(),
});
expect(processor('22.1122334455').text).toEqual('22.11');
});

View File

@ -3,14 +3,13 @@ import { toString, toNumber as _toNumber, isEmpty, isBoolean } from 'lodash';
// Types
import { Field, FieldType } from '../types/dataFrame';
import { GrafanaTheme } from '../types/theme';
import { DisplayProcessor, DisplayValue } from '../types/displayValue';
import { getValueFormat } from '../valueFormats/valueFormats';
import { getMappedValue } from '../utils/valueMappings';
import { dateTime } from '../datetime';
import { KeyValue, TimeZone } from '../types';
import { getScaleCalculator } from './scale';
import { getTestTheme } from '../utils/testdata/testTheme';
import { GrafanaThemeV2 } from '../themes/types';
interface DisplayProcessorOptions {
field: Partial<Field>;
@ -21,7 +20,7 @@ interface DisplayProcessorOptions {
/**
* Will pick 'dark' if not defined
*/
theme?: GrafanaTheme;
theme: GrafanaThemeV2;
}
// Reasonable units for time
@ -43,9 +42,6 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
const { field } = options;
const config = field.config ?? {};
// Theme should be required or we need access to default theme instance from here
const theme = options.theme ?? getTestTheme();
let unit = config.unit;
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));
@ -55,7 +51,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
}
const formatFunc = getValueFormat(unit || 'none');
const scaleFunc = getScaleCalculator(field as Field, theme);
const scaleFunc = getScaleCalculator(field as Field, options.theme);
return (value: any) => {
const { mappings } = config;

View File

@ -1,5 +1,5 @@
import { createTheme } from '../themes';
import { Field, FieldColorModeId, FieldType } from '../types';
import { getTestTheme } from '../utils/testdata/testTheme';
import { ArrayVector } from '../vector/ArrayVector';
import { fieldColorModeRegistry, FieldValueColorCalculator, getFieldSeriesColor } from './fieldColor';
@ -26,7 +26,7 @@ function getCalculator(options: GetCalcOptions): FieldValueColorCalculator {
const field = getTestField(options.mode);
const mode = fieldColorModeRegistry.get(options.mode);
field.state!.seriesIndex = options.seriesIndex;
return mode.getCalculator(field, getTestTheme());
return mode.getCalculator(field, createTheme());
}
describe('fieldColorModeRegistry', () => {
@ -53,7 +53,7 @@ describe('fieldColorModeRegistry', () => {
// last percent 75%
field.values = new ArrayVector([0, -10, 5, 10, 2, 5]);
const color = getFieldSeriesColor(field, getTestTheme());
const color = getFieldSeriesColor(field, createTheme());
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
expect(color.color).toEqual(calcFn(4, 0.75));
@ -66,7 +66,7 @@ describe('getFieldSeriesColor', () => {
it('When color.seriesBy is last use that to calc series color', () => {
field.config.color!.seriesBy = 'last';
const color = getFieldSeriesColor(field, getTestTheme());
const color = getFieldSeriesColor(field, createTheme());
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
// the 4 can be anything, 0.75 comes from 5 being 75% in the range -10 to 10 (see data above)
@ -75,7 +75,7 @@ describe('getFieldSeriesColor', () => {
it('When color.seriesBy is max use that to calc series color', () => {
field.config.color!.seriesBy = 'max';
const color = getFieldSeriesColor(field, getTestTheme());
const color = getFieldSeriesColor(field, createTheme());
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
expect(color.color).toEqual(calcFn(10, 1));
@ -83,7 +83,7 @@ describe('getFieldSeriesColor', () => {
it('When color.seriesBy is min use that to calc series color', () => {
field.config.color!.seriesBy = 'min';
const color = getFieldSeriesColor(field, getTestTheme());
const color = getFieldSeriesColor(field, createTheme());
const calcFn = getCalculator({ mode: 'continuous-GrYlRd' });
expect(color.color).toEqual(calcFn(-10, 0));

View File

@ -1,17 +1,18 @@
import { FALLBACK_COLOR, Field, FieldColorModeId, GrafanaTheme, Threshold } from '../types';
import { FALLBACK_COLOR, Field, FieldColorModeId, Threshold } from '../types';
import { classicColors, getColorForTheme, RegistryItem } from '../utils';
import { Registry } from '../utils/Registry';
import { interpolateRgbBasis } from 'd3-interpolate';
import { fallBackTreshold } from './thresholds';
import { getScaleCalculator, ColorScaleValue } from './scale';
import { reduceField } from '../transformations/fieldReducer';
import { GrafanaThemeV2 } from '../themes/types';
/** @beta */
export type FieldValueColorCalculator = (value: number, percent: number, Threshold?: Threshold) => string;
/** @beta */
export interface FieldColorMode extends RegistryItem {
getCalculator: (field: Field, theme: GrafanaTheme) => FieldValueColorCalculator;
getCalculator: (field: Field, theme: GrafanaThemeV2) => FieldValueColorCalculator;
colors?: string[];
isContinuous?: boolean;
isByValue?: boolean;
@ -34,7 +35,7 @@ export const fieldColorModeRegistry = new Registry<FieldColorMode>(() => {
getCalculator: (_field, theme) => {
return (_value, _percent, threshold) => {
const thresholdSafe = threshold ?? fallBackTreshold;
return getColorForTheme(thresholdSafe.color, theme);
return getColorForTheme(thresholdSafe.color, theme.v1);
};
},
},
@ -166,12 +167,12 @@ export class FieldColorSchemeMode implements FieldColorMode {
this.isByValue = options.isByValue;
}
private getColors(theme: GrafanaTheme) {
private getColors(theme: GrafanaThemeV2) {
if (this.colorCache) {
return this.colorCache;
}
this.colorCache = this.colors.map((c) => getColorForTheme(c, theme));
this.colorCache = this.colors.map((c) => getColorForTheme(c, theme.v1));
return this.colorCache;
}
@ -183,7 +184,7 @@ export class FieldColorSchemeMode implements FieldColorMode {
return this.interpolator;
}
getCalculator(field: Field, theme: GrafanaTheme) {
getCalculator(field: Field, theme: GrafanaThemeV2) {
const colors = this.getColors(theme);
if (this.isByValue) {
@ -221,7 +222,7 @@ export function getFieldColorMode(mode?: FieldColorModeId | string): FieldColorM
* Function that will return a series color for any given color mode. If the color mode is a by value color
* mode it will use the field.config.color.seriesBy property to figure out which value to use
*/
export function getFieldSeriesColor(field: Field, theme: GrafanaTheme): ColorScaleValue {
export function getFieldSeriesColor(field: Field, theme: GrafanaThemeV2): ColorScaleValue {
const mode = getFieldColorModeForField(field);
if (!mode.isByValue) {
@ -240,8 +241,8 @@ export function getFieldSeriesColor(field: Field, theme: GrafanaTheme): ColorSca
return scale(value);
}
function getFixedColor(field: Field, theme: GrafanaTheme) {
function getFixedColor(field: Field, theme: GrafanaThemeV2) {
return () => {
return getColorForTheme(field.config.color?.fixedColor ?? FALLBACK_COLOR, theme);
return getColorForTheme(field.config.color?.fixedColor ?? FALLBACK_COLOR, theme.v1);
};
}

View File

@ -4,7 +4,7 @@ import { toDataFrame } from '../dataframe/processDataFrame';
import { ReducerID } from '../transformations/fieldReducer';
import { MappingType } from '../types';
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
import { getTestTheme } from '../utils/testdata/testTheme';
import { createTheme } from '../themes';
describe('FieldDisplay', () => {
beforeAll(() => {
@ -335,7 +335,7 @@ function createDisplayOptions(extend: Partial<GetFieldDisplayValuesOptions> = {}
overrides: [],
defaults: {},
},
theme: getTestTheme(),
theme: createTheme(),
};
return merge<GetFieldDisplayValuesOptions, any>(options, extend);

View File

@ -15,7 +15,7 @@ import {
TimeZone,
} from '../types';
import { DataFrameView } from '../dataframe/DataFrameView';
import { GrafanaTheme } from '../types/theme';
import { GrafanaThemeV2 } from '../themes';
import { reduceField, ReducerID } from '../transformations/fieldReducer';
import { ScopedVars } from '../types/ScopedVars';
import { getTimeField } from '../dataframe/processDataFrame';
@ -71,7 +71,7 @@ export interface GetFieldDisplayValuesOptions {
fieldConfig: FieldConfigSource;
replaceVariables: InterpolateFunction;
sparkline?: boolean; // Calculate the sparkline
theme: GrafanaTheme;
theme: GrafanaThemeV2;
timeZone?: TimeZone;
}

View File

@ -27,7 +27,7 @@ import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
import { getFieldDisplayName } from './fieldState';
import { ArrayVector } from '../vector';
import { getDisplayProcessor } from './displayProcessor';
import { getTestTheme } from '../utils/testdata/testTheme';
import { createTheme } from '../themes';
const property1: any = {
id: 'custom.property1', // Match field properties
@ -179,7 +179,7 @@ describe('applyFieldOverrides', () => {
overrides: [],
},
replaceVariables: (value: any) => value,
theme: getTestTheme(),
theme: createTheme(),
fieldConfigRegistry: new FieldConfigOptionsRegistry(),
});
@ -241,7 +241,7 @@ describe('applyFieldOverrides', () => {
},
fieldConfigRegistry: customFieldRegistry,
replaceVariables: (v) => v,
theme: getTestTheme(),
theme: createTheme(),
})[0];
const outField = processed.fields[0];
@ -257,7 +257,7 @@ describe('applyFieldOverrides', () => {
data: [f0], // the frame
fieldConfig: src as FieldConfigSource, // defaults + overrides
replaceVariables: (undefined as any) as InterpolateFunction,
theme: getTestTheme(),
theme: createTheme(),
fieldConfigRegistry: customFieldRegistry,
})[0];
const valueColumn = data.fields[1];
@ -284,7 +284,7 @@ describe('applyFieldOverrides', () => {
data: [f0], // the frame
fieldConfig: src as FieldConfigSource, // defaults + overrides
replaceVariables: (undefined as any) as InterpolateFunction,
theme: getTestTheme(),
theme: createTheme(),
})[0];
const valueColumn = data.fields[1];
const range = valueColumn.state!.range!;
@ -306,7 +306,7 @@ describe('applyFieldOverrides', () => {
replaceVariablesCalls.push(variables);
return value;
}) as InterpolateFunction,
theme: getTestTheme(),
theme: createTheme(),
fieldConfigRegistry: customFieldRegistry,
})[0];
@ -550,16 +550,7 @@ describe('getLinksSupplier', () => {
});
const replaceSpy = jest.fn();
const supplier = getLinksSupplier(
f0,
f0.fields[0],
{},
replaceSpy,
// this is used only for internal links so isn't needed here
{
theme: getTestTheme(),
}
);
const supplier = getLinksSupplier(f0, f0.fields[0], {}, replaceSpy);
supplier({});
expect(replaceSpy).toBeCalledTimes(2);
@ -594,6 +585,7 @@ describe('getLinksSupplier', () => {
},
],
},
display: (v) => ({ numeric: v, text: String(v) }),
},
],
});
@ -603,10 +595,11 @@ describe('getLinksSupplier', () => {
f0.fields[0],
{},
// We do not need to interpolate anything for this test
(value, vars, format) => value,
{ theme: getTestTheme() }
(value, vars, format) => value
);
const links = supplier({ valueRowIndex: 0 });
expect(links.length).toBe(1);
expect(links[0]).toEqual(
expect.objectContaining({
@ -786,23 +779,25 @@ describe('applyRawFieldOverrides', () => {
fields: [numberAsEpoc, numberWithDecimals, numberAsBoolean, boolean, string, datetime],
});
dataFrameA.fields[0].display = getDisplayProcessor({ field: dataFrameA.fields[0] });
dataFrameA.fields[1].display = getDisplayProcessor({ field: dataFrameA.fields[1] });
dataFrameA.fields[2].display = getDisplayProcessor({ field: dataFrameA.fields[2] });
dataFrameA.fields[3].display = getDisplayProcessor({ field: dataFrameA.fields[3] });
dataFrameA.fields[4].display = getDisplayProcessor({ field: dataFrameA.fields[4] });
dataFrameA.fields[5].display = getDisplayProcessor({ field: dataFrameA.fields[5], timeZone: 'utc' });
const theme = createTheme();
dataFrameA.fields[0].display = getDisplayProcessor({ field: dataFrameA.fields[0], theme });
dataFrameA.fields[1].display = getDisplayProcessor({ field: dataFrameA.fields[1], theme });
dataFrameA.fields[2].display = getDisplayProcessor({ field: dataFrameA.fields[2], theme });
dataFrameA.fields[3].display = getDisplayProcessor({ field: dataFrameA.fields[3], theme });
dataFrameA.fields[4].display = getDisplayProcessor({ field: dataFrameA.fields[4], theme });
dataFrameA.fields[5].display = getDisplayProcessor({ field: dataFrameA.fields[5], theme, timeZone: 'utc' });
const dataFrameB: DataFrame = toDataFrame({
fields: [numberAsEpoc, numberWithDecimals, numberAsBoolean, boolean, string, datetime],
});
dataFrameB.fields[0].display = getDisplayProcessor({ field: dataFrameB.fields[0] });
dataFrameB.fields[1].display = getDisplayProcessor({ field: dataFrameB.fields[1] });
dataFrameB.fields[2].display = getDisplayProcessor({ field: dataFrameB.fields[2] });
dataFrameB.fields[3].display = getDisplayProcessor({ field: dataFrameB.fields[3] });
dataFrameB.fields[4].display = getDisplayProcessor({ field: dataFrameB.fields[4] });
dataFrameB.fields[5].display = getDisplayProcessor({ field: dataFrameB.fields[5], timeZone: 'utc' });
dataFrameB.fields[0].display = getDisplayProcessor({ field: dataFrameB.fields[0], theme });
dataFrameB.fields[1].display = getDisplayProcessor({ field: dataFrameB.fields[1], theme });
dataFrameB.fields[2].display = getDisplayProcessor({ field: dataFrameB.fields[2], theme });
dataFrameB.fields[3].display = getDisplayProcessor({ field: dataFrameB.fields[3], theme });
dataFrameB.fields[4].display = getDisplayProcessor({ field: dataFrameB.fields[4], theme });
dataFrameB.fields[5].display = getDisplayProcessor({ field: dataFrameB.fields[5], theme, timeZone: 'utc' });
const data = [dataFrameA, dataFrameB];
const rawData = applyRawFieldOverrides(data);

View File

@ -9,7 +9,6 @@ import {
FieldConfigPropertyItem,
FieldOverrideContext,
FieldType,
GrafanaTheme,
InterpolateFunction,
LinkModel,
NumericRange,
@ -206,10 +205,13 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
});
// Attach data links supplier
newField.getLinks = getLinksSupplier(newFrame, newField, fieldScopedVars, context.replaceVariables, {
theme: options.theme,
timeZone: options.timeZone,
});
newField.getLinks = getLinksSupplier(
newFrame,
newField,
fieldScopedVars,
context.replaceVariables,
options.timeZone
);
return newField;
});
@ -324,10 +326,7 @@ export const getLinksSupplier = (
field: Field,
fieldScopedVars: ScopedVars,
replaceVariables: InterpolateFunction,
options: {
theme: GrafanaTheme;
timeZone?: TimeZone;
}
timeZone?: TimeZone
) => (config: ValueLinkConfig): Array<LinkModel<Field>> => {
if (!field.config.links || field.config.links.length === 0) {
return [];
@ -342,13 +341,19 @@ export const getLinksSupplier = (
// We are not displaying reduction result
if (config.valueRowIndex !== undefined && !isNaN(config.valueRowIndex)) {
const fieldsProxy = getFieldDisplayValuesProxy(frame, config.valueRowIndex, options);
const fieldsProxy = getFieldDisplayValuesProxy({
frame,
rowIndex: config.valueRowIndex,
timeZone: timeZone,
});
valueVars = {
raw: field.values.get(config.valueRowIndex),
numeric: fieldsProxy[field.name].numeric,
text: fieldsProxy[field.name].text,
time: timeField ? timeField.values.get(config.valueRowIndex) : undefined,
};
dataFrameVars = {
__data: {
value: {

View File

@ -1,8 +1,7 @@
import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
import { applyFieldOverrides } from './fieldOverrides';
import { toDataFrame } from '../dataframe';
import { GrafanaTheme } from '../types';
import { getTestTheme } from '../utils/testdata/testTheme';
import { createTheme } from '../themes';
describe('getFieldDisplayValuesProxy', () => {
const data = applyFieldOverrides({
@ -33,7 +32,7 @@ describe('getFieldDisplayValuesProxy', () => {
},
replaceVariables: (val: string) => val,
timeZone: 'utc',
theme: getTestTheme(),
theme: createTheme(),
})[0];
it('should define all display functions', () => {
@ -45,9 +44,7 @@ describe('getFieldDisplayValuesProxy', () => {
it('should format the time values in UTC', () => {
// Test Proxies in general
const p = getFieldDisplayValuesProxy(data, 0, {
theme: {} as GrafanaTheme,
});
const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 0 });
const time = p.Time;
expect(time.numeric).toEqual(1);
expect(time.text).toEqual('1970-01-01 00:00:00');
@ -58,9 +55,7 @@ describe('getFieldDisplayValuesProxy', () => {
});
it('Lookup by name, index, or displayName', () => {
const p = getFieldDisplayValuesProxy(data, 2, {
theme: {} as GrafanaTheme,
});
const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 2 });
expect(p.power.numeric).toEqual(300);
expect(p['power'].numeric).toEqual(300);
expect(p['POWAH!'].numeric).toEqual(300);
@ -69,9 +64,7 @@ describe('getFieldDisplayValuesProxy', () => {
});
it('should return undefined when missing', () => {
const p = getFieldDisplayValuesProxy(data, 0, {
theme: {} as GrafanaTheme,
});
const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 0 });
expect(p.xyz).toBeUndefined();
expect(p[100]).toBeUndefined();
});

View File

@ -1,6 +1,5 @@
import { toNumber } from 'lodash';
import { DataFrame, DisplayValue, GrafanaTheme, TimeZone } from '../types';
import { getDisplayProcessor } from './displayProcessor';
import { DataFrame, DisplayValue, TimeZone } from '../types';
import { formattedValueToString } from '../valueFormats';
/**
@ -10,30 +9,27 @@ import { formattedValueToString } from '../valueFormats';
* @param options
* @internal
*/
export function getFieldDisplayValuesProxy(
frame: DataFrame,
rowIndex: number,
options: {
theme: GrafanaTheme;
timeZone?: TimeZone;
}
): Record<string, DisplayValue> {
export function getFieldDisplayValuesProxy(options: {
frame: DataFrame;
rowIndex: number;
timeZone?: TimeZone;
}): Record<string, DisplayValue> {
return new Proxy({} as Record<string, DisplayValue>, {
get: (obj: any, key: string) => {
// 1. Match the name
let field = frame.fields.find((f) => key === f.name);
let field = options.frame.fields.find((f) => key === f.name);
if (!field) {
// 2. Match the array index
const k = toNumber(key);
field = frame.fields[k];
field = options.frame.fields[k];
}
if (!field) {
// 3. Match the config displayName
field = frame.fields.find((f) => key === f.config.displayName);
field = options.frame.fields.find((f) => key === f.config.displayName);
}
if (!field) {
// 4. Match the name label
field = frame.fields.find((f) => {
field = options.frame.fields.find((f) => {
if (f.labels) {
return key === f.labels.name;
}
@ -44,14 +40,9 @@ export function getFieldDisplayValuesProxy(
return undefined;
}
if (!field.display) {
// Lazy load the display processor
field.display = getDisplayProcessor({
field,
theme: options.theme,
timeZone: options.timeZone,
});
throw new Error('Field missing display processor ' + field.name);
}
const raw = field.values.get(rowIndex);
const raw = field.values.get(options.rowIndex);
const disp = field.display(raw);
disp.toString = () => formattedValueToString(disp);
return disp;

View File

@ -2,7 +2,7 @@ import { ThresholdsMode, Field, FieldType } from '../types';
import { sortThresholds } from './thresholds';
import { ArrayVector } from '../vector/ArrayVector';
import { getScaleCalculator } from './scale';
import { getTestTheme } from '../utils/testdata/testTheme';
import { createTheme } from '../themes';
describe('getScaleCalculator', () => {
it('should return percent, threshold and color', () => {
@ -19,7 +19,7 @@ describe('getScaleCalculator', () => {
values: new ArrayVector([0, 50, 100]),
};
const calc = getScaleCalculator(field, getTestTheme());
const calc = getScaleCalculator(field, createTheme());
expect(calc(70)).toEqual({
percent: 0.7,
threshold: thresholds[1],

View File

@ -1,6 +1,7 @@
import { isNumber } from 'lodash';
import { GrafanaThemeV2 } from '../themes/types';
import { reduceField, ReducerID } from '../transformations/fieldReducer';
import { Field, FieldConfig, FieldType, GrafanaTheme, NumericRange, Threshold } from '../types';
import { Field, FieldConfig, FieldType, NumericRange, Threshold } from '../types';
import { getFieldColorModeForField } from './fieldColor';
import { getActiveThresholdForValue } from './thresholds';
@ -12,7 +13,7 @@ export interface ColorScaleValue {
export type ScaleCalculator = (value: number) => ColorScaleValue;
export function getScaleCalculator(field: Field, theme: GrafanaTheme): ScaleCalculator {
export function getScaleCalculator(field: Field, theme: GrafanaThemeV2): ScaleCalculator {
const mode = getFieldColorModeForField(field);
const getColor = mode.getCalculator(field, theme);
const info = field.state?.range ?? getMinMaxAndDelta(field);

View File

@ -2,6 +2,7 @@ import { DataSourceInstanceSettings } from './datasource';
import { PanelPluginMeta } from './panel';
import { GrafanaTheme } from './theme';
import { SystemDateFormatSettings } from '../datetime';
import { GrafanaThemeV2 } from '../themes';
/**
* Describes the build information that will be available via the Grafana configuration.
@ -123,6 +124,7 @@ export interface GrafanaConfig {
editorsCanAdmin: boolean;
disableSanitizeHtml: boolean;
theme: GrafanaTheme;
theme2: GrafanaThemeV2;
pluginsToPreload: string[];
featureToggles: FeatureToggles;
licenseInfo: LicenseInfo;

View File

@ -1,9 +1,10 @@
import { ComponentType } from 'react';
import { MatcherConfig, FieldConfig, Field, DataFrame, GrafanaTheme, TimeZone } from '../types';
import { MatcherConfig, FieldConfig, Field, DataFrame, TimeZone } from '../types';
import { InterpolateFunction } from './panel';
import { StandardEditorProps, FieldConfigOptionsRegistry, StandardEditorContext } from '../field';
import { OptionsEditorItem } from './OptionsUIRegistryBuilder';
import { OptionEditorConfig } from './options';
import { GrafanaThemeV2 } from '../themes';
export interface DynamicConfigValue {
id: string;
@ -113,7 +114,7 @@ export interface ApplyFieldOverrideOptions {
fieldConfig: FieldConfigSource;
fieldConfigRegistry?: FieldConfigOptionsRegistry;
replaceVariables: InterpolateFunction;
theme: GrafanaTheme;
theme: GrafanaThemeV2;
timeZone?: TimeZone;
}

View File

@ -5,6 +5,7 @@ import { getDataFrameRow, toDataFrameDTO } from '../dataframe/processDataFrame';
import fs from 'fs';
import { MutableDataFrame } from '../dataframe';
import { getDisplayProcessor } from '../field';
import { createTheme } from '../themes';
describe('read csv', () => {
it('should get X and y', () => {
@ -149,7 +150,11 @@ describe('DataFrame to CSV', () => {
],
});
dataFrame.fields[1].display = getDisplayProcessor({ field: dataFrame.fields[1], timeZone: 'utc' });
dataFrame.fields[1].display = getDisplayProcessor({
field: dataFrame.fields[1],
timeZone: 'utc',
theme: createTheme(),
});
const csv = toCSV([dataFrame]);
expect(csv).toMatchInlineSnapshot(`

View File

@ -1,11 +1,12 @@
import { merge } from 'lodash';
import { getTheme } from '@grafana/ui';
import {
BuildInfo,
createTheme,
DataSourceInstanceSettings,
FeatureToggles,
GrafanaConfig,
GrafanaTheme,
GrafanaThemeV2,
LicenseInfo,
PanelPluginMeta,
systemDateFormats,
@ -49,6 +50,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
editorsCanAdmin = false;
disableSanitizeHtml = false;
theme: GrafanaTheme;
theme2: GrafanaThemeV2;
pluginsToPreload: string[] = [];
featureToggles: FeatureToggles = {
live: false,
@ -75,7 +77,9 @@ export class GrafanaBootConfig implements GrafanaConfig {
awsAssumeRoleEnabled = false;
constructor(options: GrafanaBootConfig) {
this.theme = getTheme(options.bootData.user.lightTheme ? 'light' : 'dark');
const mode = options.bootData.user.lightTheme ? 'light' : 'dark';
this.theme2 = createTheme({ colors: { mode } });
this.theme = this.theme2.v1;
const defaults = {
datasources: {},

View File

@ -4,7 +4,7 @@ import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { BarChart } from './BarChart';
import { LegendDisplayMode } from '../VizLegend/models.gen';
import { prepDataForStorybook } from '../../utils/storybook/data';
import { useTheme } from '../../themes';
import { useTheme2 } from '../../themes';
import { select } from '@storybook/addon-knobs';
import { BarChartOptions, BarValueVisibility } from './types';
import { StackingMode } from '../uPlot/config';
@ -42,7 +42,7 @@ const getKnobs = () => {
export const Basic: React.FC = () => {
const { legendPlacement, orientation } = getKnobs();
const theme = useTheme();
const theme = useTheme2();
const frame = toDataFrame({
fields: [
{ name: 'x', type: FieldType.string, values: ['group 1', 'group 2'] },

View File

@ -2,12 +2,12 @@ import React from 'react';
import { AlignedData } from 'uplot';
import { DataFrame, TimeRange } from '@grafana/data';
import { VizLayout } from '../VizLayout/VizLayout';
import { Themeable } from '../../types';
import { Themeable2 } from '../../types';
import { UPlotChart } from '../uPlot/Plot';
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
import { GraphNGLegendEvent } from '../GraphNG/types';
import { BarChartOptions } from './types';
import { withTheme } from '../../themes';
import { withTheme2 } from '../../themes/ThemeContext';
import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
import { pluginLog, preparePlotData } from '../uPlot/utils';
import { LegendDisplayMode } from '../VizLegend/models.gen';
@ -16,7 +16,7 @@ import { PlotLegend } from '../uPlot/PlotLegend';
/**
* @alpha
*/
export interface BarChartProps extends Themeable, BarChartOptions {
export interface BarChartProps extends Themeable2, BarChartOptions {
height: number;
width: number;
data: DataFrame[];
@ -130,5 +130,5 @@ class UnthemedBarChart extends React.Component<BarChartProps, BarChartState> {
}
}
export const BarChart = withTheme(UnthemedBarChart);
export const BarChart = withTheme2(UnthemedBarChart);
BarChart.displayName = 'GraphNG';

View File

@ -1,5 +1,5 @@
import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
import { FieldConfig, FieldType, GrafanaTheme, MutableDataFrame, VizOrientation } from '@grafana/data';
import { createTheme, FieldConfig, FieldType, MutableDataFrame, VizOrientation } from '@grafana/data';
import { BarChartFieldConfig, BarChartOptions, BarValueVisibility } from './types';
import { GraphGradientMode, StackingMode } from '../uPlot/config';
import { LegendDisplayMode } from '../VizLegend/models.gen';
@ -77,29 +77,28 @@ describe('GraphNG utils', () => {
};
it.each([VizOrientation.Auto, VizOrientation.Horizontal, VizOrientation.Vertical])('orientation', (v) => {
expect(
preparePlotConfigBuilder(frame!, { colors: { panelBg: '#000000' } } as GrafanaTheme, {
...config,
orientation: v,
})
).toMatchSnapshot();
const result = preparePlotConfigBuilder(frame!, createTheme(), {
...config,
orientation: v,
}).getConfig();
expect(result).toMatchSnapshot();
});
it.each([BarValueVisibility.Always, BarValueVisibility.Auto])('value visibility', (v) => {
expect(
preparePlotConfigBuilder(frame!, { colors: { panelBg: '#000000' } } as GrafanaTheme, {
preparePlotConfigBuilder(frame!, createTheme(), {
...config,
showValue: v,
})
}).getConfig()
).toMatchSnapshot();
});
it.each([StackingMode.None, StackingMode.Percent, StackingMode.Normal])('stacking', (v) => {
expect(
preparePlotConfigBuilder(frame!, { colors: { panelBg: '#000000' } } as GrafanaTheme, {
preparePlotConfigBuilder(frame!, createTheme(), {
...config,
stacking: v,
})
}).getConfig()
).toMatchSnapshot();
});
});

View File

@ -6,7 +6,7 @@ import {
getFieldColorModeForField,
getFieldDisplayName,
getFieldSeriesColor,
GrafanaTheme,
GrafanaThemeV2,
MutableDataFrame,
VizOrientation,
} from '@grafana/data';
@ -18,7 +18,7 @@ import { FIXED_UNIT } from '../GraphNG/GraphNG';
/** @alpha */
export function preparePlotConfigBuilder(
data: DataFrame,
theme: GrafanaTheme,
theme: GrafanaThemeV2,
{ orientation, showValue, groupWidth, barWidth }: BarChartOptions
) {
const builder = new UPlotConfigBuilder();

View File

@ -5,7 +5,7 @@ import { VizOrientation, ThresholdsMode, Field, FieldType, getDisplayProcessor }
import { Props } from './BarGauge';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import mdx from './BarGauge.mdx';
import { useTheme } from '../../themes';
import { useTheme2 } from '../../themes';
export default {
title: 'Visualizations/BarGauge',
@ -72,7 +72,7 @@ interface StoryProps extends Partial<Props> {
}
const AddBarGaugeStory = (storyProps: StoryProps) => {
const theme = useTheme();
const theme = useTheme2();
const field: Partial<Field> = {
type: FieldType.number,
@ -89,7 +89,7 @@ const AddBarGaugeStory = (storyProps: StoryProps) => {
},
},
};
field.display = getDisplayProcessor({ field });
field.display = getDisplayProcessor({ field, theme });
const props: Partial<Props> = {
theme,

View File

@ -1,6 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';
import { DisplayValue, VizOrientation, ThresholdsMode, Field, FieldType, getDisplayProcessor } from '@grafana/data';
import {
DisplayValue,
VizOrientation,
ThresholdsMode,
Field,
FieldType,
getDisplayProcessor,
createTheme,
} from '@grafana/data';
import {
BarGauge,
Props,
@ -12,7 +20,6 @@ import {
BarGaugeDisplayMode,
calculateBarAndValueDimensions,
} from './BarGauge';
import { getTheme } from '../../themes';
const green = '#73BF69';
const orange = '#FF9830';
@ -33,7 +40,7 @@ function getProps(propOverrides?: Partial<Props>): Props {
},
},
};
const theme = getTheme();
const theme = createTheme();
field.display = getDisplayProcessor({ field, theme });
const props: Props = {

View File

@ -20,7 +20,7 @@ import {
import { selectors } from '@grafana/e2e-selectors';
import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay';
import { measureText, calculateFontSize } from '../../utils/measureText';
import { Themeable } from '../../types';
import { Themeable2 } from '../../types';
const MIN_VALUE_HEIGHT = 18;
const MAX_VALUE_HEIGHT = 50;
@ -29,7 +29,7 @@ const TITLE_LINE_HEIGHT = 1.5;
const VALUE_LINE_HEIGHT = 1;
const VALUE_LEFT_PADDING = 10;
export interface Props extends Themeable {
export interface Props extends Themeable2 {
height: number;
width: number;
field: FieldConfig;
@ -536,7 +536,7 @@ export function getBarGradient(props: Props, maxSize: number): string {
for (let i = 0; i < thresholds.steps.length; i++) {
const threshold = thresholds.steps[i];
const color = getColorForTheme(threshold.color, props.theme);
const color = getColorForTheme(threshold.color, props.theme.v1);
const valuePercent =
thresholds.mode === ThresholdsMode.Percentage
? threshold.value / 100
@ -561,7 +561,7 @@ export function getBarGradient(props: Props, maxSize: number): string {
}
if (mode.isContinuous && mode.colors) {
const scheme = mode.colors.map((item) => getColorForTheme(item, theme));
const scheme = mode.colors.map((item) => getColorForTheme(item, theme.v1));
for (let i = 0; i < scheme.length; i++) {
const color = scheme[i];

View File

@ -10,7 +10,7 @@ import {
} from './BigValue';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import mdx from './BigValue.mdx';
import { useTheme } from '../../themes';
import { useTheme2 } from '../../themes';
import { ArrayVector, FieldSparkline, FieldType } from '@grafana/data';
export default {
@ -68,7 +68,7 @@ export const Basic: Story<StoryProps> = ({
textMode,
justifyMode,
}) => {
const theme = useTheme();
const theme = useTheme2();
const sparkline: FieldSparkline = {
y: {
name: '',

View File

@ -1,7 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { BigValue, Props, BigValueColorMode, BigValueGraphMode } from './BigValue';
import { getTheme } from '../../themes';
import { createTheme } from '@grafana/data';
function getProps(propOverrides?: Partial<Props>): Props {
const props: Props = {
@ -13,7 +13,7 @@ function getProps(propOverrides?: Partial<Props>): Props {
text: '25',
numeric: 25,
},
theme: getTheme(),
theme: createTheme(),
};
Object.assign(props, propOverrides);

View File

@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
import { DisplayValue, DisplayValueAlignmentFactors, FieldSparkline, TextDisplayOptions } from '@grafana/data';
// Types
import { Themeable } from '../../types';
import { Themeable2 } from '../../types';
import { buildLayout } from './BigValueLayout';
import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay';
@ -34,7 +34,7 @@ export enum BigValueTextMode {
None = 'none',
}
export interface Props extends Themeable {
export interface Props extends Themeable2 {
/** Height of the component */
height: number;
/** Width of the component */

View File

@ -1,7 +1,6 @@
import { Props, BigValueColorMode, BigValueGraphMode } from './BigValue';
import { buildLayout, StackedWithChartLayout, WideWithChartLayout } from './BigValueLayout';
import { getTheme } from '../../themes';
import { ArrayVector, FieldType } from '@grafana/data';
import { ArrayVector, createTheme, FieldType } from '@grafana/data';
function getProps(propOverrides?: Partial<Props>): Props {
const props: Props = {
@ -21,7 +20,7 @@ function getProps(propOverrides?: Partial<Props>): Props {
config: {},
},
},
theme: getTheme(),
theme: createTheme(),
};
Object.assign(props, propOverrides);

View File

@ -32,7 +32,7 @@ export abstract class BigValueLayout {
constructor(private props: Props) {
const { width, height, value, theme, text } = props;
this.valueColor = getColorForTheme(value.color || 'green', theme);
this.valueColor = getColorForTheme(value.color || 'green', theme.v1);
this.panelPadding = height > 100 ? 12 : 8;
this.textValues = getTextValues(props);
this.justifyCenter = shouldJustifyCenter(props.justifyMode, this.textValues.title);

View File

@ -2,7 +2,9 @@ import React from 'react';
import { mount } from 'enzyme';
import Graph from './Graph';
import { VizTooltip, TooltipDisplayMode } from '../VizTooltip';
import { GraphSeriesXY, FieldType, ArrayVector, dateTime, FieldColorModeId } from '@grafana/data';
import { GraphSeriesXY, FieldType, ArrayVector, dateTime, FieldColorModeId, DisplayProcessor } from '@grafana/data';
const display: DisplayProcessor = (v) => ({ numeric: v, text: String(v), color: 'red' });
const series: GraphSeriesXY[] = [
{
@ -26,6 +28,7 @@ const series: GraphSeriesXY[] = [
name: 'a-series',
values: new ArrayVector([10, 20, 10]),
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'red' } },
display,
},
timeStep: 3600000,
yAxis: {
@ -53,6 +56,7 @@ const series: GraphSeriesXY[] = [
name: 'b-series',
values: new ArrayVector([20, 30, 40]),
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'blue' } },
display,
},
timeStep: 3600000,
yAxis: {

View File

@ -4,7 +4,6 @@ import { GraphDimensions } from './GraphTooltip/types';
import {
FlotDataPoint,
getValueFromDimension,
getDisplayProcessor,
Dimensions,
dateTimeFormat,
TimeZone,
@ -60,12 +59,7 @@ export const GraphContextMenu: React.FC<GraphContextMenuProps> = ({
contextDimensions.yAxis[0],
contextDimensions.yAxis[1]
);
const display =
source.series.valueField.display ??
getDisplayProcessor({
field: source.series.valueField,
timeZone,
});
const display = source.series.valueField.display!;
value = display(valueFromDimensions);
}

View File

@ -1,13 +1,15 @@
import React from 'react';
import { mount } from 'enzyme';
import { MultiModeGraphTooltip } from './MultiModeGraphTooltip';
import { createDimension, ArrayVector, FieldType } from '@grafana/data';
import { createDimension, ArrayVector, FieldType, DisplayProcessor } from '@grafana/data';
import { GraphDimensions } from './types';
import { ActiveDimensions } from '../../VizTooltip';
let dimensions: GraphDimensions;
describe('MultiModeGraphTooltip', () => {
const display: DisplayProcessor = (v) => ({ numeric: v, text: String(v), color: 'red' });
describe('when shown when hovering over a datapoint', () => {
beforeEach(() => {
dimensions = {
@ -17,12 +19,14 @@ describe('MultiModeGraphTooltip', () => {
values: new ArrayVector([0, 100, 200]),
name: 'A-series time',
type: FieldType.time,
display,
},
{
config: {},
values: new ArrayVector([0, 100, 200]),
name: 'B-series time',
type: FieldType.time,
display,
},
]),
yAxis: createDimension('yAxis', [
@ -31,12 +35,14 @@ describe('MultiModeGraphTooltip', () => {
values: new ArrayVector([10, 20, 10]),
name: 'A-series values',
type: FieldType.number,
display,
},
{
config: {},
values: new ArrayVector([20, 30, 40]),
name: 'B-series values',
type: FieldType.number,
display,
},
]),
};

View File

@ -3,7 +3,6 @@ import {
getValueFromDimension,
getColumnFromDimension,
formattedValueToString,
getDisplayProcessor,
getFieldDisplayName,
} from '@grafana/data';
import { SeriesTable } from '../../VizTooltip';
@ -29,7 +28,7 @@ export const SingleModeGraphTooltip: React.FC<GraphTooltipContentProps> = ({
const valueField = getColumnFromDimension(dimensions.yAxis, activeDimensions.yAxis[0]);
const value = getValueFromDimension(dimensions.yAxis, activeDimensions.yAxis[0], activeDimensions.yAxis[1]);
const display = valueField.display ?? getDisplayProcessor({ field: valueField, timeZone });
const display = valueField.display!;
const disp = display(value);
return (

View File

@ -1,11 +1,13 @@
import {
GraphSeriesValue,
toDataFrame,
FieldType,
FieldCache,
FieldColorModeId,
Field,
getColorForTheme,
applyFieldOverrides,
createTheme,
DataFrame,
} from '@grafana/data';
import { getTheme } from '../../themes';
import { getMultiSeriesGraphHoverInfo, findHoverIndexFromData, graphTimeFormat } from './utils';
@ -16,7 +18,7 @@ const mockResult = (
seriesIndex: number,
color?: string,
label?: string,
time?: GraphSeriesValue
time?: string
) => ({
value,
datapointIndex,
@ -26,41 +28,62 @@ const mockResult = (
time,
});
function passThroughFieldOverrides(frame: DataFrame) {
return applyFieldOverrides({
data: [frame],
fieldConfig: {
defaults: {},
overrides: [],
},
replaceVariables: (val: string) => val,
timeZone: 'utc',
theme: createTheme(),
});
}
// A and B series have the same x-axis range and the datapoints are x-axis aligned
const aSeries = toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
{
name: 'value',
type: FieldType.number,
values: [10, 20, 10],
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'red' } },
},
],
});
const bSeries = toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
{
name: 'value',
type: FieldType.number,
values: [30, 60, 30],
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'blue' } },
},
],
});
const aSeries = passThroughFieldOverrides(
toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [10000, 20000, 30000] },
{
name: 'value',
type: FieldType.number,
values: [10, 20, 10],
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'red' } },
},
],
})
)[0];
const bSeries = passThroughFieldOverrides(
toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [10000, 20000, 30000] },
{
name: 'value',
type: FieldType.number,
values: [30, 60, 30],
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'blue' } },
},
],
})
)[0];
// C-series has the same x-axis range as A and B but is missing the middle point
const cSeries = toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [100, 300] },
{
name: 'value',
type: FieldType.number,
values: [30, 30],
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'yellow' } },
},
],
});
const cSeries = passThroughFieldOverrides(
toDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [10000, 30000] },
{
name: 'value',
type: FieldType.number,
values: [30, 30],
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'yellow' } },
},
],
})
)[0];
function getFixedThemedColor(field: Field): string {
return getColorForTheme(field.config.color!.fixedColor!, getTheme());
@ -78,12 +101,12 @@ describe('Graph utils', () => {
const bTimeField = bCache.getFieldByName('time');
const result = getMultiSeriesGraphHoverInfo([aValueField!, bValueField!], [aTimeField!, bTimeField!], 0);
expect(result.time).toBe(100);
expect(result.time).toBe('1970-01-01 00:00:10');
expect(result.results[0]).toEqual(
mockResult('10', 0, 0, getFixedThemedColor(aValueField!), aValueField!.name, 100)
mockResult('10', 0, 0, getFixedThemedColor(aValueField!), aValueField!.name, '1970-01-01 00:00:10')
);
expect(result.results[1]).toEqual(
mockResult('30', 0, 1, getFixedThemedColor(bValueField!), bValueField!.name, 100)
mockResult('30', 0, 1, getFixedThemedColor(bValueField!), bValueField!.name, '1970-01-01 00:00:10')
);
});
@ -97,13 +120,13 @@ describe('Graph utils', () => {
const bTimeField = bCache.getFieldByName('time');
// hovering right before middle point
const result = getMultiSeriesGraphHoverInfo([aValueField!, bValueField!], [aTimeField!, bTimeField!], 199);
expect(result.time).toBe(100);
const result = getMultiSeriesGraphHoverInfo([aValueField!, bValueField!], [aTimeField!, bTimeField!], 19900);
expect(result.time).toBe('1970-01-01 00:00:10');
expect(result.results[0]).toEqual(
mockResult('10', 0, 0, getFixedThemedColor(aValueField!), aValueField!.name, 100)
mockResult('10', 0, 0, getFixedThemedColor(aValueField!), aValueField!.name, '1970-01-01 00:00:10')
);
expect(result.results[1]).toEqual(
mockResult('30', 0, 1, getFixedThemedColor(bValueField!), bValueField!.name, 100)
mockResult('30', 0, 1, getFixedThemedColor(bValueField!), bValueField!.name, '1970-01-01 00:00:10')
);
});
@ -116,13 +139,13 @@ describe('Graph utils', () => {
const bTimeField = bCache.getFieldByName('time');
// hovering right after middle point
const result = getMultiSeriesGraphHoverInfo([aValueField!, bValueField!], [aTimeField!, bTimeField!], 201);
expect(result.time).toBe(200);
const result = getMultiSeriesGraphHoverInfo([aValueField!, bValueField!], [aTimeField!, bTimeField!], 20100);
expect(result.time).toBe('1970-01-01 00:00:20');
expect(result.results[0]).toEqual(
mockResult('20', 1, 0, getFixedThemedColor(aValueField!), aValueField!.name, 200)
mockResult('20', 1, 0, getFixedThemedColor(aValueField!), aValueField!.name, '1970-01-01 00:00:20')
);
expect(result.results[1]).toEqual(
mockResult('60', 1, 1, getFixedThemedColor(bValueField!), bValueField!.name, 200)
mockResult('60', 1, 1, getFixedThemedColor(bValueField!), bValueField!.name, '1970-01-01 00:00:20')
);
});
});
@ -141,17 +164,17 @@ describe('Graph utils', () => {
// hovering on a middle point
// aSeries has point at that time, cSeries doesn't
const result = getMultiSeriesGraphHoverInfo([aValueField!, cValueField!], [aTimeField!, cTimeField!], 200);
const result = getMultiSeriesGraphHoverInfo([aValueField!, cValueField!], [aTimeField!, cTimeField!], 20000);
// we expect a time of the hovered point
expect(result.time).toBe(200);
expect(result.time).toBe('1970-01-01 00:00:20');
// we expect middle point from aSeries (the one we are hovering over)
expect(result.results[0]).toEqual(
mockResult('20', 1, 0, getFixedThemedColor(aValueField!), aValueField!.name, 200)
mockResult('20', 1, 0, getFixedThemedColor(aValueField!), aValueField!.name, '1970-01-01 00:00:20')
);
// we expect closest point before hovered point from cSeries (1st point)
expect(result.results[1]).toEqual(
mockResult('30', 0, 1, getFixedThemedColor(cValueField!), cValueField!.name, 100)
mockResult('30', 0, 1, getFixedThemedColor(cValueField!), cValueField!.name, '1970-01-01 00:00:10')
);
});
@ -164,17 +187,17 @@ describe('Graph utils', () => {
const cTimeField = cCache.getFieldByName('time');
// aSeries has point at that time, cSeries doesn't
const result = getMultiSeriesGraphHoverInfo([aValueField!, cValueField!], [aTimeField!, cTimeField!], 201);
const result = getMultiSeriesGraphHoverInfo([aValueField!, cValueField!], [aTimeField!, cTimeField!], 20100);
// we expect the time of the closest point before hover
expect(result.time).toBe(200);
expect(result.time).toBe('1970-01-01 00:00:20');
// we expect the closest datapoint before hover from aSeries
expect(result.results[0]).toEqual(
mockResult('20', 1, 0, getFixedThemedColor(aValueField!), aValueField!.name, 200)
mockResult('20', 1, 0, getFixedThemedColor(aValueField!), aValueField!.name, '1970-01-01 00:00:20')
);
// we expect the closest datapoint before hover from cSeries (1st point)
expect(result.results[1]).toEqual(
mockResult('30', 0, 1, getFixedThemedColor(cValueField!), cValueField!.name, 100)
mockResult('30', 0, 1, getFixedThemedColor(cValueField!), cValueField!.name, '1970-01-01 00:00:10')
);
});
});
@ -187,13 +210,13 @@ describe('Graph utils', () => {
// hovering over 1st datapoint
expect(findHoverIndexFromData(timeField!, 0)).toBe(0);
// hovering over right before 2nd datapoint
expect(findHoverIndexFromData(timeField!, 199)).toBe(0);
expect(findHoverIndexFromData(timeField!, 19900)).toBe(0);
// hovering over 2nd datapoint
expect(findHoverIndexFromData(timeField!, 200)).toBe(1);
expect(findHoverIndexFromData(timeField!, 20000)).toBe(1);
// hovering over right before 3rd datapoint
expect(findHoverIndexFromData(timeField!, 299)).toBe(1);
expect(findHoverIndexFromData(timeField!, 29900)).toBe(1);
// hovering over 3rd datapoint
expect(findHoverIndexFromData(timeField!, 300)).toBe(2);
expect(findHoverIndexFromData(timeField!, 30000)).toBe(2);
});
});

View File

@ -2,7 +2,6 @@ import {
GraphSeriesValue,
Field,
formattedValueToString,
getDisplayProcessor,
getFieldDisplayName,
TimeZone,
dateTimeFormat,
@ -85,8 +84,7 @@ export const getMultiSeriesGraphHoverInfo = (
minTime = time.display ? formattedValueToString(time.display(pointTime)) : pointTime;
}
const display = field.display ?? getDisplayProcessor({ field, timeZone });
const disp = display(field.values.get(hoverIndex));
const disp = field.display!(field.values.get(hoverIndex));
results.push({
value: formattedValueToString(disp),

View File

@ -4,7 +4,7 @@ import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { GraphNG, GraphNGProps } from './GraphNG';
import { LegendDisplayMode, LegendPlacement } from '../VizLegend/models.gen';
import { prepDataForStorybook } from '../../utils/storybook/data';
import { useTheme } from '../../themes';
import { useTheme2 } from '../../themes';
import { Story } from '@storybook/react';
export default {
@ -34,7 +34,7 @@ interface StoryProps extends GraphNGProps {
unit: string;
}
export const Lines: Story<StoryProps> = ({ placement, unit, legendDisplayMode, ...args }) => {
const theme = useTheme();
const theme = useTheme2();
const seriesA = toDataFrame({
target: 'SeriesA',
datapoints: [

View File

@ -1,8 +1,7 @@
import React from 'react';
import { AlignedData } from 'uplot';
import { DataFrame, FieldMatcherID, fieldMatchers, TimeRange, TimeZone } from '@grafana/data';
import { withTheme } from '../../themes';
import { Themeable } from '../../types';
import { Themeable2 } from '../../types';
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
import { GraphNGLegendEvent, XYFieldMatchers } from './types';
import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
@ -11,13 +10,14 @@ import { PlotLegend } from '../uPlot/PlotLegend';
import { UPlotChart } from '../uPlot/Plot';
import { LegendDisplayMode, VizLegendOptions } from '../VizLegend/models.gen';
import { VizLayout } from '../VizLayout/VizLayout';
import { withTheme2 } from '../../themes/ThemeContext';
/**
* @internal -- not a public API
*/
export const FIXED_UNIT = '__fixed';
export interface GraphNGProps extends Themeable {
export interface GraphNGProps extends Themeable2 {
width: number;
height: number;
data: DataFrame[];
@ -158,5 +158,5 @@ class UnthemedGraphNG extends React.Component<GraphNGProps, GraphNGState> {
}
}
export const GraphNG = withTheme(UnthemedGraphNG);
export const GraphNG = withTheme2(UnthemedGraphNG);
GraphNG.displayName = 'GraphNG';

View File

@ -1,36 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GraphNG utils preparePlotConfigBuilder 1`] = `
UPlotConfigBuilder {
"axes": Object {
"__fixed": UPlotAxisBuilder {
"props": Object {
"formatValue": [Function],
"label": undefined,
"placement": "left",
"scaleKey": "__fixed",
"size": undefined,
"theme": Object {
"colors": Object {
"panelBg": "#000000",
},
},
Object {
"axes": Array [
Object {
"font": "12px \\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
"gap": 5,
"grid": Object {
"show": true,
"stroke": "rgba(240, 250, 255, 0.09)",
"width": 1,
},
},
"x": UPlotAxisBuilder {
"props": Object {
"isTime": true,
"placement": "bottom",
"scaleKey": "x",
"theme": Object {
"colors": Object {
"panelBg": "#000000",
},
},
"timeZone": "utc",
"labelFont": "12px \\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
"scale": "x",
"show": true,
"side": 2,
"size": [Function],
"space": [Function],
"splits": undefined,
"stroke": "rgb(201, 209, 217)",
"ticks": Object {
"show": true,
"stroke": "rgba(240, 250, 255, 0.09)",
"width": 1,
},
"timeZone": "utc",
"values": [Function],
},
},
Object {
"font": "12px \\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
"gap": 5,
"grid": Object {
"show": true,
"stroke": "rgba(240, 250, 255, 0.09)",
"width": 1,
},
"labelFont": "12px \\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif",
"scale": "__fixed",
"show": true,
"side": 3,
"size": [Function],
"space": [Function],
"splits": undefined,
"stroke": "rgb(201, 209, 217)",
"ticks": Object {
"show": true,
"stroke": "rgba(240, 250, 255, 0.09)",
"width": 1,
},
"timeZone": undefined,
"values": [Function],
},
],
"bands": Array [
Object {
"series": Array [
@ -45,252 +66,143 @@ UPlotConfigBuilder {
],
},
],
"hasBottomAxis": true,
"hasLeftAxis": true,
"cursor": Object {
"drag": Object {
"setScale": false,
},
"focus": Object {
"prox": 30,
},
"points": Object {
"fill": [Function],
"size": [Function],
"stroke": [Function],
"width": [Function],
},
},
"hooks": Object {},
"isStacking": true,
"scales": Array [
UPlotScaleBuilder {
"props": Object {
"direction": 1,
"isTime": true,
"orientation": 0,
"range": [Function],
"scaleKey": "x",
},
"scales": Object {
"__fixed": Object {
"auto": true,
"dir": 1,
"distr": 1,
"log": undefined,
"ori": 1,
"range": [Function],
"time": undefined,
},
UPlotScaleBuilder {
"props": Object {
"direction": 1,
"distribution": undefined,
"log": undefined,
"max": undefined,
"min": undefined,
"orientation": 1,
"scaleKey": "__fixed",
"softMax": undefined,
"softMin": undefined,
},
"x": Object {
"auto": false,
"dir": 1,
"ori": 0,
"range": [Function],
"time": true,
},
],
},
"select": undefined,
"series": Array [
UPlotSeriesBuilder {
"props": Object {
"barAlignment": undefined,
"colorMode": Object {
"description": "Derive colors from thresholds",
"getCalculator": [Function],
"id": "thresholds",
"isByValue": true,
"name": "From thresholds",
},
"dataFrameFieldIndex": Object {
"fieldIndex": 1,
"frameIndex": 0,
},
"drawStyle": "line",
"fieldName": "Metric 1",
"fillOpacity": 0.1,
"gradientMode": "opacity",
"hideInLegend": undefined,
"lineColor": "#ff0000",
"lineInterpolation": "linear",
"lineStyle": Object {
"dash": Array [
1,
2,
],
"fill": "dash",
},
"lineWidth": 2,
"pointColor": "#808080",
"pointSize": undefined,
"scaleKey": "__fixed",
Object {},
Object {
"dash": Array [
1,
2,
],
"fill": [Function],
"paths": [Function],
"points": Object {
"fill": "#808080",
"show": true,
"showPoints": "always",
"spanNulls": false,
"theme": Object {
"colors": Object {
"panelBg": "#000000",
},
},
"thresholds": undefined,
"size": undefined,
"stroke": "#808080",
},
"pxAlign": undefined,
"scale": "__fixed",
"show": true,
"spanGaps": false,
"stroke": "#ff0000",
"width": 2,
},
UPlotSeriesBuilder {
"props": Object {
"barAlignment": -1,
"colorMode": Object {
"description": "Derive colors from thresholds",
"getCalculator": [Function],
"id": "thresholds",
"isByValue": true,
"name": "From thresholds",
},
"dataFrameFieldIndex": Object {
"fieldIndex": 1,
"frameIndex": 1,
},
"drawStyle": "bars",
"fieldName": "Metric 2",
"fillOpacity": 0.1,
"gradientMode": "hue",
"hideInLegend": undefined,
"lineColor": "#ff0000",
"lineInterpolation": "linear",
"lineStyle": Object {
"dash": Array [
1,
2,
],
"fill": "dash",
},
"lineWidth": 2,
"pointColor": "#808080",
"pointSize": undefined,
"scaleKey": "__fixed",
Object {
"dash": Array [
1,
2,
],
"fill": [Function],
"paths": [Function],
"points": Object {
"fill": "#808080",
"show": true,
"showPoints": "always",
"spanNulls": false,
"theme": Object {
"colors": Object {
"panelBg": "#000000",
},
},
"thresholds": undefined,
"size": undefined,
"stroke": "#808080",
},
"pxAlign": undefined,
"scale": "__fixed",
"show": true,
"spanGaps": false,
"stroke": "#ff0000",
"width": 2,
},
UPlotSeriesBuilder {
"props": Object {
"barAlignment": undefined,
"colorMode": Object {
"description": "Derive colors from thresholds",
"getCalculator": [Function],
"id": "thresholds",
"isByValue": true,
"name": "From thresholds",
},
"dataFrameFieldIndex": Object {
"fieldIndex": 2,
"frameIndex": 1,
},
"drawStyle": "line",
"fieldName": "Metric 3",
"fillOpacity": 0.1,
"gradientMode": "opacity",
"hideInLegend": undefined,
"lineColor": "#ff0000",
"lineInterpolation": "linear",
"lineStyle": Object {
"dash": Array [
1,
2,
],
"fill": "dash",
},
"lineWidth": 2,
"pointColor": "#808080",
"pointSize": undefined,
"scaleKey": "__fixed",
Object {
"dash": Array [
1,
2,
],
"fill": [Function],
"paths": [Function],
"points": Object {
"fill": "#808080",
"show": true,
"showPoints": "always",
"spanNulls": false,
"theme": Object {
"colors": Object {
"panelBg": "#000000",
},
},
"thresholds": undefined,
"size": undefined,
"stroke": "#808080",
},
"pxAlign": undefined,
"scale": "__fixed",
"show": true,
"spanGaps": false,
"stroke": "#ff0000",
"width": 2,
},
UPlotSeriesBuilder {
"props": Object {
"barAlignment": -1,
"colorMode": Object {
"description": "Derive colors from thresholds",
"getCalculator": [Function],
"id": "thresholds",
"isByValue": true,
"name": "From thresholds",
},
"dataFrameFieldIndex": Object {
"fieldIndex": 3,
"frameIndex": 1,
},
"drawStyle": "bars",
"fieldName": "Metric 4",
"fillOpacity": 0.1,
"gradientMode": "hue",
"hideInLegend": undefined,
"lineColor": "#ff0000",
"lineInterpolation": "linear",
"lineStyle": Object {
"dash": Array [
1,
2,
],
"fill": "dash",
},
"lineWidth": 2,
"pointColor": "#808080",
"pointSize": undefined,
"scaleKey": "__fixed",
Object {
"dash": Array [
1,
2,
],
"fill": [Function],
"paths": [Function],
"points": Object {
"fill": "#808080",
"show": true,
"showPoints": "always",
"spanNulls": false,
"theme": Object {
"colors": Object {
"panelBg": "#000000",
},
},
"thresholds": undefined,
"size": undefined,
"stroke": "#808080",
},
"pxAlign": undefined,
"scale": "__fixed",
"show": true,
"spanGaps": false,
"stroke": "#ff0000",
"width": 2,
},
UPlotSeriesBuilder {
"props": Object {
"barAlignment": -1,
"colorMode": Object {
"description": "Derive colors from thresholds",
"getCalculator": [Function],
"id": "thresholds",
"isByValue": true,
"name": "From thresholds",
},
"dataFrameFieldIndex": Object {
"fieldIndex": 4,
"frameIndex": 1,
},
"drawStyle": "bars",
"fieldName": "Metric 4",
"fillOpacity": 0.1,
"gradientMode": "hue",
"hideInLegend": undefined,
"lineColor": "#ff0000",
"lineInterpolation": "linear",
"lineStyle": Object {
"dash": Array [
1,
2,
],
"fill": "dash",
},
"lineWidth": 2,
"pointColor": "#808080",
"pointSize": undefined,
"scaleKey": "__fixed",
Object {
"dash": Array [
1,
2,
],
"fill": [Function],
"paths": [Function],
"points": Object {
"fill": "#808080",
"show": true,
"showPoints": "always",
"spanNulls": false,
"theme": Object {
"colors": Object {
"panelBg": "#000000",
},
},
"thresholds": undefined,
"size": undefined,
"stroke": "#808080",
},
"pxAlign": undefined,
"scale": "__fixed",
"show": true,
"spanGaps": false,
"stroke": "#ff0000",
"width": 2,
},
],
"tz": "UTC",
"tzDate": [Function],
}
`;

View File

@ -1,12 +1,12 @@
import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
import {
createTheme,
DefaultTimeZone,
FieldConfig,
FieldMatcherID,
fieldMatchers,
FieldType,
getDefaultTimeRange,
GrafanaTheme,
MutableDataFrame,
} from '@grafana/data';
import {
@ -188,13 +188,12 @@ jest.mock('@grafana/data', () => ({
describe('GraphNG utils', () => {
test('preparePlotConfigBuilder', () => {
const frame = mockDataFrame();
expect(
preparePlotConfigBuilder(
frame!,
{ colors: { panelBg: '#000000' } } as GrafanaTheme,
getDefaultTimeRange,
() => DefaultTimeZone
)
).toMatchSnapshot();
const result = preparePlotConfigBuilder(
frame!,
createTheme(),
getDefaultTimeRange,
() => DefaultTimeZone
).getConfig();
expect(result).toMatchSnapshot();
});
});

View File

@ -10,7 +10,7 @@ import {
getFieldColorModeForField,
getFieldDisplayName,
getFieldSeriesColor,
GrafanaTheme,
GrafanaThemeV2,
outerJoinDataFrames,
TimeRange,
TimeZone,
@ -83,7 +83,7 @@ export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers
export function preparePlotConfigBuilder(
frame: DataFrame,
theme: GrafanaTheme,
theme: GrafanaThemeV2,
getTimeRange: () => TimeRange,
getTimeZone: () => TimeZone
): UPlotConfigBuilder {

View File

@ -6,11 +6,11 @@ import { EdgeDatum, NodeDatum } from './types';
import { Node } from './Node';
import { Edge } from './Edge';
import { ViewControls } from './ViewControls';
import { DataFrame, GrafanaTheme, LinkModel } from '@grafana/data';
import { DataFrame, GrafanaThemeV2, LinkModel } from '@grafana/data';
import { useZoom } from './useZoom';
import { Bounds, Config, defaultConfig, useLayout } from './layout';
import { EdgeArrowMarker } from './EdgeArrowMarker';
import { stylesFactory, useTheme } from '../../themes';
import { stylesFactory, useTheme2 } from '../../themes';
import { css } from '@emotion/css';
import { useCategorizeFrames } from './useCategorizeFrames';
import { EdgeLabel } from './EdgeLabel';
@ -19,7 +19,7 @@ import { processNodes } from './utils';
import { Icon } from '..';
import { useNodeLimit } from './useNodeLimit';
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
const getStyles = stylesFactory((theme: GrafanaThemeV2) => ({
wrapper: css`
height: 100%;
width: 100%;
@ -52,13 +52,13 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
padding: 5px 8px;
font-size: 10px;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
border-radius: ${theme.border.radius.md};
border-radius: ${theme.shape.borderRadius()};
align-items: center;
position: absolute;
top: 0;
right: 0;
background: ${theme.palette.warn};
color: ${theme.palette.white};
background: ${theme.colors.warning.main};
color: ${theme.colors.warning.contrastText};
`,
}));
@ -87,7 +87,7 @@ export function NodeGraph({ getLinks, dataFrames, nodeLimit }: Props) {
const firstNodesDataFrame = nodesDataFrames[0];
const firstEdgesDataFrame = edgesDataFrames[0];
const theme = useTheme();
const theme = useTheme2();
// TODO we should be able to allow multiple dataframes for both edges and nodes, could be issue with node ids which in
// that case should be unique or figure a way to link edges and nodes dataframes together.

View File

@ -5,7 +5,7 @@ describe('processNodes', () => {
const theme = createTheme();
it('handles empty args', async () => {
expect(processNodes(undefined, undefined, theme.v1)).toEqual({ nodes: [], edges: [] });
expect(processNodes(undefined, undefined, theme)).toEqual({ nodes: [], edges: [] });
});
it('returns proper nodes and edges', async () => {
@ -17,7 +17,7 @@ describe('processNodes', () => {
[0, 2],
[1, 2],
]),
theme.v1
theme
)
).toEqual({
nodes: [

View File

@ -5,7 +5,7 @@ import {
FieldCache,
FieldType,
getFieldColorModeForField,
GrafanaTheme,
GrafanaThemeV2,
MutableDataFrame,
} from '@grafana/data';
import { EdgeDatum, NodeDatum } from './types';
@ -84,7 +84,7 @@ export enum DataFrameFieldNames {
export function processNodes(
nodes: DataFrame | undefined,
edges: DataFrame | undefined,
theme: GrafanaTheme
theme: GrafanaThemeV2
): { nodes: NodeDatum[]; edges: EdgeDatum[] } {
if (!nodes) {
return { nodes: [], edges: [] };
@ -276,7 +276,7 @@ function edgesFrame() {
});
}
function getColor(field: Field, index: number, theme: GrafanaTheme): string {
function getColor(field: Field, index: number, theme: GrafanaThemeV2): string {
if (!field.config.color) {
return field.values.get(index);
}

View File

@ -65,7 +65,7 @@ export function PieChart(props: PieChartProps) {
fieldConfig,
reduceOptions,
data,
theme: theme.v1,
theme: theme,
replaceVariables,
timeZone,
});

View File

@ -19,11 +19,11 @@ import {
} from '../uPlot/config';
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
import { UPlotChart } from '../uPlot/Plot';
import { Themeable } from '../../types';
import { Themeable2 } from '../../types';
import { preparePlotData } from '../uPlot/utils';
import { preparePlotFrame } from './utils';
export interface SparklineProps extends Themeable {
export interface SparklineProps extends Themeable2 {
width: number;
height: number;
config?: FieldConfig<GraphFieldConfig>;
@ -144,6 +144,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
min: field.config.min,
max: field.config.max,
});
builder.addAxis({
scaleKey,
theme,

View File

@ -3,12 +3,12 @@ import { merge } from 'lodash';
import { Table } from '@grafana/ui';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { number } from '@storybook/addon-knobs';
import { useTheme } from '../../themes';
import { useTheme2 } from '../../themes';
import mdx from './Table.mdx';
import {
DataFrame,
FieldType,
GrafanaTheme,
GrafanaThemeV2,
MutableDataFrame,
ThresholdsConfig,
ThresholdsMode,
@ -27,7 +27,7 @@ export default {
},
};
function buildData(theme: GrafanaTheme, config: Record<string, FieldConfig>): DataFrame {
function buildData(theme: GrafanaThemeV2, config: Record<string, FieldConfig>): DataFrame {
const data = new MutableDataFrame({
fields: [
{ name: 'Time', type: FieldType.time, values: [] }, // The time field
@ -100,7 +100,7 @@ const defaultThresholds: ThresholdsConfig = {
};
export const Simple = () => {
const theme = useTheme();
const theme = useTheme2();
const width = number('width', 700, {}, 'Props');
const data = buildData(theme, {});
@ -112,7 +112,7 @@ export const Simple = () => {
};
export const BarGaugeCell = () => {
const theme = useTheme();
const theme = useTheme2();
const width = number('width', 700, {}, 'Props');
const data = buildData(theme, {
Progress: {
@ -132,7 +132,7 @@ export const BarGaugeCell = () => {
};
export const ColoredCells = () => {
const theme = useTheme();
const theme = useTheme2();
const width = number('width', 750, {}, 'Props');
const data = buildData(theme, {
Progress: {

View File

@ -1,10 +1,8 @@
import React from 'react';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { applyFieldOverrides, DataFrame, FieldType, toDataFrame } from '@grafana/data';
import { applyFieldOverrides, createTheme, DataFrame, FieldType, toDataFrame } from '@grafana/data';
import { Props, Table } from './Table';
import { getTheme } from '../../themes';
function getDefaultDataFrame(): DataFrame {
const dataFrame = toDataFrame({
@ -67,7 +65,7 @@ function getDefaultDataFrame(): DataFrame {
return vars && value === '${__value.text}' ? vars['__value'].value.text : value;
},
timeZone: 'utc',
theme: getTheme(),
theme: createTheme(),
});
return dataFrames[0];
}

View File

@ -15,7 +15,6 @@ import {
} from 'react-table';
import { FixedSizeList } from 'react-window';
import { getColumns, sortCaseInsensitive } from './utils';
import { useTheme } from '../../themes';
import {
TableColumnResizeActionCallback,
TableFilterActionCallback,
@ -27,6 +26,7 @@ import { Icon } from '../Icon/Icon';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { Filter } from './Filter';
import { TableCell } from './TableCell';
import { useStyles2 } from '../../themes';
const COLUMN_MIN_WIDTH = 150;
@ -123,8 +123,7 @@ export const Table: FC<Props> = memo((props: Props) => {
resizable = true,
initialSortBy,
} = props;
const theme = useTheme();
const tableStyles = getTableStyles(theme);
const tableStyles = useStyles2(getTableStyles);
// React table data array. This data acts just like a dummy array to let react-table know how many rows exist
// The cells use the field to look up values

View File

@ -1,18 +1,17 @@
import { css, cx } from '@emotion/css';
import { GrafanaTheme } from '@grafana/data';
import { styleMixins, stylesFactory } from '../../themes';
import { GrafanaThemeV2 } from '@grafana/data';
import { getScrollbarWidth } from '../../utils';
export const getTableStyles = stylesFactory((theme: GrafanaTheme) => {
const { palette, colors } = theme;
const headerBg = theme.colors.bg2;
const borderColor = theme.colors.border1;
const resizerColor = theme.isLight ? palette.blue95 : palette.blue77;
export const getTableStyles = (theme: GrafanaThemeV2) => {
const { colors } = theme;
const headerBg = theme.colors.background.secondary;
const borderColor = theme.colors.border.weak;
const resizerColor = theme.colors.primary.border;
const cellPadding = 6;
const lineHeight = theme.typography.lineHeight.md;
const lineHeight = theme.typography.body.lineHeight;
const bodyFontSize = 14;
const cellHeight = cellPadding * 2 + bodyFontSize * lineHeight;
const rowHoverBg = styleMixins.hoverColor(theme.colors.bg1, theme);
const rowHoverBg = theme.colors.emphasize(theme.colors.background.primary, 0.03);
const lastChildExtraPadding = Math.max(getScrollbarWidth(), cellPadding);
const buildCellContainerStyle = (color?: string, background?: string) => {
@ -35,7 +34,7 @@ export const getTableStyles = stylesFactory((theme: GrafanaTheme) => {
&:hover {
overflow: visible;
width: auto !important;
box-shadow: 0 0 2px ${theme.colors.formFocusOutline};
box-shadow: 0 0 2px ${theme.colors.primary.main};
background: ${background ?? rowHoverBg};
z-index: 1;
@ -72,8 +71,8 @@ export const getTableStyles = stylesFactory((theme: GrafanaTheme) => {
padding: ${cellPadding}px;
overflow: hidden;
white-space: nowrap;
color: ${colors.textBlue};
border-right: 1px solid ${theme.colors.panelBg};
color: ${colors.primary.text};
border-right: 1px solid ${theme.colors.border.weak};
display: flex;
&:last-child {
@ -86,7 +85,7 @@ export const getTableStyles = stylesFactory((theme: GrafanaTheme) => {
overflow: hidden;
text-overflow: ellipsis;
display: flex;
margin-right: ${theme.spacing.xs};
margin-right: ${theme.spacing(0.5)};
`,
cellContainer: buildCellContainerStyle(),
cellText: css`
@ -145,14 +144,14 @@ export const getTableStyles = stylesFactory((theme: GrafanaTheme) => {
justify-content: flex-end;
flex-grow: 1;
opacity: 0.6;
padding-left: ${theme.spacing.xxs};
padding-left: ${theme.spacing(0.25)};
`,
'cell-filter-actions'
),
filterItem: css`
label: filterItem;
cursor: pointer;
padding: 0 ${theme.spacing.xxs};
padding: 0 ${theme.spacing(0.025)};
`,
noData: css`
align-items: center;
@ -162,6 +161,6 @@ export const getTableStyles = stylesFactory((theme: GrafanaTheme) => {
width: 100%;
`,
};
});
};
export type TableStyles = ReturnType<typeof getTableStyles>;

View File

@ -1,6 +1,6 @@
import React from 'react';
import { FieldMatcherID, fieldMatchers } from '@grafana/data';
import { withTheme } from '../../themes';
import { withTheme2 } from '../../themes/ThemeContext';
import { GraphNGState } from '../GraphNG/GraphNG';
import { preparePlotConfigBuilder, preparePlotFrame } from './utils'; // << preparePlotConfigBuilder is really the only change vs GraphNG
import { pluginLog, preparePlotData } from '../uPlot/utils';
@ -146,5 +146,5 @@ class UnthemedTimelineChart extends React.Component<TimelineProps, GraphNGState>
}
}
export const TimelineChart = withTheme(UnthemedTimelineChart);
export const TimelineChart = withTheme2(UnthemedTimelineChart);
TimelineChart.displayName = 'TimelineChart';

View File

@ -6,12 +6,12 @@ import {
FieldConfig,
formattedValueToString,
getFieldDisplayName,
GrafanaTheme,
outerJoinDataFrames,
TimeRange,
TimeZone,
classicColors,
Field,
GrafanaThemeV2,
} from '@grafana/data';
import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder';
import { TimelineCoreOptions, getConfig } from './timeline';
@ -45,14 +45,14 @@ export function preparePlotFrame(data: DataFrame[], dimFields: XYFieldMatchers)
export type uPlotConfigBuilderSupplier = (
frame: DataFrame,
theme: GrafanaTheme,
theme: GrafanaThemeV2,
getTimeRange: () => TimeRange,
getTimeZone: () => TimeZone
) => UPlotConfigBuilder;
export function preparePlotConfigBuilder(
frame: DataFrame,
theme: GrafanaTheme,
theme: GrafanaThemeV2,
getTimeRange: () => TimeRange,
getTimeZone: () => TimeZone,
coreOptions: Partial<TimelineCoreOptions>

View File

@ -1,4 +1,4 @@
import { dateTimeFormat, GrafanaTheme, systemDateFormats, TimeZone } from '@grafana/data';
import { dateTimeFormat, GrafanaThemeV2, systemDateFormats, TimeZone } from '@grafana/data';
import uPlot, { Axis } from 'uplot';
import { PlotConfigBuilder } from '../types';
import { measureText } from '../../../utils/measureText';
@ -7,7 +7,7 @@ import { optMinMax } from './UPlotScaleBuilder';
export interface AxisProps {
scaleKey: string;
theme: GrafanaTheme;
theme: GrafanaThemeV2;
label?: string;
show?: boolean;
size?: number | null;
@ -50,14 +50,14 @@ export class UPlotAxisBuilder extends PlotConfigBuilder<AxisProps, Axis> {
theme,
} = this.props;
const font = `12px ${theme.typography.fontFamily.sansSerif}`;
const font = `12px ${theme.typography.fontFamily}`;
const gridColor = theme.isDark ? 'rgba(240, 250, 255, 0.09)' : 'rgba(0, 10, 23, 0.09)';
let config: Axis = {
scale: scaleKey,
show,
stroke: theme.colors.text,
stroke: theme.colors.text.primary,
side: getUPlotSideFromAxis(placement),
font,
labelFont: font,

View File

@ -13,7 +13,7 @@ import {
import { createTheme } from '@grafana/data';
describe('UPlotConfigBuilder', () => {
const darkTheme = createTheme().v1;
const darkTheme = createTheme();
describe('default config', () => {
it('builds default config', () => {

View File

@ -1,4 +1,4 @@
import { DataFrameFieldIndex, FALLBACK_COLOR, FieldColorMode, GrafanaTheme, ThresholdsConfig } from '@grafana/data';
import { DataFrameFieldIndex, FALLBACK_COLOR, FieldColorMode, GrafanaThemeV2, ThresholdsConfig } from '@grafana/data';
import tinycolor from 'tinycolor2';
import uPlot, { Series } from 'uplot';
import {
@ -30,7 +30,7 @@ export interface SeriesProps extends LineConfig, BarConfig, FillConfig, PointsCo
show?: boolean;
dataFrameFieldIndex?: DataFrameFieldIndex;
hideInLegend?: boolean;
theme: GrafanaTheme;
theme: GrafanaThemeV2;
}
export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {

View File

@ -1,4 +1,4 @@
import { FieldColorMode, GrafanaTheme, ThresholdsConfig } from '@grafana/data';
import { FieldColorMode, GrafanaThemeV2, ThresholdsConfig } from '@grafana/data';
import tinycolor from 'tinycolor2';
import uPlot from 'uplot';
import { getCanvasContext } from '../../../utils/measureText';
@ -20,7 +20,7 @@ export function getOpacityGradientFn(
export function getHueGradientFn(
color: string,
opacity: number,
theme: GrafanaTheme
theme: GrafanaThemeV2
): (self: uPlot, seriesIdx: number) => CanvasGradient {
return (plot: uPlot, seriesIdx: number) => {
const ctx = getCanvasContext();

View File

@ -13,6 +13,7 @@ import {
import { SeriesTable, SeriesTableRowProps, TooltipDisplayMode, VizTooltipContainer } from '../../VizTooltip';
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
import { pluginLog } from '../utils';
import { useTheme2 } from '../../../themes/ThemeContext';
interface TooltipPluginProps {
mode?: TooltipDisplayMode;
@ -30,6 +31,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
config,
...otherProps
}) => {
const theme = useTheme2();
const plotCtx = usePlotContext();
const plotCanvas = useRef<HTMLDivElement>();
const plotCanvasBBox = useRef<any>({ left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 });
@ -81,7 +83,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
if (!xField) {
return null;
}
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, timeZone });
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, timeZone, theme });
let tooltip = null;
const xVal = xFieldFmt(xField!.values.get(focusedPointIdx)).text;
@ -91,7 +93,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
const field = otherProps.data.fields[focusedSeriesIdx];
const plotSeries = plotCtx.plot.series;
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone });
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone, theme });
const value = fieldFmt(plotCtx.plot.data[focusedSeriesIdx!][focusedPointIdx]);
tooltip = (

View File

@ -1,6 +1,6 @@
import { applyFieldOverrides, DataFrame, GrafanaTheme } from '@grafana/data';
import { applyFieldOverrides, DataFrame, GrafanaThemeV2 } from '@grafana/data';
export function prepDataForStorybook(data: DataFrame[], theme: GrafanaTheme) {
export function prepDataForStorybook(data: DataFrame[], theme: GrafanaThemeV2) {
return applyFieldOverrides({
data: data,
fieldConfig: {

View File

@ -32,6 +32,7 @@ import {
rangeUtil,
} from '@grafana/data';
import { getThemeColor } from 'app/core/utils/colors';
import { config } from '@grafana/runtime';
export const LIMIT_LABEL = 'Line limit';
@ -146,6 +147,7 @@ export function makeSeriesForLogs(sortedRows: LogRowModel[], bucketSize: number,
timeField.display = getDisplayProcessor({
field: timeField,
timeZone,
theme: config.theme2,
});
const valueField = fieldCache.getFirstFieldOfType(FieldType.number)!;
@ -155,7 +157,7 @@ export function makeSeriesForLogs(sortedRows: LogRowModel[], bucketSize: number,
};
valueField.name = series.alias;
const fieldDisplayProcessor = getDisplayProcessor({ field: valueField, timeZone });
const fieldDisplayProcessor = getDisplayProcessor({ field: valueField, timeZone, theme: config.theme2 });
valueField.display = (value: any) => ({ ...fieldDisplayProcessor(value), color: series.color });
const points = getFlotPairs({

View File

@ -218,7 +218,7 @@ function handleJSONResponse(frames: DataFrameJSON[]) {
overrides: [],
},
replaceVariables: (value: any) => value,
theme: config.theme,
theme: config.theme2,
});
}

View File

@ -476,7 +476,7 @@ export class PanelModel implements DataConfigSource {
fieldConfig: this.fieldConfig,
replaceVariables: this.replaceVariables,
fieldConfigRegistry: this.plugin.fieldConfigRegistry,
theme: config.theme,
theme: config.theme2,
};
}

View File

@ -22,7 +22,7 @@ export function loadSnapshotData(panel: PanelModel, dashboard: DashboardModel):
},
replaceVariables: panel.replaceVariables,
fieldConfigRegistry: panel.plugin!.fieldConfigRegistry,
theme: config.theme,
theme: config.theme2,
timeZone: dashboard.getTimezone(),
}),
annotations,

View File

@ -19,7 +19,7 @@ import {
LegendDisplayMode,
TooltipPlugin,
useStyles,
useTheme,
useTheme2,
ZoomPlugin,
TooltipDisplayMode,
} from '@grafana/ui';
@ -55,7 +55,7 @@ export function ExploreGraphNGPanel({
annotations,
splitOpenFn,
}: Props) {
const theme = useTheme();
const theme = useTheme2();
const [showAllTimeSeries, setShowAllTimeSeries] = useState(false);
const [structureRev, setStructureRev] = useState(1);
const [fieldConfig, setFieldConfig] = useState<FieldConfigSource>({

View File

@ -39,7 +39,7 @@ export const getGraphSeriesModel = (
decimals: legendOptions.decimals,
},
},
theme: config.theme,
theme: config.theme2,
timeZone,
});
@ -106,7 +106,7 @@ export const getGraphSeriesModel = (
}
: { ...field.config, color };
field.display = getDisplayProcessor({ field, timeZone, theme: config.theme });
field.display = getDisplayProcessor({ field, timeZone, theme: config.theme2 });
// Time step is used to determine bars width when graph is rendered as bar chart
const timeStep = getSeriesTimeStep(timeField);
@ -121,7 +121,7 @@ export const getGraphSeriesModel = (
unit: systemDateFormats.getTimeFieldUnit(useMsDateFormat),
},
},
theme: config.theme,
theme: config.theme2,
});
graphs.push({

View File

@ -118,7 +118,7 @@ export const decorateWithTableResult = (data: ExplorePanelData): Observable<Expl
field.display ??
getDisplayProcessor({
field,
theme: config.theme,
theme: config.theme2,
timeZone: data.request?.timezone ?? 'browser',
});
}

View File

@ -9,7 +9,7 @@ import {
DataFrame,
getFieldDisplayValuesProxy,
} from '@grafana/data';
import { config, getTemplateSrv } from '@grafana/runtime';
import { getTemplateSrv } from '@grafana/runtime';
import { SplitOpen } from 'app/types/explore';
import { getLinkSrv } from '../../panel/panellinks/link_srv';
@ -43,8 +43,9 @@ export const getFieldLinksForExplore = (options: {
value: {
name: dataFrame.name,
refId: dataFrame.refId,
fields: getFieldDisplayValuesProxy(dataFrame, rowIndex, {
theme: config.theme,
fields: getFieldDisplayValuesProxy({
frame: dataFrame,
rowIndex,
}),
},
text: 'Data',

View File

@ -149,7 +149,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
// That's because transformers create new fields and data frames, so i.e. display processor is no longer there
return applyFieldOverrides({
data,
theme: config.theme,
theme: config.theme2,
fieldConfig: panel.fieldConfig,
replaceVariables: (value: string) => {
return value;

View File

@ -3,12 +3,11 @@ import {
FieldType,
formattedValueToString,
getDisplayProcessor,
GrafanaTheme,
GrafanaThemeV2,
QueryResultMetaStat,
TimeZone,
} from '@grafana/data';
import { config } from 'app/core/config';
import { stylesFactory, useTheme } from '@grafana/ui';
import { stylesFactory, useTheme2 } from '@grafana/ui';
import { css } from '@emotion/css';
interface InspectStatsTableProps {
@ -18,7 +17,7 @@ interface InspectStatsTableProps {
}
export const InspectStatsTable: React.FC<InspectStatsTableProps> = ({ timeZone, name, stats }) => {
const theme = useTheme();
const theme = useTheme2();
const styles = getStyles(theme);
if (!stats || !stats.length) {
@ -34,7 +33,7 @@ export const InspectStatsTable: React.FC<InspectStatsTableProps> = ({ timeZone,
return (
<tr key={`${stat.displayName}-${index}`}>
<td>{stat.displayName}</td>
<td className={styles.cell}>{formatStat(stat, timeZone)}</td>
<td className={styles.cell}>{formatStat(stat, timeZone, theme)}</td>
</tr>
);
})}
@ -44,22 +43,22 @@ export const InspectStatsTable: React.FC<InspectStatsTableProps> = ({ timeZone,
);
};
function formatStat(stat: QueryResultMetaStat, timeZone?: TimeZone): string {
function formatStat(stat: QueryResultMetaStat, timeZone: TimeZone, theme: GrafanaThemeV2): string {
const display = getDisplayProcessor({
field: {
type: FieldType.number,
config: stat,
},
theme: config.theme,
theme,
timeZone,
});
return formattedValueToString(display(stat.value));
}
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const getStyles = stylesFactory((theme: GrafanaThemeV2) => {
return {
wrapper: css`
padding-bottom: ${theme.spacing.md};
padding-bottom: ${theme.spacing(2)};
`,
cell: css`
text-align: right;

View File

@ -1,11 +1,11 @@
import React, { useState, useMemo } from 'react';
import { applyFieldOverrides, FieldConfigSource, getTimeZone, PanelData, PanelPlugin } from '@grafana/data';
import { PanelRendererProps } from '@grafana/runtime';
import { config } from 'app/core/config';
import { appEvents } from 'app/core/core';
import { useAsync } from 'react-use';
import { getPanelOptionsWithDefaults, OptionDefaults } from '../dashboard/state/getPanelOptionsWithDefaults';
import { importPanelPlugin } from '../plugins/plugin_loader';
import { useTheme2 } from '@grafana/ui';
export function PanelRenderer<P extends object = any, F extends object = any>(props: PanelRendererProps<P, F>) {
const {
@ -94,6 +94,7 @@ const useFieldOverrides = (
const fieldConfig = defaultOptions?.fieldConfig;
const series = data?.series;
const fieldConfigRegistry = plugin?.fieldConfigRegistry;
const theme = useTheme2();
return useMemo(() => {
if (!fieldConfigRegistry || !fieldConfig || !data) {
@ -107,9 +108,9 @@ const useFieldOverrides = (
fieldConfig,
fieldConfigRegistry,
replaceVariables: (str: string) => str,
theme: config.theme,
theme,
timeZone,
}),
};
}, [fieldConfigRegistry, fieldConfig, data, series, timeZone]);
}, [fieldConfigRegistry, fieldConfig, data, series, timeZone, theme]);
};

View File

@ -1,9 +1,8 @@
import { getFieldLinksSupplier } from './linkSuppliers';
import { applyFieldOverrides, DataFrameView, dateTime, FieldDisplay, toDataFrame } from '@grafana/data';
import { applyFieldOverrides, createTheme, DataFrameView, dateTime, FieldDisplay, toDataFrame } from '@grafana/data';
import { getLinkSrv, LinkService, LinkSrv, setLinkSrv } from './link_srv';
import { TemplateSrv } from '../../templating/template_srv';
import { TimeSrv } from '../../dashboard/services/TimeSrv';
import { getTheme } from '@grafana/ui';
describe('getFieldLinksSupplier', () => {
let originalLinkSrv: LinkService;
@ -93,7 +92,7 @@ describe('getFieldLinksSupplier', () => {
},
replaceVariables: (val: string) => val,
timeZone: 'utc',
theme: getTheme(),
theme: createTheme(),
})[0];
const rowIndex = 0;

View File

@ -13,7 +13,6 @@ import {
ScopedVars,
} from '@grafana/data';
import { getLinkSrv } from './link_srv';
import { config } from 'app/core/config';
interface SeriesVars {
name?: string;
@ -100,8 +99,9 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
value: {
name: dataFrame.name,
refId: dataFrame.refId,
fields: getFieldDisplayValuesProxy(dataFrame, value.rowIndex!, {
theme: config.theme,
fields: getFieldDisplayValuesProxy({
frame: dataFrame,
rowIndex: value.rowIndex!,
}),
},
text: 'Data',

View File

@ -248,7 +248,7 @@ describe('PanelQueryRunner', () => {
overrides: [],
},
replaceVariables: (v) => v,
theme: {} as grafanaData.GrafanaTheme,
theme: grafanaData.createTheme(),
}),
getTransformations: () => undefined,
}
@ -312,7 +312,7 @@ describe('PanelQueryRunner', () => {
overrides: [],
},
replaceVariables: (v) => v,
theme: {} as grafanaData.GrafanaTheme,
theme: grafanaData.createTheme(),
}),
// @ts-ignore
getTransformations: () => [({} as unknown) as grafanaData.DataTransformerConfig],
@ -354,7 +354,7 @@ describe('PanelQueryRunner', () => {
overrides: [],
},
replaceVariables: (v) => v,
theme: {} as grafanaData.GrafanaTheme,
theme: grafanaData.createTheme(),
}),
// @ts-ignore
getTransformations: () => [{}],

View File

@ -103,7 +103,7 @@ export function getDefaultState(): State {
overrides: [],
},
replaceVariables: (v: string) => v,
theme: config.theme,
theme: config.theme2,
};
const dataConfig = {

View File

@ -40,7 +40,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
field={field}
text={options.text}
display={processor}
theme={config.theme}
theme={config.theme2}
itemSpacing={this.getItemSpacing()}
displayMode={options.displayMode}
onClick={openMenu}
@ -73,7 +73,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
fieldConfig,
reduceOptions: options.reduceOptions,
replaceVariables,
theme: config.theme,
theme: config.theme2,
data: data.series,
timeZone,
});

View File

@ -56,7 +56,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
fieldConfig,
reduceOptions: options.reduceOptions,
replaceVariables,
theme: config.theme,
theme: config.theme2,
data: data.series,
timeZone,
});

View File

@ -286,7 +286,7 @@ class GraphElement {
};
const fieldDisplay = getDisplayProcessor({
field: { config: fieldConfig, type: FieldType.number },
theme: config.theme,
theme: config.theme2,
timeZone: this.dashboard.getTimezone(),
})(field.values.get(dataIndex));
linksSupplier = links.length

View File

@ -188,7 +188,7 @@ export class LivePanel extends PureComponent<Props, State> {
const data: PanelData = {
series: applyFieldOverrides({
data: [message],
theme: config.theme,
theme: config.theme2,
replaceVariables: (v: string) => v,
fieldConfig: {
defaults: {},

View File

@ -45,7 +45,7 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
text={options.text}
width={width}
height={height}
theme={config.theme}
theme={config.theme2}
onClick={openMenu}
className={targetClassName}
/>
@ -87,7 +87,7 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
fieldConfig,
reduceOptions: options.reduceOptions,
replaceVariables,
theme: config.theme,
theme: config.theme2,
data: data.series,
sparkline: options.graphMode !== BigValueGraphMode.None,
timeZone,

View File

@ -14,7 +14,6 @@ import {
CartesianCoords2D,
DataFrame,
DataFrameView,
getDisplayProcessor,
getFieldDisplayName,
InterpolateFunction,
TimeZone,
@ -198,7 +197,7 @@ export const ContextMenuView: React.FC<ContextMenuProps> = ({
if (selection.point) {
const { seriesIdx, dataIdx } = selection.point;
const xFieldFmt = xField.display || getDisplayProcessor({ field: xField, timeZone });
const xFieldFmt = xField.display!;
if (seriesIdx && dataIdx) {
const field = data.fields[seriesIdx];