mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[XY] Usable reference lines for xyVis
. (#132192)
* ReferenceLineLayer -> referenceLine. * Added the referenceLine and splitted the logic at ReferenceLineAnnotations. * Fixed formatters of referenceLines * Added referenceLines keys. * Added test for the referenceLine fn. * Added some tests for reference_lines. * Unified the two different approaches of referenceLines. * Fixed types at tests and limits.
This commit is contained in:
parent
6bdef36905
commit
3982bfd3fd
35 changed files with 1615 additions and 808 deletions
|
@ -128,5 +128,5 @@ pageLoadAssetSize:
|
|||
eventAnnotation: 19334
|
||||
screenshotting: 22870
|
||||
synthetics: 40958
|
||||
expressionXY: 31000
|
||||
expressionXY: 33000
|
||||
kibanaUsageCollection: 16463
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
export const XY_VIS = 'xyVis';
|
||||
export const LAYERED_XY_VIS = 'layeredXyVis';
|
||||
export const Y_CONFIG = 'yConfig';
|
||||
export const REFERENCE_LINE_Y_CONFIG = 'referenceLineYConfig';
|
||||
export const EXTENDED_Y_CONFIG = 'extendedYConfig';
|
||||
export const DATA_LAYER = 'dataLayer';
|
||||
export const EXTENDED_DATA_LAYER = 'extendedDataLayer';
|
||||
|
@ -19,8 +20,8 @@ export const ANNOTATION_LAYER = 'annotationLayer';
|
|||
export const EXTENDED_ANNOTATION_LAYER = 'extendedAnnotationLayer';
|
||||
export const TICK_LABELS_CONFIG = 'tickLabelsConfig';
|
||||
export const AXIS_EXTENT_CONFIG = 'axisExtentConfig';
|
||||
export const REFERENCE_LINE = 'referenceLine';
|
||||
export const REFERENCE_LINE_LAYER = 'referenceLineLayer';
|
||||
export const EXTENDED_REFERENCE_LINE_LAYER = 'extendedReferenceLineLayer';
|
||||
export const LABELS_ORIENTATION_CONFIG = 'labelsOrientationConfig';
|
||||
export const AXIS_TITLES_VISIBILITY_CONFIG = 'axisTitlesVisibilityConfig';
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EXTENDED_Y_CONFIG } from '../constants';
|
||||
import { strings } from '../i18n';
|
||||
import { ReferenceLineLayerFn, ExtendedReferenceLineLayerFn } from '../types';
|
||||
|
||||
type CommonReferenceLineLayerFn = ReferenceLineLayerFn | ExtendedReferenceLineLayerFn;
|
||||
|
||||
export const commonReferenceLineLayerArgs: Omit<CommonReferenceLineLayerFn['args'], 'accessors'> = {
|
||||
yConfig: {
|
||||
types: [EXTENDED_Y_CONFIG],
|
||||
help: strings.getRLYConfigHelp(),
|
||||
multi: true,
|
||||
},
|
||||
columnToLabel: {
|
||||
types: ['string'],
|
||||
help: strings.getColumnToLabelHelp(),
|
||||
},
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { validateAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { LayerTypes, EXTENDED_REFERENCE_LINE_LAYER } from '../constants';
|
||||
import { ExtendedReferenceLineLayerFn } from '../types';
|
||||
import { strings } from '../i18n';
|
||||
import { commonReferenceLineLayerArgs } from './common_reference_line_layer_args';
|
||||
|
||||
export const extendedReferenceLineLayerFunction: ExtendedReferenceLineLayerFn = {
|
||||
name: EXTENDED_REFERENCE_LINE_LAYER,
|
||||
aliases: [],
|
||||
type: EXTENDED_REFERENCE_LINE_LAYER,
|
||||
help: strings.getRLHelp(),
|
||||
inputTypes: ['datatable'],
|
||||
args: {
|
||||
...commonReferenceLineLayerArgs,
|
||||
accessors: {
|
||||
types: ['string'],
|
||||
help: strings.getRLAccessorsHelp(),
|
||||
multi: true,
|
||||
},
|
||||
table: {
|
||||
types: ['datatable'],
|
||||
help: strings.getTableHelp(),
|
||||
},
|
||||
layerId: {
|
||||
types: ['string'],
|
||||
help: strings.getLayerIdHelp(),
|
||||
},
|
||||
},
|
||||
fn(input, args) {
|
||||
const table = args.table ?? input;
|
||||
const accessors = args.accessors ?? [];
|
||||
accessors.forEach((accessor) => validateAccessor(accessor, table.columns));
|
||||
|
||||
return {
|
||||
type: EXTENDED_REFERENCE_LINE_LAYER,
|
||||
...args,
|
||||
accessors: args.accessors ?? [],
|
||||
layerType: LayerTypes.REFERENCELINE,
|
||||
table,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -18,6 +18,6 @@ export * from './grid_lines_config';
|
|||
export * from './axis_extent_config';
|
||||
export * from './tick_labels_config';
|
||||
export * from './labels_orientation_config';
|
||||
export * from './reference_line';
|
||||
export * from './reference_line_layer';
|
||||
export * from './extended_reference_line_layer';
|
||||
export * from './axis_titles_visibility_config';
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { LayeredXyVisFn } from '../types';
|
||||
import {
|
||||
EXTENDED_DATA_LAYER,
|
||||
EXTENDED_REFERENCE_LINE_LAYER,
|
||||
REFERENCE_LINE_LAYER,
|
||||
LAYERED_XY_VIS,
|
||||
EXTENDED_ANNOTATION_LAYER,
|
||||
} from '../constants';
|
||||
|
@ -24,8 +25,10 @@ export const layeredXyVisFunction: LayeredXyVisFn = {
|
|||
args: {
|
||||
...commonXYArgs,
|
||||
layers: {
|
||||
types: [EXTENDED_DATA_LAYER, EXTENDED_REFERENCE_LINE_LAYER, EXTENDED_ANNOTATION_LAYER],
|
||||
help: strings.getLayersHelp(),
|
||||
types: [EXTENDED_DATA_LAYER, REFERENCE_LINE_LAYER, EXTENDED_ANNOTATION_LAYER],
|
||||
help: i18n.translate('expressionXY.layeredXyVis.layers.help', {
|
||||
defaultMessage: 'Layers of visual series',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks';
|
||||
import { ReferenceLineArgs, ReferenceLineConfigResult } from '../types';
|
||||
import { referenceLineFunction } from './reference_line';
|
||||
|
||||
describe('referenceLine', () => {
|
||||
test('produces the correct arguments for minimum arguments', async () => {
|
||||
const args: ReferenceLineArgs = {
|
||||
value: 100,
|
||||
};
|
||||
|
||||
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
const expectedResult: ReferenceLineConfigResult = {
|
||||
type: 'referenceLine',
|
||||
layerType: 'referenceLine',
|
||||
lineLength: 0,
|
||||
yConfig: [
|
||||
{
|
||||
type: 'referenceLineYConfig',
|
||||
...args,
|
||||
textVisibility: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
test('produces the correct arguments for maximum arguments', async () => {
|
||||
const args: ReferenceLineArgs = {
|
||||
name: 'some value',
|
||||
value: 100,
|
||||
icon: 'alert',
|
||||
iconPosition: 'below',
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
lineWidth: 10,
|
||||
color: '#fff',
|
||||
fill: 'below',
|
||||
textVisibility: true,
|
||||
};
|
||||
|
||||
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
const expectedResult: ReferenceLineConfigResult = {
|
||||
type: 'referenceLine',
|
||||
layerType: 'referenceLine',
|
||||
lineLength: 0,
|
||||
yConfig: [
|
||||
{
|
||||
type: 'referenceLineYConfig',
|
||||
...args,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
test('adds text visibility if name is provided ', async () => {
|
||||
const args: ReferenceLineArgs = {
|
||||
name: 'some name',
|
||||
value: 100,
|
||||
};
|
||||
|
||||
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
const expectedResult: ReferenceLineConfigResult = {
|
||||
type: 'referenceLine',
|
||||
layerType: 'referenceLine',
|
||||
lineLength: 0,
|
||||
yConfig: [
|
||||
{
|
||||
type: 'referenceLineYConfig',
|
||||
...args,
|
||||
textVisibility: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
test('hides text if textVisibility is true and no text is provided', async () => {
|
||||
const args: ReferenceLineArgs = {
|
||||
value: 100,
|
||||
textVisibility: true,
|
||||
};
|
||||
|
||||
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
const expectedResult: ReferenceLineConfigResult = {
|
||||
type: 'referenceLine',
|
||||
layerType: 'referenceLine',
|
||||
lineLength: 0,
|
||||
yConfig: [
|
||||
{
|
||||
type: 'referenceLineYConfig',
|
||||
...args,
|
||||
textVisibility: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
test('applies text visibility if name is provided', async () => {
|
||||
const checktextVisibility = (textVisibility: boolean = false) => {
|
||||
const args: ReferenceLineArgs = {
|
||||
value: 100,
|
||||
name: 'some text',
|
||||
textVisibility,
|
||||
};
|
||||
|
||||
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
const expectedResult: ReferenceLineConfigResult = {
|
||||
type: 'referenceLine',
|
||||
layerType: 'referenceLine',
|
||||
lineLength: 0,
|
||||
yConfig: [
|
||||
{
|
||||
type: 'referenceLineYConfig',
|
||||
...args,
|
||||
textVisibility,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(result).toEqual(expectedResult);
|
||||
};
|
||||
|
||||
checktextVisibility();
|
||||
checktextVisibility(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
AvailableReferenceLineIcons,
|
||||
FillStyles,
|
||||
IconPositions,
|
||||
LayerTypes,
|
||||
LineStyles,
|
||||
REFERENCE_LINE,
|
||||
REFERENCE_LINE_Y_CONFIG,
|
||||
YAxisModes,
|
||||
} from '../constants';
|
||||
import { ReferenceLineFn } from '../types';
|
||||
import { strings } from '../i18n';
|
||||
|
||||
export const referenceLineFunction: ReferenceLineFn = {
|
||||
name: REFERENCE_LINE,
|
||||
aliases: [],
|
||||
type: REFERENCE_LINE,
|
||||
help: strings.getRLHelp(),
|
||||
inputTypes: ['datatable', 'null'],
|
||||
args: {
|
||||
name: {
|
||||
types: ['string'],
|
||||
help: strings.getReferenceLineNameHelp(),
|
||||
},
|
||||
value: {
|
||||
types: ['number'],
|
||||
help: strings.getReferenceLineValueHelp(),
|
||||
required: true,
|
||||
},
|
||||
axisMode: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(YAxisModes)],
|
||||
help: strings.getAxisModeHelp(),
|
||||
default: YAxisModes.AUTO,
|
||||
strict: true,
|
||||
},
|
||||
color: {
|
||||
types: ['string'],
|
||||
help: strings.getColorHelp(),
|
||||
},
|
||||
lineStyle: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(LineStyles)],
|
||||
help: i18n.translate('expressionXY.yConfig.lineStyle.help', {
|
||||
defaultMessage: 'The style of the reference line',
|
||||
}),
|
||||
default: LineStyles.SOLID,
|
||||
strict: true,
|
||||
},
|
||||
lineWidth: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionXY.yConfig.lineWidth.help', {
|
||||
defaultMessage: 'The width of the reference line',
|
||||
}),
|
||||
default: 1,
|
||||
},
|
||||
icon: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.yConfig.icon.help', {
|
||||
defaultMessage: 'An optional icon used for reference lines',
|
||||
}),
|
||||
options: [...Object.values(AvailableReferenceLineIcons)],
|
||||
strict: true,
|
||||
},
|
||||
iconPosition: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(IconPositions)],
|
||||
help: i18n.translate('expressionXY.yConfig.iconPosition.help', {
|
||||
defaultMessage: 'The placement of the icon for the reference line',
|
||||
}),
|
||||
default: IconPositions.AUTO,
|
||||
strict: true,
|
||||
},
|
||||
textVisibility: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionXY.yConfig.textVisibility.help', {
|
||||
defaultMessage: 'Visibility of the label on the reference line',
|
||||
}),
|
||||
},
|
||||
fill: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(FillStyles)],
|
||||
help: i18n.translate('expressionXY.yConfig.fill.help', {
|
||||
defaultMessage: 'Fill',
|
||||
}),
|
||||
default: FillStyles.NONE,
|
||||
strict: true,
|
||||
},
|
||||
},
|
||||
fn(table, args) {
|
||||
const textVisibility =
|
||||
args.name !== undefined && args.textVisibility === undefined
|
||||
? true
|
||||
: args.name === undefined
|
||||
? false
|
||||
: args.textVisibility;
|
||||
|
||||
return {
|
||||
type: REFERENCE_LINE,
|
||||
layerType: LayerTypes.REFERENCELINE,
|
||||
lineLength: table?.rows.length ?? 0,
|
||||
yConfig: [{ ...args, textVisibility, type: REFERENCE_LINE_Y_CONFIG }],
|
||||
};
|
||||
},
|
||||
};
|
|
@ -7,10 +7,9 @@
|
|||
*/
|
||||
|
||||
import { validateAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { LayerTypes, REFERENCE_LINE_LAYER } from '../constants';
|
||||
import { LayerTypes, REFERENCE_LINE_LAYER, EXTENDED_Y_CONFIG } from '../constants';
|
||||
import { ReferenceLineLayerFn } from '../types';
|
||||
import { strings } from '../i18n';
|
||||
import { commonReferenceLineLayerArgs } from './common_reference_line_layer_args';
|
||||
|
||||
export const referenceLineLayerFunction: ReferenceLineLayerFn = {
|
||||
name: REFERENCE_LINE_LAYER,
|
||||
|
@ -19,14 +18,31 @@ export const referenceLineLayerFunction: ReferenceLineLayerFn = {
|
|||
help: strings.getRLHelp(),
|
||||
inputTypes: ['datatable'],
|
||||
args: {
|
||||
...commonReferenceLineLayerArgs,
|
||||
accessors: {
|
||||
types: ['string', 'vis_dimension'],
|
||||
types: ['string'],
|
||||
help: strings.getRLAccessorsHelp(),
|
||||
multi: true,
|
||||
},
|
||||
yConfig: {
|
||||
types: [EXTENDED_Y_CONFIG],
|
||||
help: strings.getRLYConfigHelp(),
|
||||
multi: true,
|
||||
},
|
||||
columnToLabel: {
|
||||
types: ['string'],
|
||||
help: strings.getColumnToLabelHelp(),
|
||||
},
|
||||
table: {
|
||||
types: ['datatable'],
|
||||
help: strings.getTableHelp(),
|
||||
},
|
||||
layerId: {
|
||||
types: ['string'],
|
||||
help: strings.getLayerIdHelp(),
|
||||
},
|
||||
},
|
||||
fn(table, args) {
|
||||
fn(input, args) {
|
||||
const table = args.table ?? input;
|
||||
const accessors = args.accessors ?? [];
|
||||
accessors.forEach((accessor) => validateAccessor(accessor, table.columns));
|
||||
|
||||
|
@ -34,8 +50,7 @@ export const referenceLineLayerFunction: ReferenceLineLayerFn = {
|
|||
type: REFERENCE_LINE_LAYER,
|
||||
...args,
|
||||
layerType: LayerTypes.REFERENCELINE,
|
||||
accessors,
|
||||
table,
|
||||
table: args.table ?? input,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -30,11 +30,12 @@ describe('xyVis', () => {
|
|||
}
|
||||
),
|
||||
} as Datatable;
|
||||
|
||||
const { layers, ...rest } = args;
|
||||
const { layerId, layerType, table, type, ...restLayerArgs } = sampleLayer;
|
||||
const result = await xyVisFunction.fn(
|
||||
newData,
|
||||
{ ...rest, ...restLayerArgs, referenceLineLayers: [], annotationLayers: [] },
|
||||
{ ...rest, ...restLayerArgs, referenceLines: [], annotationLayers: [] },
|
||||
createMockExecutionContext()
|
||||
);
|
||||
|
||||
|
@ -60,7 +61,7 @@ describe('xyVis', () => {
|
|||
...rest,
|
||||
...{ ...sampleLayer, markSizeAccessor: 'b' },
|
||||
markSizeRatio: 0,
|
||||
referenceLineLayers: [],
|
||||
referenceLines: [],
|
||||
annotationLayers: [],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
|
@ -74,7 +75,7 @@ describe('xyVis', () => {
|
|||
...rest,
|
||||
...{ ...sampleLayer, markSizeAccessor: 'b' },
|
||||
markSizeRatio: 101,
|
||||
referenceLineLayers: [],
|
||||
referenceLines: [],
|
||||
annotationLayers: [],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
|
@ -92,7 +93,7 @@ describe('xyVis', () => {
|
|||
...rest,
|
||||
...restLayerArgs,
|
||||
minTimeBarInterval: '1q',
|
||||
referenceLineLayers: [],
|
||||
referenceLines: [],
|
||||
annotationLayers: [],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
|
@ -111,7 +112,7 @@ describe('xyVis', () => {
|
|||
...rest,
|
||||
...restLayerArgs,
|
||||
minTimeBarInterval: '1h',
|
||||
referenceLineLayers: [],
|
||||
referenceLines: [],
|
||||
annotationLayers: [],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
|
@ -131,7 +132,7 @@ describe('xyVis', () => {
|
|||
{
|
||||
...rest,
|
||||
...restLayerArgs,
|
||||
referenceLineLayers: [],
|
||||
referenceLines: [],
|
||||
annotationLayers: [],
|
||||
splitRowAccessor,
|
||||
},
|
||||
|
@ -152,7 +153,7 @@ describe('xyVis', () => {
|
|||
{
|
||||
...rest,
|
||||
...restLayerArgs,
|
||||
referenceLineLayers: [],
|
||||
referenceLines: [],
|
||||
annotationLayers: [],
|
||||
splitColumnAccessor,
|
||||
},
|
||||
|
@ -172,7 +173,7 @@ describe('xyVis', () => {
|
|||
{
|
||||
...rest,
|
||||
...restLayerArgs,
|
||||
referenceLineLayers: [],
|
||||
referenceLines: [],
|
||||
annotationLayers: [],
|
||||
markSizeRatio: 5,
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { XyVisFn } from '../types';
|
||||
import { XY_VIS, REFERENCE_LINE_LAYER, ANNOTATION_LAYER } from '../constants';
|
||||
import { XY_VIS, REFERENCE_LINE, ANNOTATION_LAYER } from '../constants';
|
||||
import { strings } from '../i18n';
|
||||
import { commonXYArgs } from './common_xy_args';
|
||||
import { commonDataLayerArgs } from './common_data_layer_args';
|
||||
|
@ -33,9 +33,9 @@ export const xyVisFunction: XyVisFn = {
|
|||
help: strings.getAccessorsHelp(),
|
||||
multi: true,
|
||||
},
|
||||
referenceLineLayers: {
|
||||
types: [REFERENCE_LINE_LAYER],
|
||||
help: strings.getReferenceLineLayerHelp(),
|
||||
referenceLines: {
|
||||
types: [REFERENCE_LINE],
|
||||
help: strings.getReferenceLinesHelp(),
|
||||
multi: true,
|
||||
},
|
||||
annotationLayers: {
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from '@kbn/visualizations-plugin/common/utils';
|
||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
|
||||
import { LayerTypes, XY_VIS_RENDERER, DATA_LAYER } from '../constants';
|
||||
import { LayerTypes, XY_VIS_RENDERER, DATA_LAYER, REFERENCE_LINE } from '../constants';
|
||||
import { appendLayerIds, getAccessors, normalizeTable } from '../helpers';
|
||||
import { DataLayerConfigResult, XYLayerConfig, XyVisFn, XYArgs } from '../types';
|
||||
import { getLayerDimensions } from '../utils';
|
||||
|
@ -53,7 +53,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
|
|||
validateAccessor(args.splitColumnAccessor, data.columns);
|
||||
|
||||
const {
|
||||
referenceLineLayers = [],
|
||||
referenceLines = [],
|
||||
annotationLayers = [],
|
||||
// data_layer args
|
||||
seriesType,
|
||||
|
@ -81,7 +81,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
|
|||
|
||||
const layers: XYLayerConfig[] = [
|
||||
...appendLayerIds(dataLayers, 'dataLayers'),
|
||||
...appendLayerIds(referenceLineLayers, 'referenceLineLayers'),
|
||||
...appendLayerIds(referenceLines, 'referenceLines'),
|
||||
...appendLayerIds(annotationLayers, 'annotationLayers'),
|
||||
];
|
||||
|
||||
|
@ -90,7 +90,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
|
|||
handlers.inspectorAdapters.tables.allowCsvExport = true;
|
||||
|
||||
const layerDimensions = layers.reduce<Dimension[]>((dimensions, layer) => {
|
||||
if (layer.layerType === LayerTypes.ANNOTATIONS) {
|
||||
if (layer.layerType === LayerTypes.ANNOTATIONS || layer.type === REFERENCE_LINE) {
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ describe('#getDataLayers', () => {
|
|||
palette: { type: 'system_palette', name: 'system' },
|
||||
},
|
||||
{
|
||||
type: 'extendedReferenceLineLayer',
|
||||
type: 'referenceLineLayer',
|
||||
layerType: 'referenceLine',
|
||||
accessors: ['y'],
|
||||
table: { rows: [], columns: [], type: 'datatable' },
|
||||
|
|
|
@ -93,9 +93,9 @@ export const strings = {
|
|||
i18n.translate('expressionXY.xyVis.dataLayer.help', {
|
||||
defaultMessage: 'Data layer of visual series',
|
||||
}),
|
||||
getReferenceLineLayerHelp: () =>
|
||||
i18n.translate('expressionXY.xyVis.referenceLineLayer.help', {
|
||||
defaultMessage: 'Reference line layer',
|
||||
getReferenceLinesHelp: () =>
|
||||
i18n.translate('expressionXY.xyVis.referenceLines.help', {
|
||||
defaultMessage: 'Reference line',
|
||||
}),
|
||||
getAnnotationLayerHelp: () =>
|
||||
i18n.translate('expressionXY.xyVis.annotationLayer.help', {
|
||||
|
@ -237,4 +237,12 @@ export const strings = {
|
|||
i18n.translate('expressionXY.annotationLayer.annotations.help', {
|
||||
defaultMessage: 'Annotations',
|
||||
}),
|
||||
getReferenceLineNameHelp: () =>
|
||||
i18n.translate('expressionXY.referenceLine.name.help', {
|
||||
defaultMessage: 'Reference line name',
|
||||
}),
|
||||
getReferenceLineValueHelp: () =>
|
||||
i18n.translate('expressionXY.referenceLine.Value.help', {
|
||||
defaultMessage: 'Reference line value',
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -58,6 +58,5 @@ export type {
|
|||
ReferenceLineLayerConfigResult,
|
||||
CommonXYReferenceLineLayerConfig,
|
||||
AxisTitlesVisibilityConfigResult,
|
||||
ExtendedReferenceLineLayerConfigResult,
|
||||
CommonXYReferenceLineLayerConfigResult,
|
||||
} from './types';
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
XYCurveTypes,
|
||||
YAxisModes,
|
||||
YScaleTypes,
|
||||
REFERENCE_LINE_LAYER,
|
||||
REFERENCE_LINE,
|
||||
Y_CONFIG,
|
||||
AXIS_TITLES_VISIBILITY_CONFIG,
|
||||
LABELS_ORIENTATION_CONFIG,
|
||||
|
@ -36,7 +36,7 @@ import {
|
|||
DATA_LAYER,
|
||||
AXIS_EXTENT_CONFIG,
|
||||
EXTENDED_DATA_LAYER,
|
||||
EXTENDED_REFERENCE_LINE_LAYER,
|
||||
REFERENCE_LINE_LAYER,
|
||||
ANNOTATION_LAYER,
|
||||
EndValues,
|
||||
EXTENDED_Y_CONFIG,
|
||||
|
@ -44,6 +44,7 @@ import {
|
|||
XY_VIS,
|
||||
LAYERED_XY_VIS,
|
||||
EXTENDED_ANNOTATION_LAYER,
|
||||
REFERENCE_LINE_Y_CONFIG,
|
||||
} from '../constants';
|
||||
import { XYRender } from './expression_renderers';
|
||||
|
||||
|
@ -194,7 +195,7 @@ export interface XYArgs extends DataLayerArgs {
|
|||
endValue?: EndValue;
|
||||
emphasizeFitting?: boolean;
|
||||
valueLabels: ValueLabelMode;
|
||||
referenceLineLayers: ReferenceLineLayerConfigResult[];
|
||||
referenceLines: ReferenceLineConfigResult[];
|
||||
annotationLayers: AnnotationLayerConfigResult[];
|
||||
fittingFunction?: FittingFunction;
|
||||
axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult;
|
||||
|
@ -287,13 +288,12 @@ export type ExtendedAnnotationLayerConfigResult = ExtendedAnnotationLayerArgs &
|
|||
layerType: typeof LayerTypes.ANNOTATIONS;
|
||||
};
|
||||
|
||||
export interface ReferenceLineLayerArgs {
|
||||
accessors: Array<ExpressionValueVisDimension | string>;
|
||||
columnToLabel?: string;
|
||||
yConfig?: ExtendedYConfigResult[];
|
||||
export interface ReferenceLineArgs extends Omit<ExtendedYConfig, 'forAccessor'> {
|
||||
name?: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface ExtendedReferenceLineLayerArgs {
|
||||
export interface ReferenceLineLayerArgs {
|
||||
layerId?: string;
|
||||
accessors: string[];
|
||||
columnToLabel?: string;
|
||||
|
@ -301,30 +301,35 @@ export interface ExtendedReferenceLineLayerArgs {
|
|||
table?: Datatable;
|
||||
}
|
||||
|
||||
export type XYLayerArgs = DataLayerArgs | ReferenceLineLayerArgs | AnnotationLayerArgs;
|
||||
export type XYLayerConfig = DataLayerConfig | ReferenceLineLayerConfig | AnnotationLayerConfig;
|
||||
export type XYLayerArgs = DataLayerArgs | ReferenceLineArgs | AnnotationLayerArgs;
|
||||
export type XYLayerConfig = DataLayerConfig | ReferenceLineConfig | AnnotationLayerConfig;
|
||||
export type XYExtendedLayerConfig =
|
||||
| ExtendedDataLayerConfig
|
||||
| ExtendedReferenceLineLayerConfig
|
||||
| ReferenceLineLayerConfig
|
||||
| ExtendedAnnotationLayerConfig;
|
||||
|
||||
export type XYExtendedLayerConfigResult =
|
||||
| ExtendedDataLayerConfigResult
|
||||
| ExtendedReferenceLineLayerConfigResult
|
||||
| ReferenceLineLayerConfigResult
|
||||
| ExtendedAnnotationLayerConfigResult;
|
||||
|
||||
export interface ReferenceLineYConfig extends ReferenceLineArgs {
|
||||
type: typeof REFERENCE_LINE_Y_CONFIG;
|
||||
}
|
||||
|
||||
export interface ReferenceLineConfigResult {
|
||||
type: typeof REFERENCE_LINE;
|
||||
layerType: typeof LayerTypes.REFERENCELINE;
|
||||
lineLength: number;
|
||||
yConfig: [ReferenceLineYConfig];
|
||||
}
|
||||
|
||||
export type ReferenceLineLayerConfigResult = ReferenceLineLayerArgs & {
|
||||
type: typeof REFERENCE_LINE_LAYER;
|
||||
layerType: typeof LayerTypes.REFERENCELINE;
|
||||
table: Datatable;
|
||||
};
|
||||
|
||||
export type ExtendedReferenceLineLayerConfigResult = ExtendedReferenceLineLayerArgs & {
|
||||
type: typeof EXTENDED_REFERENCE_LINE_LAYER;
|
||||
layerType: typeof LayerTypes.REFERENCELINE;
|
||||
table: Datatable;
|
||||
};
|
||||
|
||||
export type DataLayerConfigResult = Omit<DataLayerArgs, 'palette'> & {
|
||||
type: typeof DATA_LAYER;
|
||||
layerType: typeof LayerTypes.DATA;
|
||||
|
@ -337,11 +342,11 @@ export interface WithLayerId {
|
|||
}
|
||||
|
||||
export type DataLayerConfig = DataLayerConfigResult & WithLayerId;
|
||||
export type ReferenceLineLayerConfig = ReferenceLineLayerConfigResult & WithLayerId;
|
||||
export type ReferenceLineConfig = ReferenceLineConfigResult & WithLayerId;
|
||||
export type AnnotationLayerConfig = AnnotationLayerConfigResult & WithLayerId;
|
||||
|
||||
export type ExtendedDataLayerConfig = ExtendedDataLayerConfigResult & WithLayerId;
|
||||
export type ExtendedReferenceLineLayerConfig = ExtendedReferenceLineLayerConfigResult & WithLayerId;
|
||||
export type ReferenceLineLayerConfig = ReferenceLineLayerConfigResult & WithLayerId;
|
||||
export type ExtendedAnnotationLayerConfig = ExtendedAnnotationLayerConfigResult & WithLayerId;
|
||||
|
||||
export type ExtendedDataLayerConfigResult = Omit<ExtendedDataLayerArgs, 'palette'> & {
|
||||
|
@ -370,13 +375,11 @@ export type TickLabelsConfigResult = AxesSettingsConfig & { type: typeof TICK_LA
|
|||
export type CommonXYLayerConfig = XYLayerConfig | XYExtendedLayerConfig;
|
||||
export type CommonXYDataLayerConfigResult = DataLayerConfigResult | ExtendedDataLayerConfigResult;
|
||||
export type CommonXYReferenceLineLayerConfigResult =
|
||||
| ReferenceLineLayerConfigResult
|
||||
| ExtendedReferenceLineLayerConfigResult;
|
||||
| ReferenceLineConfigResult
|
||||
| ReferenceLineLayerConfigResult;
|
||||
|
||||
export type CommonXYDataLayerConfig = DataLayerConfig | ExtendedDataLayerConfig;
|
||||
export type CommonXYReferenceLineLayerConfig =
|
||||
| ReferenceLineLayerConfig
|
||||
| ExtendedReferenceLineLayerConfig;
|
||||
export type CommonXYReferenceLineLayerConfig = ReferenceLineConfig | ReferenceLineLayerConfig;
|
||||
|
||||
export type CommonXYAnnotationLayerConfig = AnnotationLayerConfig | ExtendedAnnotationLayerConfig;
|
||||
|
||||
|
@ -400,18 +403,18 @@ export type ExtendedDataLayerFn = ExpressionFunctionDefinition<
|
|||
Promise<ExtendedDataLayerConfigResult>
|
||||
>;
|
||||
|
||||
export type ReferenceLineFn = ExpressionFunctionDefinition<
|
||||
typeof REFERENCE_LINE,
|
||||
Datatable | null,
|
||||
ReferenceLineArgs,
|
||||
ReferenceLineConfigResult
|
||||
>;
|
||||
export type ReferenceLineLayerFn = ExpressionFunctionDefinition<
|
||||
typeof REFERENCE_LINE_LAYER,
|
||||
Datatable,
|
||||
ReferenceLineLayerArgs,
|
||||
ReferenceLineLayerConfigResult
|
||||
>;
|
||||
export type ExtendedReferenceLineLayerFn = ExpressionFunctionDefinition<
|
||||
typeof EXTENDED_REFERENCE_LINE_LAYER,
|
||||
Datatable,
|
||||
ExtendedReferenceLineLayerArgs,
|
||||
ExtendedReferenceLineLayerConfigResult
|
||||
>;
|
||||
|
||||
export type YConfigFn = ExpressionFunctionDefinition<typeof Y_CONFIG, null, YConfig, YConfigResult>;
|
||||
export type ExtendedYConfigFn = ExpressionFunctionDefinition<
|
||||
|
|
|
@ -8,13 +8,9 @@
|
|||
|
||||
import { ExecutionContext } from '@kbn/expressions-plugin';
|
||||
import { Dimension, prepareLogTable } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { LayerTypes } from '../constants';
|
||||
import { LayerTypes, REFERENCE_LINE } from '../constants';
|
||||
import { strings } from '../i18n';
|
||||
import {
|
||||
CommonXYDataLayerConfig,
|
||||
CommonXYLayerConfig,
|
||||
CommonXYReferenceLineLayerConfig,
|
||||
} from '../types';
|
||||
import { CommonXYDataLayerConfig, CommonXYLayerConfig, ReferenceLineLayerConfig } from '../types';
|
||||
|
||||
export const logDatatables = (layers: CommonXYLayerConfig[], handlers: ExecutionContext) => {
|
||||
if (!handlers?.inspectorAdapters?.tables) {
|
||||
|
@ -25,16 +21,17 @@ export const logDatatables = (layers: CommonXYLayerConfig[], handlers: Execution
|
|||
handlers.inspectorAdapters.tables.allowCsvExport = true;
|
||||
|
||||
layers.forEach((layer) => {
|
||||
if (layer.layerType === LayerTypes.ANNOTATIONS) {
|
||||
if (layer.layerType === LayerTypes.ANNOTATIONS || layer.type === REFERENCE_LINE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const logTable = prepareLogTable(layer.table, getLayerDimensions(layer), true);
|
||||
handlers.inspectorAdapters.tables.logDatatable(layer.layerId, logTable);
|
||||
});
|
||||
};
|
||||
|
||||
export const getLayerDimensions = (
|
||||
layer: CommonXYDataLayerConfig | CommonXYReferenceLineLayerConfig
|
||||
layer: CommonXYDataLayerConfig | ReferenceLineLayerConfig
|
||||
): Dimension[] => {
|
||||
let xAccessor;
|
||||
let splitAccessor;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import './annotations.scss';
|
||||
import './reference_lines.scss';
|
||||
import './reference_lines/reference_lines.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { snakeCase } from 'lodash';
|
||||
|
|
|
@ -1,369 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { LineAnnotation, RectAnnotation } from '@elastic/charts';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { LayerTypes } from '../../common/constants';
|
||||
import {
|
||||
ReferenceLineLayerArgs,
|
||||
ReferenceLineLayerConfig,
|
||||
ExtendedYConfig,
|
||||
} from '../../common/types';
|
||||
import { ReferenceLineAnnotations, ReferenceLineAnnotationsProps } from './reference_lines';
|
||||
|
||||
const row: Record<string, number> = {
|
||||
xAccessorFirstId: 1,
|
||||
xAccessorSecondId: 2,
|
||||
yAccessorLeftFirstId: 5,
|
||||
yAccessorLeftSecondId: 10,
|
||||
yAccessorRightFirstId: 5,
|
||||
yAccessorRightSecondId: 10,
|
||||
};
|
||||
|
||||
const data: Datatable = {
|
||||
type: 'datatable',
|
||||
rows: [row],
|
||||
columns: Object.keys(row).map((id) => ({
|
||||
id,
|
||||
name: `Static value: ${row[id]}`,
|
||||
meta: {
|
||||
type: 'number',
|
||||
params: { id: 'number' },
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
function createLayers(yConfigs: ReferenceLineLayerArgs['yConfig']): ReferenceLineLayerConfig[] {
|
||||
return [
|
||||
{
|
||||
layerId: 'first',
|
||||
accessors: (yConfigs || []).map(({ forAccessor }) => forAccessor),
|
||||
yConfig: yConfigs,
|
||||
type: 'referenceLineLayer',
|
||||
layerType: LayerTypes.REFERENCELINE,
|
||||
table: data,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
interface YCoords {
|
||||
y0: number | undefined;
|
||||
y1: number | undefined;
|
||||
}
|
||||
interface XCoords {
|
||||
x0: number | undefined;
|
||||
x1: number | undefined;
|
||||
}
|
||||
|
||||
function getAxisFromId(layerPrefix: string): ExtendedYConfig['axisMode'] {
|
||||
return /left/i.test(layerPrefix) ? 'left' : /right/i.test(layerPrefix) ? 'right' : 'bottom';
|
||||
}
|
||||
|
||||
const emptyCoords = { x0: undefined, x1: undefined, y0: undefined, y1: undefined };
|
||||
|
||||
describe('ReferenceLineAnnotations', () => {
|
||||
describe('with fill', () => {
|
||||
let formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
|
||||
let defaultProps: Omit<ReferenceLineAnnotationsProps, 'data' | 'layers'>;
|
||||
|
||||
beforeEach(() => {
|
||||
formatters = {
|
||||
left: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
right: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
bottom: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
};
|
||||
|
||||
defaultProps = {
|
||||
formatters,
|
||||
isHorizontal: false,
|
||||
axesMap: { left: true, right: false },
|
||||
paddingMap: {},
|
||||
};
|
||||
});
|
||||
|
||||
it.each([
|
||||
['yAccessorLeft', 'above'],
|
||||
['yAccessorLeft', 'below'],
|
||||
['yAccessorRight', 'above'],
|
||||
['yAccessorRight', 'below'],
|
||||
] as Array<[string, ExtendedYConfig['fill']]>)(
|
||||
'should render a RectAnnotation for a reference line with fill set: %s %s',
|
||||
(layerPrefix, fill) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
const wrapper = shallow(
|
||||
<ReferenceLineAnnotations
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
const y0 = fill === 'above' ? 5 : undefined;
|
||||
const y1 = fill === 'above' ? undefined : 5;
|
||||
|
||||
expect(wrapper.find(LineAnnotation).exists()).toBe(true);
|
||||
expect(wrapper.find(RectAnnotation).exists()).toBe(true);
|
||||
expect(wrapper.find(RectAnnotation).prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { x0: undefined, x1: undefined, y0, y1 },
|
||||
details: y0 ?? y1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['xAccessor', 'above'],
|
||||
['xAccessor', 'below'],
|
||||
] as Array<[string, ExtendedYConfig['fill']]>)(
|
||||
'should render a RectAnnotation for a reference line with fill set: %s %s',
|
||||
(layerPrefix, fill) => {
|
||||
const wrapper = shallow(
|
||||
<ReferenceLineAnnotations
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
const x0 = fill === 'above' ? 1 : undefined;
|
||||
const x1 = fill === 'above' ? undefined : 1;
|
||||
|
||||
expect(wrapper.find(LineAnnotation).exists()).toBe(true);
|
||||
expect(wrapper.find(RectAnnotation).exists()).toBe(true);
|
||||
expect(wrapper.find(RectAnnotation).prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, x0, x1 },
|
||||
details: x0 ?? x1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['yAccessorLeft', 'above', { y0: 5, y1: 10 }, { y0: 10, y1: undefined }],
|
||||
['yAccessorLeft', 'below', { y0: undefined, y1: 5 }, { y0: 5, y1: 10 }],
|
||||
['yAccessorRight', 'above', { y0: 5, y1: 10 }, { y0: 10, y1: undefined }],
|
||||
['yAccessorRight', 'below', { y0: undefined, y1: 5 }, { y0: 5, y1: 10 }],
|
||||
] as Array<[string, ExtendedYConfig['fill'], YCoords, YCoords]>)(
|
||||
'should avoid overlap between two reference lines with fill in the same direction: 2 x %s %s',
|
||||
(layerPrefix, fill, coordsA, coordsB) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
const wrapper = shallow(
|
||||
<ReferenceLineAnnotations
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find(RectAnnotation).first().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsA },
|
||||
details: coordsA.y0 ?? coordsA.y1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(wrapper.find(RectAnnotation).last().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsB },
|
||||
details: coordsB.y1 ?? coordsB.y0,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['xAccessor', 'above', { x0: 1, x1: 2 }, { x0: 2, x1: undefined }],
|
||||
['xAccessor', 'below', { x0: undefined, x1: 1 }, { x0: 1, x1: 2 }],
|
||||
] as Array<[string, ExtendedYConfig['fill'], XCoords, XCoords]>)(
|
||||
'should avoid overlap between two reference lines with fill in the same direction: 2 x %s %s',
|
||||
(layerPrefix, fill, coordsA, coordsB) => {
|
||||
const wrapper = shallow(
|
||||
<ReferenceLineAnnotations
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find(RectAnnotation).first().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsA },
|
||||
details: coordsA.x0 ?? coordsA.x1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(wrapper.find(RectAnnotation).last().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsB },
|
||||
details: coordsB.x1 ?? coordsB.x0,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(['yAccessorLeft', 'yAccessorRight', 'xAccessor'])(
|
||||
'should let areas in different directions overlap: %s',
|
||||
(layerPrefix) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
|
||||
const wrapper = shallow(
|
||||
<ReferenceLineAnnotations
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill: 'above',
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill: 'below',
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find(RectAnnotation).first().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...(axisMode === 'bottom' ? { x0: 1 } : { y0: 5 }) },
|
||||
details: axisMode === 'bottom' ? 1 : 5,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(wrapper.find(RectAnnotation).last().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...(axisMode === 'bottom' ? { x1: 2 } : { y1: 10 }) },
|
||||
details: axisMode === 'bottom' ? 2 : 10,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['above', { y0: 5, y1: 10 }, { y0: 10, y1: undefined }],
|
||||
['below', { y0: undefined, y1: 5 }, { y0: 5, y1: 10 }],
|
||||
] as Array<[ExtendedYConfig['fill'], YCoords, YCoords]>)(
|
||||
'should be robust and works also for different axes when on same direction: 1x Left + 1x Right both %s',
|
||||
(fill, coordsA, coordsB) => {
|
||||
const wrapper = shallow(
|
||||
<ReferenceLineAnnotations
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `yAccessorLeftFirstId`,
|
||||
axisMode: 'left',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
{
|
||||
forAccessor: `yAccessorRightSecondId`,
|
||||
axisMode: 'right',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find(RectAnnotation).first().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsA },
|
||||
details: coordsA.y0 ?? coordsA.y1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(wrapper.find(RectAnnotation).last().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsB },
|
||||
details: coordsB.y1 ?? coordsB.y0,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,268 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import './reference_lines.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { groupBy } from 'lodash';
|
||||
import { RectAnnotation, AnnotationDomainType, LineAnnotation, Position } from '@elastic/charts';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import type { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import type { CommonXYReferenceLineLayerConfig, IconPosition, YAxisMode } from '../../common/types';
|
||||
import {
|
||||
LINES_MARKER_SIZE,
|
||||
mapVerticalToHorizontalPlacement,
|
||||
Marker,
|
||||
MarkerBody,
|
||||
} from '../helpers';
|
||||
|
||||
export const computeChartMargins = (
|
||||
referenceLinePaddings: Partial<Record<Position, number>>,
|
||||
labelVisibility: Partial<Record<'x' | 'yLeft' | 'yRight', boolean>>,
|
||||
titleVisibility: Partial<Record<'x' | 'yLeft' | 'yRight', boolean>>,
|
||||
axesMap: Record<'left' | 'right', unknown>,
|
||||
isHorizontal: boolean
|
||||
) => {
|
||||
const result: Partial<Record<Position, number>> = {};
|
||||
if (!labelVisibility?.x && !titleVisibility?.x && referenceLinePaddings.bottom) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('bottom') : 'bottom';
|
||||
result[placement] = referenceLinePaddings.bottom;
|
||||
}
|
||||
if (
|
||||
referenceLinePaddings.left &&
|
||||
(isHorizontal || (!labelVisibility?.yLeft && !titleVisibility?.yLeft))
|
||||
) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('left') : 'left';
|
||||
result[placement] = referenceLinePaddings.left;
|
||||
}
|
||||
if (
|
||||
referenceLinePaddings.right &&
|
||||
(isHorizontal || !axesMap.right || (!labelVisibility?.yRight && !titleVisibility?.yRight))
|
||||
) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('right') : 'right';
|
||||
result[placement] = referenceLinePaddings.right;
|
||||
}
|
||||
// there's no top axis, so just check if a margin has been computed
|
||||
if (referenceLinePaddings.top) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('top') : 'top';
|
||||
result[placement] = referenceLinePaddings.top;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// if there's just one axis, put it on the other one
|
||||
// otherwise use the same axis
|
||||
// this function assume the chart is vertical
|
||||
export function getBaseIconPlacement(
|
||||
iconPosition: IconPosition | undefined,
|
||||
axesMap?: Record<string, unknown>,
|
||||
axisMode?: YAxisMode
|
||||
) {
|
||||
if (iconPosition === 'auto') {
|
||||
if (axisMode === 'bottom') {
|
||||
return Position.Top;
|
||||
}
|
||||
if (axesMap) {
|
||||
if (axisMode === 'left') {
|
||||
return axesMap.right ? Position.Left : Position.Right;
|
||||
}
|
||||
return axesMap.left ? Position.Right : Position.Left;
|
||||
}
|
||||
}
|
||||
|
||||
if (iconPosition === 'left') {
|
||||
return Position.Left;
|
||||
}
|
||||
if (iconPosition === 'right') {
|
||||
return Position.Right;
|
||||
}
|
||||
if (iconPosition === 'below') {
|
||||
return Position.Bottom;
|
||||
}
|
||||
return Position.Top;
|
||||
}
|
||||
|
||||
export interface ReferenceLineAnnotationsProps {
|
||||
layers: CommonXYReferenceLineLayerConfig[];
|
||||
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
|
||||
axesMap: Record<'left' | 'right', boolean>;
|
||||
isHorizontal: boolean;
|
||||
paddingMap: Partial<Record<Position, number>>;
|
||||
}
|
||||
|
||||
export const ReferenceLineAnnotations = ({
|
||||
layers,
|
||||
formatters,
|
||||
axesMap,
|
||||
isHorizontal,
|
||||
paddingMap,
|
||||
}: ReferenceLineAnnotationsProps) => {
|
||||
return (
|
||||
<>
|
||||
{layers.flatMap((layer) => {
|
||||
if (!layer.yConfig) {
|
||||
return [];
|
||||
}
|
||||
const { columnToLabel, yConfig: yConfigs, table } = layer;
|
||||
const columnToLabelMap: Record<string, string> = columnToLabel
|
||||
? JSON.parse(columnToLabel)
|
||||
: {};
|
||||
|
||||
const row = table.rows[0];
|
||||
|
||||
const yConfigByValue = yConfigs.sort(
|
||||
({ forAccessor: idA }, { forAccessor: idB }) => row[idA] - row[idB]
|
||||
);
|
||||
|
||||
const groupedByDirection = groupBy(yConfigByValue, 'fill');
|
||||
if (groupedByDirection.below) {
|
||||
groupedByDirection.below.reverse();
|
||||
}
|
||||
|
||||
return yConfigByValue.flatMap((yConfig, i) => {
|
||||
// Find the formatter for the given axis
|
||||
const groupId =
|
||||
yConfig.axisMode === 'bottom'
|
||||
? undefined
|
||||
: yConfig.axisMode === 'right'
|
||||
? 'right'
|
||||
: 'left';
|
||||
|
||||
const formatter = formatters[groupId || 'bottom'];
|
||||
|
||||
const defaultColor = euiLightVars.euiColorDarkShade;
|
||||
|
||||
// get the position for vertical chart
|
||||
const markerPositionVertical = getBaseIconPlacement(
|
||||
yConfig.iconPosition,
|
||||
axesMap,
|
||||
yConfig.axisMode
|
||||
);
|
||||
// the padding map is built for vertical chart
|
||||
const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE;
|
||||
|
||||
const props = {
|
||||
groupId,
|
||||
marker: (
|
||||
<Marker
|
||||
config={yConfig}
|
||||
label={columnToLabelMap[yConfig.forAccessor]}
|
||||
isHorizontal={isHorizontal}
|
||||
hasReducedPadding={hasReducedPadding}
|
||||
/>
|
||||
),
|
||||
markerBody: (
|
||||
<MarkerBody
|
||||
label={
|
||||
yConfig.textVisibility && !hasReducedPadding
|
||||
? columnToLabelMap[yConfig.forAccessor]
|
||||
: undefined
|
||||
}
|
||||
isHorizontal={
|
||||
(!isHorizontal && yConfig.axisMode === 'bottom') ||
|
||||
(isHorizontal && yConfig.axisMode !== 'bottom')
|
||||
}
|
||||
/>
|
||||
),
|
||||
// rotate the position if required
|
||||
markerPosition: isHorizontal
|
||||
? mapVerticalToHorizontalPlacement(markerPositionVertical)
|
||||
: markerPositionVertical,
|
||||
};
|
||||
const annotations = [];
|
||||
|
||||
const sharedStyle = {
|
||||
strokeWidth: yConfig.lineWidth || 1,
|
||||
stroke: yConfig.color || defaultColor,
|
||||
dash:
|
||||
yConfig.lineStyle === 'dashed'
|
||||
? [(yConfig.lineWidth || 1) * 3, yConfig.lineWidth || 1]
|
||||
: yConfig.lineStyle === 'dotted'
|
||||
? [yConfig.lineWidth || 1, yConfig.lineWidth || 1]
|
||||
: undefined,
|
||||
};
|
||||
|
||||
annotations.push(
|
||||
<LineAnnotation
|
||||
{...props}
|
||||
id={`${layer.layerId}-${yConfig.forAccessor}-line`}
|
||||
key={`${layer.layerId}-${yConfig.forAccessor}-line`}
|
||||
dataValues={table.rows.map(() => ({
|
||||
dataValue: row[yConfig.forAccessor],
|
||||
header: columnToLabelMap[yConfig.forAccessor],
|
||||
details: formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor],
|
||||
}))}
|
||||
domainType={
|
||||
yConfig.axisMode === 'bottom'
|
||||
? AnnotationDomainType.XDomain
|
||||
: AnnotationDomainType.YDomain
|
||||
}
|
||||
style={{
|
||||
line: {
|
||||
...sharedStyle,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (yConfig.fill && yConfig.fill !== 'none') {
|
||||
const isFillAbove = yConfig.fill === 'above';
|
||||
const indexFromSameType = groupedByDirection[yConfig.fill].findIndex(
|
||||
({ forAccessor }) => forAccessor === yConfig.forAccessor
|
||||
);
|
||||
const shouldCheckNextReferenceLine =
|
||||
indexFromSameType < groupedByDirection[yConfig.fill].length - 1;
|
||||
annotations.push(
|
||||
<RectAnnotation
|
||||
{...props}
|
||||
id={`${layer.layerId}-${yConfig.forAccessor}-rect`}
|
||||
key={`${layer.layerId}-${yConfig.forAccessor}-rect`}
|
||||
dataValues={table.rows.map(() => {
|
||||
const nextValue = shouldCheckNextReferenceLine
|
||||
? row[groupedByDirection[yConfig.fill!][indexFromSameType + 1].forAccessor]
|
||||
: undefined;
|
||||
if (yConfig.axisMode === 'bottom') {
|
||||
return {
|
||||
coordinates: {
|
||||
x0: isFillAbove ? row[yConfig.forAccessor] : nextValue,
|
||||
y0: undefined,
|
||||
x1: isFillAbove ? nextValue : row[yConfig.forAccessor],
|
||||
y1: undefined,
|
||||
},
|
||||
header: columnToLabelMap[yConfig.forAccessor],
|
||||
details:
|
||||
formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor],
|
||||
};
|
||||
}
|
||||
return {
|
||||
coordinates: {
|
||||
x0: undefined,
|
||||
y0: isFillAbove ? row[yConfig.forAccessor] : nextValue,
|
||||
x1: undefined,
|
||||
y1: isFillAbove ? nextValue : row[yConfig.forAccessor],
|
||||
},
|
||||
header: columnToLabelMap[yConfig.forAccessor],
|
||||
details:
|
||||
formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor],
|
||||
};
|
||||
})}
|
||||
style={{
|
||||
...sharedStyle,
|
||||
fill: yConfig.color || defaultColor,
|
||||
opacity: 0.1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return annotations;
|
||||
});
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './reference_lines';
|
||||
export * from './utils';
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { ReferenceLineConfig } from '../../../common/types';
|
||||
import { getGroupId } from './utils';
|
||||
import { ReferenceLineAnnotations } from './reference_line_annotations';
|
||||
|
||||
interface ReferenceLineProps {
|
||||
layer: ReferenceLineConfig;
|
||||
paddingMap: Partial<Record<Position, number>>;
|
||||
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
|
||||
axesMap: Record<'left' | 'right', boolean>;
|
||||
isHorizontal: boolean;
|
||||
}
|
||||
|
||||
export const ReferenceLine: FC<ReferenceLineProps> = ({
|
||||
layer,
|
||||
axesMap,
|
||||
formatters,
|
||||
paddingMap,
|
||||
isHorizontal,
|
||||
}) => {
|
||||
const {
|
||||
yConfig: [yConfig],
|
||||
} = layer;
|
||||
|
||||
if (!yConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { axisMode, value } = yConfig;
|
||||
|
||||
// Find the formatter for the given axis
|
||||
const groupId = getGroupId(axisMode);
|
||||
|
||||
const formatter = formatters[groupId || 'bottom'];
|
||||
const id = `${layer.layerId}-${value}`;
|
||||
|
||||
return (
|
||||
<ReferenceLineAnnotations
|
||||
config={{ id, ...yConfig }}
|
||||
paddingMap={paddingMap}
|
||||
axesMap={axesMap}
|
||||
formatter={formatter}
|
||||
isHorizontal={isHorizontal}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { AnnotationDomainType, LineAnnotation, Position, RectAnnotation } from '@elastic/charts';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import React, { FC } from 'react';
|
||||
import { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { LINES_MARKER_SIZE } from '../../helpers';
|
||||
import {
|
||||
AvailableReferenceLineIcon,
|
||||
FillStyle,
|
||||
IconPosition,
|
||||
LineStyle,
|
||||
YAxisMode,
|
||||
} from '../../../common/types';
|
||||
import {
|
||||
getBaseIconPlacement,
|
||||
getBottomRect,
|
||||
getGroupId,
|
||||
getHorizontalRect,
|
||||
getLineAnnotationProps,
|
||||
getSharedStyle,
|
||||
} from './utils';
|
||||
|
||||
export interface ReferenceLineAnnotationConfig {
|
||||
id: string;
|
||||
name?: string;
|
||||
value: number;
|
||||
nextValue?: number;
|
||||
icon?: AvailableReferenceLineIcon;
|
||||
lineWidth?: number;
|
||||
lineStyle?: LineStyle;
|
||||
fill?: FillStyle;
|
||||
iconPosition?: IconPosition;
|
||||
textVisibility?: boolean;
|
||||
axisMode?: YAxisMode;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
config: ReferenceLineAnnotationConfig;
|
||||
paddingMap: Partial<Record<Position, number>>;
|
||||
formatter?: FieldFormat;
|
||||
axesMap: Record<'left' | 'right', boolean>;
|
||||
isHorizontal: boolean;
|
||||
}
|
||||
|
||||
const getRectDataValue = (
|
||||
annotationConfig: ReferenceLineAnnotationConfig,
|
||||
formatter: FieldFormat | undefined
|
||||
) => {
|
||||
const { name, value, nextValue, fill, axisMode } = annotationConfig;
|
||||
const isFillAbove = fill === 'above';
|
||||
|
||||
if (axisMode === 'bottom') {
|
||||
return getBottomRect(name, isFillAbove, formatter, value, nextValue);
|
||||
}
|
||||
|
||||
return getHorizontalRect(name, isFillAbove, formatter, value, nextValue);
|
||||
};
|
||||
|
||||
export const ReferenceLineAnnotations: FC<Props> = ({
|
||||
config,
|
||||
axesMap,
|
||||
formatter,
|
||||
paddingMap,
|
||||
isHorizontal,
|
||||
}) => {
|
||||
const { id, axisMode, iconPosition, name, textVisibility, value, fill, color } = config;
|
||||
|
||||
// Find the formatter for the given axis
|
||||
const groupId = getGroupId(axisMode);
|
||||
const defaultColor = euiLightVars.euiColorDarkShade;
|
||||
// get the position for vertical chart
|
||||
const markerPositionVertical = getBaseIconPlacement(iconPosition, axesMap, axisMode);
|
||||
// the padding map is built for vertical chart
|
||||
const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE;
|
||||
|
||||
const props = getLineAnnotationProps(
|
||||
config,
|
||||
{
|
||||
markerLabel: name,
|
||||
markerBodyLabel: textVisibility && !hasReducedPadding ? name : undefined,
|
||||
},
|
||||
axesMap,
|
||||
paddingMap,
|
||||
groupId,
|
||||
isHorizontal
|
||||
);
|
||||
|
||||
const sharedStyle = getSharedStyle(config);
|
||||
|
||||
const dataValues = {
|
||||
dataValue: value,
|
||||
header: name,
|
||||
details: formatter?.convert(value) || value.toString(),
|
||||
};
|
||||
|
||||
const line = (
|
||||
<LineAnnotation
|
||||
{...props}
|
||||
id={`${id}-line`}
|
||||
key={`${id}-line`}
|
||||
dataValues={[dataValues]}
|
||||
domainType={
|
||||
axisMode === 'bottom' ? AnnotationDomainType.XDomain : AnnotationDomainType.YDomain
|
||||
}
|
||||
style={{ line: { ...sharedStyle, opacity: 1 } }}
|
||||
/>
|
||||
);
|
||||
|
||||
let rect;
|
||||
if (fill && fill !== 'none') {
|
||||
const rectDataValues = getRectDataValue(config, formatter);
|
||||
|
||||
rect = (
|
||||
<RectAnnotation
|
||||
{...props}
|
||||
id={`${id}-rect`}
|
||||
key={`${id}-rect`}
|
||||
dataValues={[rectDataValues]}
|
||||
style={{ ...sharedStyle, fill: color || defaultColor, opacity: 0.1 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{line}
|
||||
{rect}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { groupBy } from 'lodash';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { ReferenceLineLayerConfig } from '../../../common/types';
|
||||
import { getGroupId } from './utils';
|
||||
import { ReferenceLineAnnotations } from './reference_line_annotations';
|
||||
|
||||
interface ReferenceLineLayerProps {
|
||||
layer: ReferenceLineLayerConfig;
|
||||
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
|
||||
paddingMap: Partial<Record<Position, number>>;
|
||||
axesMap: Record<'left' | 'right', boolean>;
|
||||
isHorizontal: boolean;
|
||||
}
|
||||
|
||||
export const ReferenceLineLayer: FC<ReferenceLineLayerProps> = ({
|
||||
layer,
|
||||
formatters,
|
||||
paddingMap,
|
||||
axesMap,
|
||||
isHorizontal,
|
||||
}) => {
|
||||
if (!layer.yConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { columnToLabel, yConfig: yConfigs, table } = layer;
|
||||
const columnToLabelMap: Record<string, string> = columnToLabel ? JSON.parse(columnToLabel) : {};
|
||||
|
||||
const row = table.rows[0];
|
||||
|
||||
const yConfigByValue = yConfigs.sort(
|
||||
({ forAccessor: idA }, { forAccessor: idB }) => row[idA] - row[idB]
|
||||
);
|
||||
|
||||
const groupedByDirection = groupBy(yConfigByValue, 'fill');
|
||||
if (groupedByDirection.below) {
|
||||
groupedByDirection.below.reverse();
|
||||
}
|
||||
|
||||
const referenceLineElements = yConfigByValue.flatMap((yConfig) => {
|
||||
const { axisMode } = yConfig;
|
||||
|
||||
// Find the formatter for the given axis
|
||||
const groupId = getGroupId(axisMode);
|
||||
|
||||
const formatter = formatters[groupId || 'bottom'];
|
||||
const name = columnToLabelMap[yConfig.forAccessor];
|
||||
const value = row[yConfig.forAccessor];
|
||||
const yConfigsWithSameDirection = groupedByDirection[yConfig.fill!];
|
||||
const indexFromSameType = yConfigsWithSameDirection.findIndex(
|
||||
({ forAccessor }) => forAccessor === yConfig.forAccessor
|
||||
);
|
||||
|
||||
const shouldCheckNextReferenceLine = indexFromSameType < yConfigsWithSameDirection.length - 1;
|
||||
|
||||
const nextValue = shouldCheckNextReferenceLine
|
||||
? row[yConfigsWithSameDirection[indexFromSameType + 1].forAccessor]
|
||||
: undefined;
|
||||
|
||||
const { forAccessor, type, ...restAnnotationConfig } = yConfig;
|
||||
const id = `${layer.layerId}-${yConfig.forAccessor}`;
|
||||
|
||||
return (
|
||||
<ReferenceLineAnnotations
|
||||
key={id}
|
||||
config={{
|
||||
id,
|
||||
value,
|
||||
nextValue,
|
||||
name,
|
||||
...restAnnotationConfig,
|
||||
}}
|
||||
paddingMap={paddingMap}
|
||||
axesMap={axesMap}
|
||||
formatter={formatter}
|
||||
isHorizontal={isHorizontal}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return <>{referenceLineElements}</>;
|
||||
};
|
|
@ -0,0 +1,683 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { LineAnnotation, RectAnnotation } from '@elastic/charts';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { LayerTypes } from '../../../common/constants';
|
||||
import {
|
||||
ReferenceLineLayerArgs,
|
||||
ReferenceLineLayerConfig,
|
||||
ExtendedYConfig,
|
||||
ReferenceLineArgs,
|
||||
ReferenceLineConfig,
|
||||
} from '../../../common/types';
|
||||
import { ReferenceLines, ReferenceLinesProps } from './reference_lines';
|
||||
import { ReferenceLineLayer } from './reference_line_layer';
|
||||
import { ReferenceLine } from './reference_line';
|
||||
import { ReferenceLineAnnotations } from './reference_line_annotations';
|
||||
|
||||
const row: Record<string, number> = {
|
||||
xAccessorFirstId: 1,
|
||||
xAccessorSecondId: 2,
|
||||
yAccessorLeftFirstId: 5,
|
||||
yAccessorLeftSecondId: 10,
|
||||
yAccessorRightFirstId: 5,
|
||||
yAccessorRightSecondId: 10,
|
||||
};
|
||||
|
||||
const data: Datatable = {
|
||||
type: 'datatable',
|
||||
rows: [row],
|
||||
columns: Object.keys(row).map((id) => ({
|
||||
id,
|
||||
name: `Static value: ${row[id]}`,
|
||||
meta: {
|
||||
type: 'number',
|
||||
params: { id: 'number' },
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
function createLayers(yConfigs: ReferenceLineLayerArgs['yConfig']): ReferenceLineLayerConfig[] {
|
||||
return [
|
||||
{
|
||||
layerId: 'first',
|
||||
accessors: (yConfigs || []).map(({ forAccessor }) => forAccessor),
|
||||
yConfig: yConfigs,
|
||||
type: 'referenceLineLayer',
|
||||
layerType: LayerTypes.REFERENCELINE,
|
||||
table: data,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function createReferenceLine(
|
||||
layerId: string,
|
||||
lineLength: number,
|
||||
args: ReferenceLineArgs
|
||||
): ReferenceLineConfig {
|
||||
return {
|
||||
layerId,
|
||||
type: 'referenceLine',
|
||||
layerType: 'referenceLine',
|
||||
lineLength,
|
||||
yConfig: [{ type: 'referenceLineYConfig', ...args }],
|
||||
};
|
||||
}
|
||||
|
||||
interface YCoords {
|
||||
y0: number | undefined;
|
||||
y1: number | undefined;
|
||||
}
|
||||
interface XCoords {
|
||||
x0: number | undefined;
|
||||
x1: number | undefined;
|
||||
}
|
||||
|
||||
function getAxisFromId(layerPrefix: string): ExtendedYConfig['axisMode'] {
|
||||
return /left/i.test(layerPrefix) ? 'left' : /right/i.test(layerPrefix) ? 'right' : 'bottom';
|
||||
}
|
||||
|
||||
const emptyCoords = { x0: undefined, x1: undefined, y0: undefined, y1: undefined };
|
||||
|
||||
describe('ReferenceLines', () => {
|
||||
describe('referenceLineLayers', () => {
|
||||
let formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
|
||||
let defaultProps: Omit<ReferenceLinesProps, 'data' | 'layers'>;
|
||||
|
||||
beforeEach(() => {
|
||||
formatters = {
|
||||
left: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
right: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
bottom: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
};
|
||||
|
||||
defaultProps = {
|
||||
formatters,
|
||||
isHorizontal: false,
|
||||
axesMap: { left: true, right: false },
|
||||
paddingMap: {},
|
||||
};
|
||||
});
|
||||
|
||||
it.each([
|
||||
['yAccessorLeft', 'above'],
|
||||
['yAccessorLeft', 'below'],
|
||||
['yAccessorRight', 'above'],
|
||||
['yAccessorRight', 'below'],
|
||||
] as Array<[string, ExtendedYConfig['fill']]>)(
|
||||
'should render a RectAnnotation for a reference line with fill set: %s %s',
|
||||
(layerPrefix, fill) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
const referenceLineLayer = wrapper.find(ReferenceLineLayer).dive();
|
||||
|
||||
const y0 = fill === 'above' ? 5 : undefined;
|
||||
const y1 = fill === 'above' ? undefined : 5;
|
||||
|
||||
const referenceLineAnnotation = referenceLineLayer.find(ReferenceLineAnnotations).dive();
|
||||
expect(referenceLineAnnotation.find(LineAnnotation).exists()).toBe(true);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).exists()).toBe(true);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { x0: undefined, x1: undefined, y0, y1 },
|
||||
details: y0 ?? y1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['xAccessor', 'above'],
|
||||
['xAccessor', 'below'],
|
||||
] as Array<[string, ExtendedYConfig['fill']]>)(
|
||||
'should render a RectAnnotation for a reference line with fill set: %s %s',
|
||||
(layerPrefix, fill) => {
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
const referenceLineLayer = wrapper.find(ReferenceLineLayer).dive();
|
||||
|
||||
const x0 = fill === 'above' ? 1 : undefined;
|
||||
const x1 = fill === 'above' ? undefined : 1;
|
||||
|
||||
const referenceLineAnnotation = referenceLineLayer.find(ReferenceLineAnnotations).dive();
|
||||
expect(referenceLineAnnotation.find(LineAnnotation).exists()).toBe(true);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).exists()).toBe(true);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, x0, x1 },
|
||||
details: x0 ?? x1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['yAccessorLeft', 'above', { y0: 5, y1: 10 }, { y0: 10, y1: undefined }],
|
||||
['yAccessorLeft', 'below', { y0: undefined, y1: 5 }, { y0: 5, y1: 10 }],
|
||||
['yAccessorRight', 'above', { y0: 5, y1: 10 }, { y0: 10, y1: undefined }],
|
||||
['yAccessorRight', 'below', { y0: undefined, y1: 5 }, { y0: 5, y1: 10 }],
|
||||
] as Array<[string, ExtendedYConfig['fill'], YCoords, YCoords]>)(
|
||||
'should avoid overlap between two reference lines with fill in the same direction: 2 x %s %s',
|
||||
(layerPrefix, fill, coordsA, coordsB) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
const referenceLineLayer = wrapper.find(ReferenceLineLayer).dive();
|
||||
const referenceLineAnnotations = referenceLineLayer.find(ReferenceLineAnnotations);
|
||||
|
||||
expect(
|
||||
referenceLineAnnotations.first().dive().find(RectAnnotation).prop('dataValues')
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsA },
|
||||
details: coordsA.y0 ?? coordsA.y1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(
|
||||
referenceLineAnnotations.last().dive().find(RectAnnotation).prop('dataValues')
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsB },
|
||||
details: coordsB.y1 ?? coordsB.y0,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['xAccessor', 'above', { x0: 1, x1: 2 }, { x0: 2, x1: undefined }],
|
||||
['xAccessor', 'below', { x0: undefined, x1: 1 }, { x0: 1, x1: 2 }],
|
||||
] as Array<[string, ExtendedYConfig['fill'], XCoords, XCoords]>)(
|
||||
'should avoid overlap between two reference lines with fill in the same direction: 2 x %s %s',
|
||||
(layerPrefix, fill, coordsA, coordsB) => {
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'extendedYConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
const referenceLineLayer = wrapper.find(ReferenceLineLayer).dive();
|
||||
const referenceLineAnnotations = referenceLineLayer.find(ReferenceLineAnnotations);
|
||||
|
||||
expect(
|
||||
referenceLineAnnotations.first().dive().find(RectAnnotation).prop('dataValues')
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsA },
|
||||
details: coordsA.x0 ?? coordsA.x1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(
|
||||
referenceLineAnnotations.last().dive().find(RectAnnotation).prop('dataValues')
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsB },
|
||||
details: coordsB.x1 ?? coordsB.x0,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(['yAccessorLeft', 'yAccessorRight', 'xAccessor'])(
|
||||
'should let areas in different directions overlap: %s',
|
||||
(layerPrefix) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill: 'above',
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill: 'below',
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
const referenceLineLayer = wrapper.find(ReferenceLineLayer).dive();
|
||||
const referenceLineAnnotations = referenceLineLayer.find(ReferenceLineAnnotations);
|
||||
|
||||
expect(
|
||||
referenceLineAnnotations.first().dive().find(RectAnnotation).prop('dataValues')
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...(axisMode === 'bottom' ? { x0: 1 } : { y0: 5 }) },
|
||||
details: axisMode === 'bottom' ? 1 : 5,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(
|
||||
referenceLineAnnotations.last().dive().find(RectAnnotation).prop('dataValues')
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...(axisMode === 'bottom' ? { x1: 2 } : { y1: 10 }) },
|
||||
details: axisMode === 'bottom' ? 2 : 10,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['above', { y0: 5, y1: 10 }, { y0: 10, y1: undefined }],
|
||||
['below', { y0: undefined, y1: 5 }, { y0: 5, y1: 10 }],
|
||||
] as Array<[ExtendedYConfig['fill'], YCoords, YCoords]>)(
|
||||
'should be robust and works also for different axes when on same direction: 1x Left + 1x Right both %s',
|
||||
(fill, coordsA, coordsB) => {
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={createLayers([
|
||||
{
|
||||
forAccessor: `yAccessorLeftFirstId`,
|
||||
axisMode: 'left',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
{
|
||||
forAccessor: `yAccessorRightSecondId`,
|
||||
axisMode: 'right',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'extendedYConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
const referenceLineLayer = wrapper.find(ReferenceLineLayer).dive();
|
||||
const referenceLineAnnotations = referenceLineLayer.find(ReferenceLineAnnotations);
|
||||
|
||||
expect(
|
||||
referenceLineAnnotations.first().dive().find(RectAnnotation).prop('dataValues')
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsA },
|
||||
details: coordsA.y0 ?? coordsA.y1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(
|
||||
referenceLineAnnotations.last().dive().find(RectAnnotation).prop('dataValues')
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsB },
|
||||
details: coordsB.y1 ?? coordsB.y0,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('referenceLines', () => {
|
||||
let formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
|
||||
let defaultProps: Omit<ReferenceLinesProps, 'data' | 'layers'>;
|
||||
|
||||
beforeEach(() => {
|
||||
formatters = {
|
||||
left: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
right: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
bottom: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
|
||||
};
|
||||
|
||||
defaultProps = {
|
||||
formatters,
|
||||
isHorizontal: false,
|
||||
axesMap: { left: true, right: false },
|
||||
paddingMap: {},
|
||||
};
|
||||
});
|
||||
|
||||
it.each([
|
||||
['yAccessorLeft', 'above'],
|
||||
['yAccessorLeft', 'below'],
|
||||
['yAccessorRight', 'above'],
|
||||
['yAccessorRight', 'below'],
|
||||
] as Array<[string, ExtendedYConfig['fill']]>)(
|
||||
'should render a RectAnnotation for a reference line with fill set: %s %s',
|
||||
(layerPrefix, fill) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
const value = 5;
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={[
|
||||
createReferenceLine(layerPrefix, 1, {
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
value,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
const referenceLine = wrapper.find(ReferenceLine).dive();
|
||||
const referenceLineAnnotation = referenceLine.find(ReferenceLineAnnotations).dive();
|
||||
|
||||
const y0 = fill === 'above' ? value : undefined;
|
||||
const y1 = fill === 'above' ? undefined : value;
|
||||
|
||||
expect(referenceLineAnnotation.find(LineAnnotation).exists()).toBe(true);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).exists()).toBe(true);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { x0: undefined, x1: undefined, y0, y1 },
|
||||
details: y0 ?? y1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['xAccessor', 'above'],
|
||||
['xAccessor', 'below'],
|
||||
] as Array<[string, ExtendedYConfig['fill']]>)(
|
||||
'should render a RectAnnotation for a reference line with fill set: %s %s',
|
||||
(layerPrefix, fill) => {
|
||||
const value = 1;
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={[
|
||||
createReferenceLine(layerPrefix, 1, {
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
value,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
const referenceLine = wrapper.find(ReferenceLine).dive();
|
||||
const referenceLineAnnotation = referenceLine.find(ReferenceLineAnnotations).dive();
|
||||
|
||||
const x0 = fill === 'above' ? value : undefined;
|
||||
const x1 = fill === 'above' ? undefined : value;
|
||||
|
||||
expect(referenceLineAnnotation.find(LineAnnotation).exists()).toBe(true);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).exists()).toBe(true);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, x0, x1 },
|
||||
details: x0 ?? x1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['yAccessorLeft', 'above', { y0: 10, y1: undefined }, { y0: 10, y1: undefined }],
|
||||
['yAccessorLeft', 'below', { y0: undefined, y1: 5 }, { y0: undefined, y1: 5 }],
|
||||
] as Array<[string, ExtendedYConfig['fill'], YCoords, YCoords]>)(
|
||||
'should avoid overlap between two reference lines with fill in the same direction: 2 x %s %s',
|
||||
(layerPrefix, fill, coordsA, coordsB) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
const value = coordsA.y0 ?? coordsA.y1!;
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={[
|
||||
createReferenceLine(layerPrefix, 10, {
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
value,
|
||||
}),
|
||||
createReferenceLine(layerPrefix, 10, {
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
value,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
const referenceLine = wrapper.find(ReferenceLine).first().dive();
|
||||
const referenceLineAnnotation = referenceLine.find(ReferenceLineAnnotations).dive();
|
||||
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).first().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsA },
|
||||
details: value,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
expect(referenceLineAnnotation.find(RectAnnotation).last().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsB },
|
||||
details: value,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
['xAccessor', 'above', { x0: 1, x1: undefined }, { x0: 1, x1: undefined }],
|
||||
['xAccessor', 'below', { x0: undefined, x1: 1 }, { x0: undefined, x1: 1 }],
|
||||
] as Array<[string, ExtendedYConfig['fill'], XCoords, XCoords]>)(
|
||||
'should avoid overlap between two reference lines with fill in the same direction: 2 x %s %s',
|
||||
(layerPrefix, fill, coordsA, coordsB) => {
|
||||
const value = coordsA.x0 ?? coordsA.x1!;
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={[
|
||||
createReferenceLine(layerPrefix, 10, {
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
value,
|
||||
}),
|
||||
createReferenceLine(layerPrefix, 10, {
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
value,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
const referenceLine1 = wrapper.find(ReferenceLine).first().dive();
|
||||
const referenceLineAnnotation1 = referenceLine1.find(ReferenceLineAnnotations).dive();
|
||||
|
||||
expect(referenceLineAnnotation1.find(RectAnnotation).first().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsA },
|
||||
details: value,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
const referenceLine2 = wrapper.find(ReferenceLine).last().dive();
|
||||
const referenceLineAnnotation2 = referenceLine2.find(ReferenceLineAnnotations).dive();
|
||||
|
||||
expect(referenceLineAnnotation2.find(RectAnnotation).last().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: { ...emptyCoords, ...coordsB },
|
||||
details: value,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(['yAccessorLeft', 'yAccessorRight', 'xAccessor'])(
|
||||
'should let areas in different directions overlap: %s',
|
||||
(layerPrefix) => {
|
||||
const axisMode = getAxisFromId(layerPrefix);
|
||||
const value1 = 1;
|
||||
const value2 = 10;
|
||||
const wrapper = shallow(
|
||||
<ReferenceLines
|
||||
{...defaultProps}
|
||||
layers={[
|
||||
createReferenceLine(layerPrefix, 10, {
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill: 'above',
|
||||
value: value1,
|
||||
}),
|
||||
createReferenceLine(layerPrefix, 10, {
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill: 'below',
|
||||
value: value2,
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
const referenceLine1 = wrapper.find(ReferenceLine).first().dive();
|
||||
const referenceLineAnnotation1 = referenceLine1.find(ReferenceLineAnnotations).dive();
|
||||
|
||||
expect(referenceLineAnnotation1.find(RectAnnotation).first().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: {
|
||||
...emptyCoords,
|
||||
...(axisMode === 'bottom' ? { x0: value1 } : { y0: value1 }),
|
||||
},
|
||||
details: value1,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
const referenceLine2 = wrapper.find(ReferenceLine).last().dive();
|
||||
const referenceLineAnnotation2 = referenceLine2.find(ReferenceLineAnnotations).dive();
|
||||
|
||||
expect(referenceLineAnnotation2.find(RectAnnotation).last().prop('dataValues')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
coordinates: {
|
||||
...emptyCoords,
|
||||
...(axisMode === 'bottom' ? { x1: value2 } : { y1: value2 }),
|
||||
},
|
||||
details: value2,
|
||||
header: undefined,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import './reference_lines.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { Position } from '@elastic/charts';
|
||||
import type { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import type { CommonXYReferenceLineLayerConfig } from '../../../common/types';
|
||||
import { isReferenceLine, mapVerticalToHorizontalPlacement } from '../../helpers';
|
||||
import { ReferenceLineLayer } from './reference_line_layer';
|
||||
import { ReferenceLine } from './reference_line';
|
||||
|
||||
export const computeChartMargins = (
|
||||
referenceLinePaddings: Partial<Record<Position, number>>,
|
||||
labelVisibility: Partial<Record<'x' | 'yLeft' | 'yRight', boolean>>,
|
||||
titleVisibility: Partial<Record<'x' | 'yLeft' | 'yRight', boolean>>,
|
||||
axesMap: Record<'left' | 'right', unknown>,
|
||||
isHorizontal: boolean
|
||||
) => {
|
||||
const result: Partial<Record<Position, number>> = {};
|
||||
if (!labelVisibility?.x && !titleVisibility?.x && referenceLinePaddings.bottom) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('bottom') : 'bottom';
|
||||
result[placement] = referenceLinePaddings.bottom;
|
||||
}
|
||||
if (
|
||||
referenceLinePaddings.left &&
|
||||
(isHorizontal || (!labelVisibility?.yLeft && !titleVisibility?.yLeft))
|
||||
) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('left') : 'left';
|
||||
result[placement] = referenceLinePaddings.left;
|
||||
}
|
||||
if (
|
||||
referenceLinePaddings.right &&
|
||||
(isHorizontal || !axesMap.right || (!labelVisibility?.yRight && !titleVisibility?.yRight))
|
||||
) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('right') : 'right';
|
||||
result[placement] = referenceLinePaddings.right;
|
||||
}
|
||||
// there's no top axis, so just check if a margin has been computed
|
||||
if (referenceLinePaddings.top) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('top') : 'top';
|
||||
result[placement] = referenceLinePaddings.top;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export interface ReferenceLinesProps {
|
||||
layers: CommonXYReferenceLineLayerConfig[];
|
||||
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
|
||||
axesMap: Record<'left' | 'right', boolean>;
|
||||
isHorizontal: boolean;
|
||||
paddingMap: Partial<Record<Position, number>>;
|
||||
}
|
||||
|
||||
export const ReferenceLines = ({ layers, ...rest }: ReferenceLinesProps) => {
|
||||
return (
|
||||
<>
|
||||
{layers.flatMap((layer) => {
|
||||
if (!layer.yConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isReferenceLine(layer)) {
|
||||
return <ReferenceLine key={`referenceLine-${layer.layerId}`} layer={layer} {...rest} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ReferenceLineLayer key={`referenceLine-${layer.layerId}`} layer={layer} {...rest} />
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import { FieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import { IconPosition, YAxisMode } from '../../../common/types';
|
||||
import {
|
||||
LINES_MARKER_SIZE,
|
||||
mapVerticalToHorizontalPlacement,
|
||||
Marker,
|
||||
MarkerBody,
|
||||
} from '../../helpers';
|
||||
import { ReferenceLineAnnotationConfig } from './reference_line_annotations';
|
||||
|
||||
// if there's just one axis, put it on the other one
|
||||
// otherwise use the same axis
|
||||
// this function assume the chart is vertical
|
||||
export function getBaseIconPlacement(
|
||||
iconPosition: IconPosition | undefined,
|
||||
axesMap?: Record<string, unknown>,
|
||||
axisMode?: YAxisMode
|
||||
) {
|
||||
if (iconPosition === 'auto') {
|
||||
if (axisMode === 'bottom') {
|
||||
return Position.Top;
|
||||
}
|
||||
if (axesMap) {
|
||||
if (axisMode === 'left') {
|
||||
return axesMap.right ? Position.Left : Position.Right;
|
||||
}
|
||||
return axesMap.left ? Position.Right : Position.Left;
|
||||
}
|
||||
}
|
||||
|
||||
if (iconPosition === 'left') {
|
||||
return Position.Left;
|
||||
}
|
||||
if (iconPosition === 'right') {
|
||||
return Position.Right;
|
||||
}
|
||||
if (iconPosition === 'below') {
|
||||
return Position.Bottom;
|
||||
}
|
||||
return Position.Top;
|
||||
}
|
||||
|
||||
export const getSharedStyle = (config: ReferenceLineAnnotationConfig) => ({
|
||||
strokeWidth: config.lineWidth || 1,
|
||||
stroke: config.color || euiLightVars.euiColorDarkShade,
|
||||
dash:
|
||||
config.lineStyle === 'dashed'
|
||||
? [(config.lineWidth || 1) * 3, config.lineWidth || 1]
|
||||
: config.lineStyle === 'dotted'
|
||||
? [config.lineWidth || 1, config.lineWidth || 1]
|
||||
: undefined,
|
||||
});
|
||||
|
||||
export const getLineAnnotationProps = (
|
||||
config: ReferenceLineAnnotationConfig,
|
||||
labels: { markerLabel?: string; markerBodyLabel?: string },
|
||||
axesMap: Record<'left' | 'right', boolean>,
|
||||
paddingMap: Partial<Record<Position, number>>,
|
||||
groupId: 'left' | 'right' | undefined,
|
||||
isHorizontal: boolean
|
||||
) => {
|
||||
// get the position for vertical chart
|
||||
const markerPositionVertical = getBaseIconPlacement(
|
||||
config.iconPosition,
|
||||
axesMap,
|
||||
config.axisMode
|
||||
);
|
||||
// the padding map is built for vertical chart
|
||||
const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE;
|
||||
|
||||
return {
|
||||
groupId,
|
||||
marker: (
|
||||
<Marker
|
||||
config={config}
|
||||
label={labels.markerLabel}
|
||||
isHorizontal={isHorizontal}
|
||||
hasReducedPadding={hasReducedPadding}
|
||||
/>
|
||||
),
|
||||
markerBody: (
|
||||
<MarkerBody
|
||||
label={labels.markerBodyLabel}
|
||||
isHorizontal={
|
||||
(!isHorizontal && config.axisMode === 'bottom') ||
|
||||
(isHorizontal && config.axisMode !== 'bottom')
|
||||
}
|
||||
/>
|
||||
),
|
||||
// rotate the position if required
|
||||
markerPosition: isHorizontal
|
||||
? mapVerticalToHorizontalPlacement(markerPositionVertical)
|
||||
: markerPositionVertical,
|
||||
};
|
||||
};
|
||||
|
||||
export const getGroupId = (axisMode: YAxisMode | undefined) =>
|
||||
axisMode === 'bottom' ? undefined : axisMode === 'right' ? 'right' : 'left';
|
||||
|
||||
export const getBottomRect = (
|
||||
headerLabel: string | undefined,
|
||||
isFillAbove: boolean,
|
||||
formatter: FieldFormat | undefined,
|
||||
currentValue: number,
|
||||
nextValue?: number
|
||||
) => ({
|
||||
coordinates: {
|
||||
x0: isFillAbove ? currentValue : nextValue,
|
||||
y0: undefined,
|
||||
x1: isFillAbove ? nextValue : currentValue,
|
||||
y1: undefined,
|
||||
},
|
||||
header: headerLabel,
|
||||
details: formatter?.convert(currentValue) || currentValue.toString(),
|
||||
});
|
||||
|
||||
export const getHorizontalRect = (
|
||||
headerLabel: string | undefined,
|
||||
isFillAbove: boolean,
|
||||
formatter: FieldFormat | undefined,
|
||||
currentValue: number,
|
||||
nextValue?: number
|
||||
) => ({
|
||||
coordinates: {
|
||||
x0: undefined,
|
||||
y0: isFillAbove ? currentValue : nextValue,
|
||||
x1: undefined,
|
||||
y1: isFillAbove ? nextValue : currentValue,
|
||||
},
|
||||
header: headerLabel,
|
||||
details: formatter?.convert(currentValue) || currentValue.toString(),
|
||||
});
|
|
@ -42,14 +42,24 @@ import {
|
|||
LegendSizeToPixels,
|
||||
} from '@kbn/visualizations-plugin/common/constants';
|
||||
import type { FilterEvent, BrushEvent, FormatFactory } from '../types';
|
||||
import type { CommonXYDataLayerConfig, SeriesType, XYChartProps } from '../../common/types';
|
||||
import type {
|
||||
CommonXYDataLayerConfig,
|
||||
ExtendedYConfig,
|
||||
ReferenceLineYConfig,
|
||||
SeriesType,
|
||||
XYChartProps,
|
||||
} from '../../common/types';
|
||||
import {
|
||||
isHorizontalChart,
|
||||
getAnnotationsLayers,
|
||||
getDataLayers,
|
||||
Series,
|
||||
getFormat,
|
||||
isReferenceLineYConfig,
|
||||
getFormattedTablesByLayers,
|
||||
} from '../helpers';
|
||||
|
||||
import {
|
||||
getFilteredLayers,
|
||||
getReferenceLayers,
|
||||
isDataLayer,
|
||||
|
@ -60,7 +70,7 @@ import {
|
|||
} from '../helpers';
|
||||
import { getXDomain, XyEndzones } from './x_domain';
|
||||
import { getLegendAction } from './legend_action';
|
||||
import { ReferenceLineAnnotations, computeChartMargins } from './reference_lines';
|
||||
import { ReferenceLines, computeChartMargins } from './reference_lines';
|
||||
import { visualizationDefinitions } from '../definitions';
|
||||
import { CommonXYLayerConfig } from '../../common/types';
|
||||
import { SplitChart } from './split_chart';
|
||||
|
@ -270,6 +280,7 @@ export function XYChart({
|
|||
};
|
||||
|
||||
const referenceLineLayers = getReferenceLayers(layers);
|
||||
|
||||
const annotationsLayers = getAnnotationsLayers(layers);
|
||||
const firstTable = dataLayers[0]?.table;
|
||||
|
||||
|
@ -286,7 +297,9 @@ export function XYChart({
|
|||
const rangeAnnotations = getRangeAnnotations(annotationsLayers);
|
||||
|
||||
const visualConfigs = [
|
||||
...referenceLineLayers.flatMap(({ yConfig }) => yConfig),
|
||||
...referenceLineLayers.flatMap<ExtendedYConfig | ReferenceLineYConfig | undefined>(
|
||||
({ yConfig }) => yConfig
|
||||
),
|
||||
...groupedLineAnnotations,
|
||||
].filter(Boolean);
|
||||
|
||||
|
@ -364,9 +377,10 @@ export function XYChart({
|
|||
l.yConfig ? l.yConfig.map((yConfig) => ({ layerId: l.layerId, yConfig })) : []
|
||||
)
|
||||
.filter(({ yConfig }) => yConfig.axisMode === axis.groupId)
|
||||
.map(
|
||||
({ layerId, yConfig }) =>
|
||||
`${layerId}-${yConfig.forAccessor}-${yConfig.fill !== 'none' ? 'rect' : 'line'}`
|
||||
.map(({ layerId, yConfig }) =>
|
||||
isReferenceLineYConfig(yConfig)
|
||||
? `${layerId}-${yConfig.value}-${yConfig.fill !== 'none' ? 'rect' : 'line'}`
|
||||
: `${layerId}-${yConfig.forAccessor}-${yConfig.fill !== 'none' ? 'rect' : 'line'}`
|
||||
),
|
||||
};
|
||||
};
|
||||
|
@ -668,7 +682,7 @@ export function XYChart({
|
|||
/>
|
||||
)}
|
||||
{referenceLineLayers.length ? (
|
||||
<ReferenceLineAnnotations
|
||||
<ReferenceLines
|
||||
layers={referenceLineLayers}
|
||||
formatters={{
|
||||
left: yAxesMap.left?.formatter,
|
||||
|
|
|
@ -12,13 +12,13 @@ import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/com
|
|||
import {
|
||||
CommonXYDataLayerConfig,
|
||||
CommonXYLayerConfig,
|
||||
CommonXYReferenceLineLayerConfig,
|
||||
ReferenceLineLayerConfig,
|
||||
} from '../../common/types';
|
||||
import { isDataLayer, isReferenceLayer } from './visualization';
|
||||
|
||||
export function getFilteredLayers(layers: CommonXYLayerConfig[]) {
|
||||
return layers.filter<CommonXYReferenceLineLayerConfig | CommonXYDataLayerConfig>(
|
||||
(layer): layer is CommonXYReferenceLineLayerConfig | CommonXYDataLayerConfig => {
|
||||
return layers.filter<ReferenceLineLayerConfig | CommonXYDataLayerConfig>(
|
||||
(layer): layer is ReferenceLineLayerConfig | CommonXYDataLayerConfig => {
|
||||
let table: Datatable | undefined;
|
||||
let accessors: Array<ExpressionValueVisDimension | string> = [];
|
||||
let xAccessor: undefined | string | number;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type { CommonXYLayerConfig, SeriesType, ExtendedYConfig, YConfig } from '../../common';
|
||||
import { getDataLayers, isAnnotationsLayer, isDataLayer } from './visualization';
|
||||
import { getDataLayers, isAnnotationsLayer, isDataLayer, isReferenceLine } from './visualization';
|
||||
|
||||
export function isHorizontalSeries(seriesType: SeriesType) {
|
||||
return (
|
||||
|
@ -26,7 +26,11 @@ export function isHorizontalChart(layers: CommonXYLayerConfig[]) {
|
|||
}
|
||||
|
||||
export const getSeriesColor = (layer: CommonXYLayerConfig, accessor: string) => {
|
||||
if ((isDataLayer(layer) && layer.splitAccessor) || isAnnotationsLayer(layer)) {
|
||||
if (
|
||||
(isDataLayer(layer) && layer.splitAccessor) ||
|
||||
isAnnotationsLayer(layer) ||
|
||||
isReferenceLine(layer)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const yConfig: Array<YConfig | ExtendedYConfig> | undefined = layer?.yConfig;
|
||||
|
|
|
@ -6,12 +6,21 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { LayerTypes } from '../../common/constants';
|
||||
import {
|
||||
LayerTypes,
|
||||
REFERENCE_LINE,
|
||||
REFERENCE_LINE_LAYER,
|
||||
REFERENCE_LINE_Y_CONFIG,
|
||||
} from '../../common/constants';
|
||||
import {
|
||||
CommonXYLayerConfig,
|
||||
CommonXYDataLayerConfig,
|
||||
CommonXYReferenceLineLayerConfig,
|
||||
CommonXYAnnotationLayerConfig,
|
||||
ReferenceLineLayerConfig,
|
||||
ReferenceLineConfig,
|
||||
ExtendedYConfigResult,
|
||||
ReferenceLineYConfig,
|
||||
} from '../../common/types';
|
||||
|
||||
export const isDataLayer = (layer: CommonXYLayerConfig): layer is CommonXYDataLayerConfig =>
|
||||
|
@ -20,13 +29,24 @@ export const isDataLayer = (layer: CommonXYLayerConfig): layer is CommonXYDataLa
|
|||
export const getDataLayers = (layers: CommonXYLayerConfig[]) =>
|
||||
(layers || []).filter((layer): layer is CommonXYDataLayerConfig => isDataLayer(layer));
|
||||
|
||||
export const isReferenceLayer = (
|
||||
export const isReferenceLayer = (layer: CommonXYLayerConfig): layer is ReferenceLineLayerConfig =>
|
||||
layer.layerType === LayerTypes.REFERENCELINE && layer.type === REFERENCE_LINE_LAYER;
|
||||
|
||||
export const isReferenceLine = (layer: CommonXYLayerConfig): layer is ReferenceLineConfig =>
|
||||
layer.type === REFERENCE_LINE;
|
||||
|
||||
export const isReferenceLineYConfig = (
|
||||
yConfig: ReferenceLineYConfig | ExtendedYConfigResult
|
||||
): yConfig is ReferenceLineYConfig => yConfig.type === REFERENCE_LINE_Y_CONFIG;
|
||||
|
||||
export const isReferenceLineOrLayer = (
|
||||
layer: CommonXYLayerConfig
|
||||
): layer is CommonXYReferenceLineLayerConfig => layer.layerType === LayerTypes.REFERENCELINE;
|
||||
|
||||
export const getReferenceLayers = (layers: CommonXYLayerConfig[]) =>
|
||||
(layers || []).filter((layer): layer is CommonXYReferenceLineLayerConfig =>
|
||||
isReferenceLayer(layer)
|
||||
(layers || []).filter(
|
||||
(layer): layer is CommonXYReferenceLineLayerConfig =>
|
||||
isReferenceLayer(layer) || isReferenceLine(layer)
|
||||
);
|
||||
|
||||
const isAnnotationLayerCommon = (
|
||||
|
|
|
@ -24,8 +24,8 @@ import {
|
|||
gridlinesConfigFunction,
|
||||
axisExtentConfigFunction,
|
||||
tickLabelsConfigFunction,
|
||||
referenceLineFunction,
|
||||
referenceLineLayerFunction,
|
||||
extendedReferenceLineLayerFunction,
|
||||
annotationLayerFunction,
|
||||
labelsOrientationConfigFunction,
|
||||
axisTitlesVisibilityConfigFunction,
|
||||
|
@ -64,8 +64,8 @@ export class ExpressionXyPlugin {
|
|||
expressions.registerFunction(annotationLayerFunction);
|
||||
expressions.registerFunction(extendedAnnotationLayerFunction);
|
||||
expressions.registerFunction(labelsOrientationConfigFunction);
|
||||
expressions.registerFunction(referenceLineFunction);
|
||||
expressions.registerFunction(referenceLineLayerFunction);
|
||||
expressions.registerFunction(extendedReferenceLineLayerFunction);
|
||||
expressions.registerFunction(axisTitlesVisibilityConfigFunction);
|
||||
expressions.registerFunction(xyVisFunction);
|
||||
expressions.registerFunction(layeredXyVisFunction);
|
||||
|
|
|
@ -19,10 +19,10 @@ import {
|
|||
tickLabelsConfigFunction,
|
||||
annotationLayerFunction,
|
||||
labelsOrientationConfigFunction,
|
||||
referenceLineLayerFunction,
|
||||
referenceLineFunction,
|
||||
axisTitlesVisibilityConfigFunction,
|
||||
extendedDataLayerFunction,
|
||||
extendedReferenceLineLayerFunction,
|
||||
referenceLineLayerFunction,
|
||||
layeredXyVisFunction,
|
||||
extendedAnnotationLayerFunction,
|
||||
} from '../common/expression_functions';
|
||||
|
@ -42,8 +42,8 @@ export class ExpressionXyPlugin
|
|||
expressions.registerFunction(annotationLayerFunction);
|
||||
expressions.registerFunction(extendedAnnotationLayerFunction);
|
||||
expressions.registerFunction(labelsOrientationConfigFunction);
|
||||
expressions.registerFunction(referenceLineFunction);
|
||||
expressions.registerFunction(referenceLineLayerFunction);
|
||||
expressions.registerFunction(extendedReferenceLineLayerFunction);
|
||||
expressions.registerFunction(axisTitlesVisibilityConfigFunction);
|
||||
expressions.registerFunction(xyVisFunction);
|
||||
expressions.registerFunction(layeredXyVisFunction);
|
||||
|
|
|
@ -356,7 +356,7 @@ const referenceLineLayerToExpression = (
|
|||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'extendedReferenceLineLayer',
|
||||
function: 'referenceLineLayer',
|
||||
arguments: {
|
||||
layerId: [layer.layerId],
|
||||
yConfig: layer.yConfig
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue