[XY] Add axes support (#129476)

* Added `axes` support

* Refactoring auto-assignment logic

* Fixed reference line position

* Fixed bug with auto-assignment.

* Fixed snapshots

* Fixed behavior of the horizontal reference lines.

Co-authored-by: Yaroslav Kuznietsov <kuznetsov.yaroslav.yk@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Marta Bondyra <marta.bondyra@gmail.com>
This commit is contained in:
Uladzislau Lasitsa 2022-06-23 07:09:46 -07:00 committed by GitHub
parent ec3e3b27c6
commit 8fa2608172
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 2784 additions and 1737 deletions

View file

@ -58,6 +58,9 @@ export const sampleLayer: DataLayerConfig = {
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
xScaleType: 'ordinal',
isHistogram: false,
isHorizontal: false,
isPercentage: false,
isStacked: false,
palette: mockPaletteOutput,
table: createSampleDatatableWithRows([]),
};
@ -73,6 +76,9 @@ export const sampleExtendedLayer: ExtendedDataLayerConfig = {
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
xScaleType: 'ordinal',
isHistogram: false,
isHorizontal: false,
isStacked: false,
isPercentage: false,
palette: mockPaletteOutput,
table: createSampleDatatableWithRows([]),
};
@ -80,9 +86,6 @@ export const sampleExtendedLayer: ExtendedDataLayerConfig = {
export const createArgsWithLayers = (
layers: DataLayerConfig | DataLayerConfig[] = sampleLayer
): XYProps => ({
xTitle: '',
yTitle: '',
yRightTitle: '',
showTooltip: true,
legend: {
type: 'legendConfig',
@ -91,41 +94,44 @@ export const createArgsWithLayers = (
},
valueLabels: 'hide',
valuesInLegend: false,
axisTitlesVisibilitySettings: {
type: 'axisTitlesVisibilityConfig',
x: true,
yLeft: true,
yRight: true,
},
tickLabelsVisibilitySettings: {
type: 'tickLabelsConfig',
x: true,
yLeft: false,
yRight: false,
},
labelsOrientation: {
type: 'labelsOrientationConfig',
x: 0,
yLeft: -90,
yRight: -45,
},
gridlinesVisibilitySettings: {
type: 'gridlinesConfig',
x: true,
yLeft: false,
yRight: false,
},
yLeftExtent: {
mode: 'full',
type: 'axisExtentConfig',
},
yRightExtent: {
mode: 'full',
type: 'axisExtentConfig',
xAxisConfig: {
type: 'xAxisConfig',
position: 'bottom',
showGridLines: true,
labelsOrientation: 0,
showLabels: true,
showTitle: true,
title: '',
},
yAxisConfigs: [
{
type: 'yAxisConfig',
position: 'right',
showGridLines: false,
labelsOrientation: -45,
showLabels: false,
showTitle: true,
title: '',
extent: {
mode: 'full',
type: 'axisExtentConfig',
},
},
{
type: 'yAxisConfig',
position: 'left',
showGridLines: false,
labelsOrientation: -90,
showLabels: false,
showTitle: true,
title: '',
extent: {
mode: 'full',
type: 'axisExtentConfig',
},
},
],
layers: Array.isArray(layers) ? layers : [layers],
yLeftScale: 'linear',
yRightScale: 'linear',
});
export function sampleArgs() {

View file

@ -8,22 +8,20 @@
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_DECORATION_CONFIG = 'dataDecorationConfig';
export const REFERENCE_LINE_DECORATION_CONFIG = 'referenceLineDecorationConfig';
export const EXTENDED_REFERENCE_LINE_DECORATION_CONFIG = 'extendedReferenceLineDecorationConfig';
export const X_AXIS_CONFIG = 'xAxisConfig';
export const Y_AXIS_CONFIG = 'yAxisConfig';
export const DATA_LAYER = 'dataLayer';
export const EXTENDED_DATA_LAYER = 'extendedDataLayer';
export const LEGEND_CONFIG = 'legendConfig';
export const XY_VIS_RENDERER = 'xyVis';
export const GRID_LINES_CONFIG = 'gridlinesConfig';
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 LABELS_ORIENTATION_CONFIG = 'labelsOrientationConfig';
export const AXIS_TITLES_VISIBILITY_CONFIG = 'axisTitlesVisibilityConfig';
export const LayerTypes = {
DATA: 'data',
@ -82,13 +80,6 @@ export const SeriesTypes = {
BAR: 'bar',
LINE: 'line',
AREA: 'area',
BAR_STACKED: 'bar_stacked',
AREA_STACKED: 'area_stacked',
BAR_HORIZONTAL: 'bar_horizontal',
BAR_PERCENTAGE_STACKED: 'bar_percentage_stacked',
BAR_HORIZONTAL_STACKED: 'bar_horizontal_stacked',
AREA_PERCENTAGE_STACKED: 'area_percentage_stacked',
BAR_HORIZONTAL_PERCENTAGE_STACKED: 'bar_horizontal_percentage_stacked',
} as const;
export const YScaleTypes = {
@ -131,3 +122,10 @@ export const AvailableReferenceLineIcons = {
TAG: 'tag',
TRIANGLE: 'triangle',
} as const;
export const AxisModes = {
NORMAL: 'normal',
PERCENTAGE: 'percentage',
WIGGLE: 'wiggle',
SILHOUETTE: 'silhouette',
} as const;

View file

@ -1,53 +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 { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
import { AXIS_TITLES_VISIBILITY_CONFIG } from '../constants';
import { AxesSettingsConfig, AxisTitlesVisibilityConfigResult } from '../types';
export const axisTitlesVisibilityConfigFunction: ExpressionFunctionDefinition<
typeof AXIS_TITLES_VISIBILITY_CONFIG,
null,
AxesSettingsConfig,
AxisTitlesVisibilityConfigResult
> = {
name: AXIS_TITLES_VISIBILITY_CONFIG,
aliases: [],
type: AXIS_TITLES_VISIBILITY_CONFIG,
help: i18n.translate('expressionXY.axisTitlesVisibilityConfig.help', {
defaultMessage: `Configure the xy chart's axis titles appearance`,
}),
inputTypes: ['null'],
args: {
x: {
types: ['boolean'],
help: i18n.translate('expressionXY.axisTitlesVisibilityConfig.x.help', {
defaultMessage: 'Specifies whether or not the title of the x-axis are visible.',
}),
},
yLeft: {
types: ['boolean'],
help: i18n.translate('expressionXY.axisTitlesVisibilityConfig.yLeft.help', {
defaultMessage: 'Specifies whether or not the title of the left y-axis are visible.',
}),
},
yRight: {
types: ['boolean'],
help: i18n.translate('expressionXY.axisTitlesVisibilityConfig.yRight.help', {
defaultMessage: 'Specifies whether or not the title of the right y-axis are visible.',
}),
},
},
fn(inputn, args) {
return {
type: AXIS_TITLES_VISIBILITY_CONFIG,
...args,
};
},
};

View file

@ -0,0 +1,72 @@
/*
* 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 { strings } from '../i18n';
import { XAxisConfigFn, YAxisConfigFn } from '../types';
import { AXIS_EXTENT_CONFIG } from '../constants';
type CommonAxisConfigFn = XAxisConfigFn | YAxisConfigFn;
export const commonAxisConfigArgs: Omit<
CommonAxisConfigFn['args'],
'scaleType' | 'mode' | 'boundsMargin'
> = {
title: {
types: ['string'],
help: strings.getAxisTitleHelp(),
},
id: {
types: ['string'],
help: strings.getAxisIdHelp(),
},
hide: {
types: ['boolean'],
help: strings.getAxisHideHelp(),
},
labelColor: {
types: ['string'],
help: strings.getAxisLabelColorHelp(),
},
showOverlappingLabels: {
types: ['boolean'],
help: strings.getAxisShowOverlappingLabelsHelp(),
},
showDuplicates: {
types: ['boolean'],
help: strings.getAxisShowDuplicatesHelp(),
},
showGridLines: {
types: ['boolean'],
help: strings.getAxisShowGridLinesHelp(),
default: false,
},
labelsOrientation: {
types: ['number'],
options: [0, -90, -45],
help: strings.getAxisLabelsOrientationHelp(),
},
showLabels: {
types: ['boolean'],
help: strings.getAxisShowLabelsHelp(),
default: true,
},
showTitle: {
types: ['boolean'],
help: strings.getAxisShowTitleHelp(),
default: true,
},
truncate: {
types: ['number'],
help: strings.getAxisTruncateHelp(),
},
extent: {
types: [AXIS_EXTENT_CONFIG],
help: strings.getAxisExtentHelp(),
default: `{${AXIS_EXTENT_CONFIG}}`,
},
};

View file

@ -7,7 +7,7 @@
*/
import { ArgumentType } from '@kbn/expressions-plugin/common';
import { SeriesTypes, XScaleTypes, Y_CONFIG } from '../constants';
import { SeriesTypes, XScaleTypes, DATA_DECORATION_CONFIG } from '../constants';
import { strings } from '../i18n';
import { DataLayerArgs, ExtendedDataLayerArgs } from '../types';
@ -43,6 +43,21 @@ export const commonDataLayerArgs: Omit<
default: false,
help: strings.getIsHistogramHelp(),
},
isPercentage: {
types: ['boolean'],
default: false,
help: strings.getIsPercentageHelp(),
},
isStacked: {
types: ['boolean'],
default: false,
help: strings.getIsStackedHelp(),
},
isHorizontal: {
types: ['boolean'],
default: false,
help: strings.getIsHorizontalHelp(),
},
lineWidth: {
types: ['number'],
help: strings.getLineWidthHelp(),
@ -59,9 +74,9 @@ export const commonDataLayerArgs: Omit<
types: ['boolean'],
help: strings.getShowLinesHelp(),
},
yConfig: {
types: [Y_CONFIG],
help: strings.getYConfigHelp(),
decorations: {
types: [DATA_DECORATION_CONFIG],
help: strings.getDecorationsHelp(),
multi: true,
},
columnToLabel: {

View file

@ -7,17 +7,13 @@
*/
import {
AXIS_EXTENT_CONFIG,
AXIS_TITLES_VISIBILITY_CONFIG,
EndValues,
FittingFunctions,
GRID_LINES_CONFIG,
LABELS_ORIENTATION_CONFIG,
LEGEND_CONFIG,
TICK_LABELS_CONFIG,
ValueLabelModes,
XYCurveTypes,
YScaleTypes,
X_AXIS_CONFIG,
Y_AXIS_CONFIG,
} from '../constants';
import { strings } from '../i18n';
import { LayeredXyVisFn, XyVisFn } from '../types';
@ -25,45 +21,6 @@ import { LayeredXyVisFn, XyVisFn } from '../types';
type CommonXYFn = XyVisFn | LayeredXyVisFn;
export const commonXYArgs: CommonXYFn['args'] = {
xTitle: {
types: ['string'],
help: strings.getXTitleHelp(),
},
yTitle: {
types: ['string'],
help: strings.getYTitleHelp(),
},
yRightTitle: {
types: ['string'],
help: strings.getYRightTitleHelp(),
},
xExtent: {
types: [AXIS_EXTENT_CONFIG],
help: strings.getXExtentHelp(),
default: `{${AXIS_EXTENT_CONFIG}}`,
},
yLeftExtent: {
types: [AXIS_EXTENT_CONFIG],
help: strings.getYLeftExtentHelp(),
default: `{${AXIS_EXTENT_CONFIG}}`,
},
yRightExtent: {
types: [AXIS_EXTENT_CONFIG],
help: strings.getYRightExtentHelp(),
default: `{${AXIS_EXTENT_CONFIG}}`,
},
yLeftScale: {
options: [...Object.values(YScaleTypes)],
help: strings.getYLeftScaleTypeHelp(),
default: YScaleTypes.LINEAR,
strict: true,
},
yRightScale: {
options: [...Object.values(YScaleTypes)],
help: strings.getYRightScaleTypeHelp(),
default: YScaleTypes.LINEAR,
strict: true,
},
legend: {
types: [LEGEND_CONFIG],
help: strings.getLegendHelp(),
@ -93,22 +50,6 @@ export const commonXYArgs: CommonXYFn['args'] = {
strict: true,
default: ValueLabelModes.HIDE,
},
tickLabelsVisibilitySettings: {
types: [TICK_LABELS_CONFIG],
help: strings.getTickLabelsVisibilitySettingsHelp(),
},
labelsOrientation: {
types: [LABELS_ORIENTATION_CONFIG],
help: strings.getLabelsOrientationHelp(),
},
gridlinesVisibilitySettings: {
types: [GRID_LINES_CONFIG],
help: strings.getGridlinesVisibilitySettingsHelp(),
},
axisTitlesVisibilitySettings: {
types: [AXIS_TITLES_VISIBILITY_CONFIG],
help: strings.getAxisTitlesVisibilitySettingsHelp(),
},
curveType: {
types: ['string'],
options: [...Object.values(XYCurveTypes)],
@ -133,6 +74,15 @@ export const commonXYArgs: CommonXYFn['args'] = {
types: ['string'],
help: strings.getAriaLabelHelp(),
},
xAxisConfig: {
types: [X_AXIS_CONFIG],
help: strings.getXAxisConfigHelp(),
},
yAxisConfigs: {
types: [Y_AXIS_CONFIG],
help: strings.getyAxisConfigsHelp(),
multi: true,
},
detailedTooltip: {
types: ['boolean'],
help: strings.getDetailedTooltipHelp(),

View file

@ -6,22 +6,19 @@
* Side Public License, v 1.
*/
import { YAxisModes } from '../constants';
import { strings } from '../i18n';
import { YConfigFn, ExtendedYConfigFn } from '../types';
import { DataDecorationConfigFn, ReferenceLineDecorationConfigFn } from '../types';
type CommonYConfigFn = YConfigFn | ExtendedYConfigFn;
type CommonDecorationConfigFn = DataDecorationConfigFn | ReferenceLineDecorationConfigFn;
export const commonYConfigArgs: CommonYConfigFn['args'] = {
export const commonDecorationConfigArgs: CommonDecorationConfigFn['args'] = {
forAccessor: {
types: ['string'],
help: strings.getForAccessorHelp(),
},
axisMode: {
axisId: {
types: ['string'],
options: [...Object.values(YAxisModes)],
help: strings.getAxisModeHelp(),
strict: true,
help: strings.getAxisIdHelp(),
},
color: {
types: ['string'],

View file

@ -0,0 +1,37 @@
/*
* 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 type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
import { DATA_DECORATION_CONFIG } from '../constants';
import { DataDecorationConfig, DataDecorationConfigResult } from '../types';
import { commonDecorationConfigArgs } from './common_y_config_args';
export const dataDecorationConfigFunction: ExpressionFunctionDefinition<
typeof DATA_DECORATION_CONFIG,
null,
DataDecorationConfig,
DataDecorationConfigResult
> = {
name: DATA_DECORATION_CONFIG,
aliases: [],
type: DATA_DECORATION_CONFIG,
help: i18n.translate('expressionXY.dataDecorationConfig.help', {
defaultMessage: `Configure the decoration of data`,
}),
inputTypes: ['null'],
args: {
...commonDecorationConfigArgs,
},
fn(input, args) {
return {
type: DATA_DECORATION_CONFIG,
...args,
};
},
};

View file

@ -20,6 +20,9 @@ describe('extendedDataLayerConfig', () => {
splitAccessor: 'd',
xScaleType: 'linear',
isHistogram: false,
isHorizontal: false,
isPercentage: false,
isStacked: false,
palette: mockPaletteOutput,
};

View file

@ -1,20 +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 { AxesSettingsConfig } from '../types';
import { gridlinesConfigFunction } from '.';
import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks';
describe('gridlinesConfig', () => {
test('produces the correct arguments', () => {
const args: AxesSettingsConfig = { x: true, yLeft: false, yRight: false };
const result = gridlinesConfigFunction.fn(null, args, createMockExecutionContext());
expect(result).toEqual({ type: 'gridlinesConfig', ...args });
});
});

View file

@ -1,53 +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 { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
import { GRID_LINES_CONFIG } from '../constants';
import { AxesSettingsConfig, GridlinesConfigResult } from '../types';
export const gridlinesConfigFunction: ExpressionFunctionDefinition<
typeof GRID_LINES_CONFIG,
null,
AxesSettingsConfig,
GridlinesConfigResult
> = {
name: GRID_LINES_CONFIG,
aliases: [],
type: GRID_LINES_CONFIG,
help: i18n.translate('expressionXY.gridlinesConfig.help', {
defaultMessage: `Configure the xy chart's gridlines appearance`,
}),
inputTypes: ['null'],
args: {
x: {
types: ['boolean'],
help: i18n.translate('expressionXY.gridlinesConfig.x.help', {
defaultMessage: 'Specifies whether or not the gridlines of the x-axis are visible.',
}),
},
yLeft: {
types: ['boolean'],
help: i18n.translate('expressionXY.gridlinesConfig.yLeft.help', {
defaultMessage: 'Specifies whether or not the gridlines of the left y-axis are visible.',
}),
},
yRight: {
types: ['boolean'],
help: i18n.translate('expressionXY.gridlinesConfig.yRight.help', {
defaultMessage: 'Specifies whether or not the gridlines of the right y-axis are visible.',
}),
},
},
fn(input, args) {
return {
type: GRID_LINES_CONFIG,
...args,
};
},
};

View file

@ -11,13 +11,11 @@ export * from './layered_xy_vis';
export * from './legend_config';
export * from './annotation_layer';
export * from './extended_annotation_layer';
export * from './data_decoration_config';
export * from './y_axis_config';
export * from './extended_y_axis_config';
export * from './x_axis_config';
export * from './reference_line_decoration_config';
export * from './extended_data_layer';
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 './axis_titles_visibility_config';

View file

@ -1,20 +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 { LabelsOrientationConfig } from '../types';
import { labelsOrientationConfigFunction } from '.';
import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks';
describe('labelsOrientationConfig', () => {
test('produces the correct arguments', () => {
const args: LabelsOrientationConfig = { x: 0, yLeft: -90, yRight: -45 };
const result = labelsOrientationConfigFunction.fn(null, args, createMockExecutionContext());
expect(result).toEqual({ type: 'labelsOrientationConfig', ...args });
});
});

View file

@ -1,56 +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 { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
import { LABELS_ORIENTATION_CONFIG } from '../constants';
import { LabelsOrientationConfig, LabelsOrientationConfigResult } from '../types';
export const labelsOrientationConfigFunction: ExpressionFunctionDefinition<
typeof LABELS_ORIENTATION_CONFIG,
null,
LabelsOrientationConfig,
LabelsOrientationConfigResult
> = {
name: LABELS_ORIENTATION_CONFIG,
aliases: [],
type: LABELS_ORIENTATION_CONFIG,
help: i18n.translate('expressionXY.labelsOrientationConfig.help', {
defaultMessage: `Configure the xy chart's tick labels orientation`,
}),
inputTypes: ['null'],
args: {
x: {
types: ['number'],
options: [0, -90, -45],
help: i18n.translate('expressionXY.labelsOrientationConfig.x.help', {
defaultMessage: 'Specifies the labels orientation of the x-axis.',
}),
},
yLeft: {
types: ['number'],
options: [0, -90, -45],
help: i18n.translate('expressionXY.labelsOrientationConfig.yLeft.help', {
defaultMessage: 'Specifies the labels orientation of the left y-axis.',
}),
},
yRight: {
types: ['number'],
options: [0, -90, -45],
help: i18n.translate('expressionXY.labelsOrientationConfig.yRight.help', {
defaultMessage: 'Specifies the labels orientation of the right y-axis.',
}),
},
},
fn(input, args) {
return {
type: LABELS_ORIENTATION_CONFIG,
...args,
};
},
};

View file

@ -15,6 +15,7 @@ import {
validateMinTimeBarInterval,
hasBarLayer,
errors,
validateAxes,
} from './validate';
import { appendLayerIds, getDataLayers } from '../helpers';
@ -35,6 +36,8 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers)
throw new Error(errors.markSizeRatioWithoutAccessor());
}
validateAxes(dataLayers, args.yAxisConfigs);
return {
type: 'render',
as: XY_VIS_RENDERER,

View file

@ -15,6 +15,7 @@ describe('referenceLine', () => {
const args: ReferenceLineArgs = {
value: 100,
fill: 'above',
position: 'bottom',
};
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
@ -23,9 +24,9 @@ describe('referenceLine', () => {
type: 'referenceLine',
layerType: 'referenceLine',
lineLength: 0,
yConfig: [
decorations: [
{
type: 'referenceLineYConfig',
type: 'extendedReferenceLineDecorationConfig',
...args,
textVisibility: false,
},
@ -40,7 +41,7 @@ describe('referenceLine', () => {
value: 100,
icon: 'alert',
iconPosition: 'below',
axisMode: 'bottom',
position: 'bottom',
lineStyle: 'solid',
lineWidth: 10,
color: '#fff',
@ -54,9 +55,9 @@ describe('referenceLine', () => {
type: 'referenceLine',
layerType: 'referenceLine',
lineLength: 0,
yConfig: [
decorations: [
{
type: 'referenceLineYConfig',
type: 'extendedReferenceLineDecorationConfig',
...args,
},
],
@ -69,6 +70,7 @@ describe('referenceLine', () => {
name: 'some name',
value: 100,
fill: 'none',
position: 'bottom',
};
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
@ -77,9 +79,9 @@ describe('referenceLine', () => {
type: 'referenceLine',
layerType: 'referenceLine',
lineLength: 0,
yConfig: [
decorations: [
{
type: 'referenceLineYConfig',
type: 'extendedReferenceLineDecorationConfig',
...args,
textVisibility: true,
},
@ -93,6 +95,7 @@ describe('referenceLine', () => {
value: 100,
textVisibility: true,
fill: 'none',
position: 'bottom',
};
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
@ -101,9 +104,9 @@ describe('referenceLine', () => {
type: 'referenceLine',
layerType: 'referenceLine',
lineLength: 0,
yConfig: [
decorations: [
{
type: 'referenceLineYConfig',
type: 'extendedReferenceLineDecorationConfig',
...args,
textVisibility: false,
},
@ -119,6 +122,7 @@ describe('referenceLine', () => {
name: 'some text',
textVisibility,
fill: 'none',
position: 'bottom',
};
const result = referenceLineFunction.fn(null, args, createMockExecutionContext());
@ -127,9 +131,9 @@ describe('referenceLine', () => {
type: 'referenceLine',
layerType: 'referenceLine',
lineLength: 0,
yConfig: [
decorations: [
{
type: 'referenceLineYConfig',
type: 'extendedReferenceLineDecorationConfig',
...args,
textVisibility,
},

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { Position } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import {
AvailableReferenceLineIcons,
@ -14,8 +15,7 @@ import {
LayerTypes,
LineStyles,
REFERENCE_LINE,
REFERENCE_LINE_Y_CONFIG,
YAxisModes,
EXTENDED_REFERENCE_LINE_DECORATION_CONFIG,
} from '../constants';
import { ReferenceLineFn } from '../types';
import { strings } from '../i18n';
@ -36,13 +36,23 @@ export const referenceLineFunction: ReferenceLineFn = {
help: strings.getReferenceLineValueHelp(),
required: true,
},
axisMode: {
position: {
types: ['string'],
options: [...Object.values(YAxisModes)],
help: strings.getAxisModeHelp(),
default: YAxisModes.AUTO,
options: [Position.Right, Position.Left],
help: i18n.translate('expressionXY.referenceLine.position.help', {
defaultMessage:
'Position of axis (first axis of that position) to which the reference line belongs.',
}),
default: Position.Left,
strict: true,
},
axisId: {
types: ['string'],
help: i18n.translate('expressionXY.referenceLine.axisId.help', {
defaultMessage:
'Id of axis to which the reference line belongs. It has higher priority than "position"',
}),
},
color: {
types: ['string'],
help: strings.getColorHelp(),
@ -50,7 +60,7 @@ export const referenceLineFunction: ReferenceLineFn = {
lineStyle: {
types: ['string'],
options: [...Object.values(LineStyles)],
help: i18n.translate('expressionXY.yConfig.lineStyle.help', {
help: i18n.translate('expressionXY.decorationConfig.lineStyle.help', {
defaultMessage: 'The style of the reference line',
}),
default: LineStyles.SOLID,
@ -58,14 +68,14 @@ export const referenceLineFunction: ReferenceLineFn = {
},
lineWidth: {
types: ['number'],
help: i18n.translate('expressionXY.yConfig.lineWidth.help', {
help: i18n.translate('expressionXY.decorationConfig.lineWidth.help', {
defaultMessage: 'The width of the reference line',
}),
default: 1,
},
icon: {
types: ['string'],
help: i18n.translate('expressionXY.yConfig.icon.help', {
help: i18n.translate('expressionXY.decorationConfig.icon.help', {
defaultMessage: 'An optional icon used for reference lines',
}),
options: [...Object.values(AvailableReferenceLineIcons)],
@ -74,7 +84,7 @@ export const referenceLineFunction: ReferenceLineFn = {
iconPosition: {
types: ['string'],
options: [...Object.values(IconPositions)],
help: i18n.translate('expressionXY.yConfig.iconPosition.help', {
help: i18n.translate('expressionXY.decorationConfig.iconPosition.help', {
defaultMessage: 'The placement of the icon for the reference line',
}),
default: IconPositions.AUTO,
@ -82,14 +92,14 @@ export const referenceLineFunction: ReferenceLineFn = {
},
textVisibility: {
types: ['boolean'],
help: i18n.translate('expressionXY.yConfig.textVisibility.help', {
help: i18n.translate('expressionXY.decorationConfig.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', {
help: i18n.translate('expressionXY.decorationConfig.fill.help', {
defaultMessage: 'Fill',
}),
default: FillStyles.NONE,
@ -108,7 +118,7 @@ export const referenceLineFunction: ReferenceLineFn = {
type: REFERENCE_LINE,
layerType: LayerTypes.REFERENCELINE,
lineLength: table?.rows.length ?? 0,
yConfig: [{ ...args, textVisibility, type: REFERENCE_LINE_Y_CONFIG }],
decorations: [{ ...args, textVisibility, type: EXTENDED_REFERENCE_LINE_DECORATION_CONFIG }],
};
},
};

View file

@ -6,43 +6,54 @@
* Side Public License, v 1.
*/
import { Position } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import {
AvailableReferenceLineIcons,
EXTENDED_Y_CONFIG,
REFERENCE_LINE_DECORATION_CONFIG,
FillStyles,
IconPositions,
LineStyles,
} from '../constants';
import { strings } from '../i18n';
import { ExtendedYConfigFn } from '../types';
import { commonYConfigArgs } from './common_y_config_args';
import { ReferenceLineDecorationConfigFn } from '../types';
import { commonDecorationConfigArgs } from './common_y_config_args';
export const extendedYAxisConfigFunction: ExtendedYConfigFn = {
name: EXTENDED_Y_CONFIG,
export const referenceLineDecorationConfigFunction: ReferenceLineDecorationConfigFn = {
name: REFERENCE_LINE_DECORATION_CONFIG,
aliases: [],
type: EXTENDED_Y_CONFIG,
help: strings.getYConfigFnHelp(),
type: REFERENCE_LINE_DECORATION_CONFIG,
help: strings.getDecorationsHelp(),
inputTypes: ['null'],
args: {
...commonYConfigArgs,
...commonDecorationConfigArgs,
position: {
types: ['string'],
options: [Position.Right, Position.Left, Position.Bottom],
help: i18n.translate('expressionXY.referenceLine.position.help', {
defaultMessage:
'Position of axis (first axis of that position) to which the reference line belongs.',
}),
default: Position.Left,
strict: true,
},
lineStyle: {
types: ['string'],
options: [...Object.values(LineStyles)],
help: i18n.translate('expressionXY.yConfig.lineStyle.help', {
help: i18n.translate('expressionXY.decorationConfig.lineStyle.help', {
defaultMessage: 'The style of the reference line',
}),
strict: true,
},
lineWidth: {
types: ['number'],
help: i18n.translate('expressionXY.yConfig.lineWidth.help', {
help: i18n.translate('expressionXY.decorationConfig.lineWidth.help', {
defaultMessage: 'The width of the reference line',
}),
},
icon: {
types: ['string'],
help: i18n.translate('expressionXY.yConfig.icon.help', {
help: i18n.translate('expressionXY.decorationConfig.icon.help', {
defaultMessage: 'An optional icon used for reference lines',
}),
options: [...Object.values(AvailableReferenceLineIcons)],
@ -51,21 +62,21 @@ export const extendedYAxisConfigFunction: ExtendedYConfigFn = {
iconPosition: {
types: ['string'],
options: [...Object.values(IconPositions)],
help: i18n.translate('expressionXY.yConfig.iconPosition.help', {
help: i18n.translate('expressionXY.decorationConfig.iconPosition.help', {
defaultMessage: 'The placement of the icon for the reference line',
}),
strict: true,
},
textVisibility: {
types: ['boolean'],
help: i18n.translate('expressionXY.yConfig.textVisibility.help', {
help: i18n.translate('expressionXY.decorationConfig.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', {
help: i18n.translate('expressionXY.decorationConfig.fill.help', {
defaultMessage: 'Fill',
}),
strict: true,
@ -73,7 +84,7 @@ export const extendedYAxisConfigFunction: ExtendedYConfigFn = {
},
fn(input, args) {
return {
type: EXTENDED_Y_CONFIG,
type: REFERENCE_LINE_DECORATION_CONFIG,
...args,
};
},

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { REFERENCE_LINE_LAYER, EXTENDED_Y_CONFIG } from '../constants';
import { REFERENCE_LINE_LAYER, REFERENCE_LINE_DECORATION_CONFIG } from '../constants';
import { ReferenceLineLayerFn } from '../types';
import { strings } from '../i18n';
@ -22,9 +22,9 @@ export const referenceLineLayerFunction: ReferenceLineLayerFn = {
help: strings.getRLAccessorsHelp(),
multi: true,
},
yConfig: {
types: [EXTENDED_Y_CONFIG],
help: strings.getRLYConfigHelp(),
decorations: {
types: [REFERENCE_LINE_DECORATION_CONFIG],
help: strings.getRLDecorationConfigHelp(),
multi: true,
},
columnToLabel: {

View file

@ -1,20 +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 { AxesSettingsConfig } from '../types';
import { tickLabelsConfigFunction } from '.';
import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks';
describe('tickLabelsConfig', () => {
test('produces the correct arguments', () => {
const args: AxesSettingsConfig = { x: true, yLeft: false, yRight: false };
const result = tickLabelsConfigFunction.fn(null, args, createMockExecutionContext());
expect(result).toEqual({ type: 'tickLabelsConfig', ...args });
});
});

View file

@ -1,53 +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 { i18n } from '@kbn/i18n';
import type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
import { TICK_LABELS_CONFIG } from '../constants';
import { AxesSettingsConfig, TickLabelsConfigResult } from '../types';
export const tickLabelsConfigFunction: ExpressionFunctionDefinition<
typeof TICK_LABELS_CONFIG,
null,
AxesSettingsConfig,
TickLabelsConfigResult
> = {
name: TICK_LABELS_CONFIG,
aliases: [],
type: TICK_LABELS_CONFIG,
help: i18n.translate('expressionXY.tickLabelsConfig.help', {
defaultMessage: `Configure the xy chart's tick labels appearance`,
}),
inputTypes: ['null'],
args: {
x: {
types: ['boolean'],
help: i18n.translate('expressionXY.tickLabelsConfig.x.help', {
defaultMessage: 'Specifies whether or not the tick labels of the x-axis are visible.',
}),
},
yLeft: {
types: ['boolean'],
help: i18n.translate('expressionXY.tickLabelsConfig.yLeft.help', {
defaultMessage: 'Specifies whether or not the tick labels of the left y-axis are visible.',
}),
},
yRight: {
types: ['boolean'],
help: i18n.translate('expressionXY.tickLabelsConfig.yRight.help', {
defaultMessage: 'Specifies whether or not the tick labels of the right y-axis are visible.',
}),
},
},
fn(input, args) {
return {
type: TICK_LABELS_CONFIG,
...args,
};
},
};

View file

@ -9,7 +9,7 @@
import { i18n } from '@kbn/i18n';
import { isValidInterval } from '@kbn/data-plugin/common';
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
import { AxisExtentModes, ValueLabelModes } from '../constants';
import { AxisExtentModes, ValueLabelModes, SeriesTypes } from '../constants';
import {
SeriesType,
AxisExtentConfigResult,
@ -17,7 +17,9 @@ import {
CommonXYDataLayerConfigResult,
ValueLabelMode,
CommonXYDataLayerConfig,
YAxisConfigResult,
ExtendedDataLayerConfigResult,
XAxisConfigResult,
} from '../types';
import { isTimeChart } from '../helpers';
@ -111,19 +113,23 @@ export const errors = {
defaultMessage: '`minTimeBarInterval` argument is applicable only for time bar charts.',
}
),
axisIsNotAssignedError: (axisId: string) =>
i18n.translate('expressionXY.reusable.function.xyVis.errors.axisIsNotAssignedError', {
defaultMessage:
'Axis with id: "{axisId}" is not assigned to any accessor. Please assign axis using the following construction: `decorations=\\{dataDecorationConfig forAccessor="your-accessor" axisId="{axisId}"\\}`',
values: { axisId },
}),
};
export const hasBarLayer = (layers: Array<DataLayerConfigResult | CommonXYDataLayerConfig>) =>
layers.filter(({ seriesType }) => seriesType.includes('bar')).length > 0;
layers.some(({ seriesType }) => seriesType === SeriesTypes.BAR);
export const hasAreaLayer = (layers: Array<DataLayerConfigResult | CommonXYDataLayerConfig>) =>
layers.filter(({ seriesType }) => seriesType.includes('area')).length > 0;
layers.some(({ seriesType }) => seriesType === SeriesTypes.AREA);
export const hasHistogramBarLayer = (
layers: Array<DataLayerConfigResult | CommonXYDataLayerConfig>
) =>
layers.filter(({ seriesType, isHistogram }) => seriesType.includes('bar') && isHistogram).length >
0;
) => layers.some(({ seriesType, isHistogram }) => seriesType === SeriesTypes.BAR && isHistogram);
export const isValidExtentWithCustomMode = (extent: AxisExtentConfigResult) => {
const isValidLowerBound =
@ -138,8 +144,8 @@ export const validateExtentForDataBounds = (
extent: AxisExtentConfigResult,
layers: Array<DataLayerConfigResult | CommonXYDataLayerConfig>
) => {
const lineSeries = layers.filter(({ seriesType }) => seriesType.includes('line'));
if (!lineSeries.length && extent.mode === AxisExtentModes.DATA_BOUNDS) {
const hasLineSeries = layers.some(({ seriesType }) => seriesType === SeriesTypes.LINE);
if (!hasLineSeries && extent.mode === AxisExtentModes.DATA_BOUNDS) {
throw new Error(errors.dataBoundsForNotLineChartError());
}
};
@ -158,20 +164,46 @@ export const validateXExtent = (
}
};
export const validateExtent = (
extent: AxisExtentConfigResult,
export const validateExtents = (
dataLayers: Array<DataLayerConfigResult | CommonXYDataLayerConfig>,
hasBarOrArea: boolean,
dataLayers: Array<DataLayerConfigResult | CommonXYDataLayerConfig>
yAxisConfigs?: YAxisConfigResult[],
xAxisConfig?: XAxisConfigResult
) => {
if (
extent.mode === AxisExtentModes.CUSTOM &&
hasBarOrArea &&
!isValidExtentWithCustomMode(extent)
) {
throw new Error(errors.extendBoundsAreInvalidError());
}
yAxisConfigs?.forEach((axis) => {
if (!axis.extent) {
return;
}
if (
hasBarOrArea &&
axis.extent?.mode === AxisExtentModes.CUSTOM &&
!isValidExtentWithCustomMode(axis.extent)
) {
throw new Error(errors.extendBoundsAreInvalidError());
}
validateExtentForDataBounds(extent, dataLayers);
validateExtentForDataBounds(axis.extent, dataLayers);
});
validateXExtent(xAxisConfig?.extent, dataLayers);
};
export const validateAxes = (
dataLayers: Array<DataLayerConfigResult | CommonXYDataLayerConfig>,
yAxisConfigs?: YAxisConfigResult[]
) => {
yAxisConfigs?.forEach((axis) => {
if (
axis.id &&
dataLayers.every(
(layer) =>
!layer.decorations ||
layer.decorations?.every((decorationConfig) => decorationConfig.axisId !== axis.id)
)
) {
throw new Error(errors.axisIsNotAssignedError(axis.id));
}
});
};
export const validateFillOpacity = (fillOpacity: number | undefined, hasArea: boolean) => {
@ -191,7 +223,7 @@ export const validateValueLabels = (
};
const isAreaOrLineChart = (seriesType: SeriesType) =>
seriesType.includes('line') || seriesType.includes('area');
seriesType === SeriesTypes.LINE || seriesType === SeriesTypes.AREA;
export const validateAddTimeMarker = (
dataLayers: Array<DataLayerConfigResult | ExtendedDataLayerConfigResult>,
@ -206,7 +238,7 @@ export const validateMarkSizeForChartType = (
markSizeAccessor: ExpressionValueVisDimension | string | undefined,
seriesType: SeriesType
) => {
if (markSizeAccessor && !seriesType.includes('line') && !seriesType.includes('area')) {
if (markSizeAccessor && !isAreaOrLineChart(seriesType)) {
throw new Error(errors.markSizeAccessorForNonLineOrAreaChartsError());
}
};
@ -248,7 +280,7 @@ export const validateLinesVisibilityForChartType = (
showLines: boolean | undefined,
seriesType: SeriesType
) => {
if (showLines && !(seriesType.includes('line') || seriesType.includes('area'))) {
if (showLines && !isAreaOrLineChart(seriesType)) {
throw new Error(errors.linesVisibilityForNonLineChartError());
}
};

View file

@ -0,0 +1,37 @@
/*
* 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 { Position } from '@elastic/charts';
import { strings } from '../i18n';
import { XAxisConfigFn } from '../types';
import { X_AXIS_CONFIG } from '../constants';
import { commonAxisConfigArgs } from './common_axis_args';
export const xAxisConfigFunction: XAxisConfigFn = {
name: X_AXIS_CONFIG,
aliases: [],
type: X_AXIS_CONFIG,
help: strings.getXAxisConfigFnHelp(),
inputTypes: ['null'],
args: {
...commonAxisConfigArgs,
position: {
types: ['string'],
options: [Position.Top, Position.Bottom],
help: strings.getAxisPositionHelp(),
strict: true,
},
},
fn(input, args) {
return {
type: X_AXIS_CONFIG,
...args,
position: args.position ?? Position.Bottom,
};
},
};

View file

@ -233,7 +233,10 @@ describe('xyVis', () => {
annotationLayers: [],
isHistogram: true,
xScaleType: 'time',
xExtent: { type: 'axisExtentConfig', mode: 'dataBounds' },
xAxisConfig: {
type: 'xAxisConfig',
extent: { type: 'axisExtentConfig', mode: 'dataBounds' },
},
},
createMockExecutionContext()
)
@ -255,11 +258,14 @@ describe('xyVis', () => {
...restLayerArgs,
referenceLines: [],
annotationLayers: [],
xExtent: {
type: 'axisExtentConfig',
mode: 'full',
lowerBound: undefined,
upperBound: undefined,
xAxisConfig: {
type: 'xAxisConfig',
extent: {
type: 'axisExtentConfig',
mode: 'full',
lowerBound: undefined,
upperBound: undefined,
},
},
},
createMockExecutionContext()
@ -282,9 +288,9 @@ describe('xyVis', () => {
...restLayerArgs,
referenceLines: [],
annotationLayers: [],
xExtent: {
type: 'axisExtentConfig',
mode: 'dataBounds',
xAxisConfig: {
type: 'xAxisConfig',
extent: { type: 'axisExtentConfig', mode: 'dataBounds' },
},
},
createMockExecutionContext()
@ -292,7 +298,7 @@ describe('xyVis', () => {
).rejects.toThrowErrorMatchingSnapshot();
});
test('it renders with custom xExtent for a numeric histogram', async () => {
test('it renders with custom x-axis extent for a numeric histogram', async () => {
const { data, args } = sampleArgs();
const { layers, ...rest } = args;
const { layerId, layerType, table, type, ...restLayerArgs } = sampleLayer;
@ -304,11 +310,14 @@ describe('xyVis', () => {
referenceLines: [],
annotationLayers: [],
isHistogram: true,
xExtent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 0,
upperBound: 10,
xAxisConfig: {
type: 'xAxisConfig',
extent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 0,
upperBound: 10,
},
},
},
createMockExecutionContext()
@ -320,11 +329,14 @@ describe('xyVis', () => {
value: {
args: {
...rest,
xExtent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 0,
upperBound: 10,
xAxisConfig: {
type: 'xAxisConfig',
extent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 0,
upperBound: 10,
},
},
layers: [
{

View file

@ -21,7 +21,7 @@ import {
hasAreaLayer,
hasBarLayer,
hasHistogramBarLayer,
validateExtent,
validateExtents,
validateFillOpacity,
validateMarkSizeRatioLimits,
validateValueLabels,
@ -33,7 +33,7 @@ import {
validateLineWidthForChartType,
validatePointsRadiusForChartType,
validateLinesVisibilityForChartType,
validateXExtent,
validateAxes,
} from './validate';
const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult => {
@ -46,8 +46,11 @@ const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult
columnToLabel: args.columnToLabel,
xScaleType: args.xScaleType,
isHistogram: args.isHistogram,
isPercentage: args.isPercentage,
isHorizontal: args.isHorizontal,
isStacked: args.isStacked,
palette: args.palette,
yConfig: args.yConfig,
decorations: args.decorations,
showPoints: args.showPoints,
pointsRadius: args.pointsRadius,
lineWidth: args.lineWidth,
@ -74,7 +77,10 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
columnToLabel,
xScaleType,
isHistogram,
yConfig,
isHorizontal,
isPercentage,
isStacked,
decorations,
palette,
markSizeAccessor,
showPoints,
@ -121,9 +127,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
const hasBar = hasBarLayer(dataLayers);
const hasArea = hasAreaLayer(dataLayers);
validateXExtent(args.xExtent, dataLayers);
validateExtent(args.yLeftExtent, hasBar || hasArea, dataLayers);
validateExtent(args.yRightExtent, hasBar || hasArea, dataLayers);
validateExtents(dataLayers, hasBar || hasArea, args.yAxisConfigs, args.xAxisConfig);
validateFillOpacity(args.fillOpacity, hasArea);
validateAddTimeMarker(dataLayers, args.addTimeMarker);
validateMinTimeBarInterval(dataLayers, hasBar, args.minTimeBarInterval);
@ -136,6 +140,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
validateLineWidthForChartType(lineWidth, args.seriesType);
validateShowPointsForChartType(showPoints, args.seriesType);
validatePointsRadiusForChartType(pointsRadius, args.seriesType);
validateAxes(dataLayers, args.yAxisConfigs);
return {
type: 'render',

View file

@ -6,22 +6,46 @@
* Side Public License, v 1.
*/
import { Y_CONFIG } from '../constants';
import { YConfigFn } from '../types';
import { Position } from '@elastic/charts';
import { strings } from '../i18n';
import { commonYConfigArgs } from './common_y_config_args';
import { Y_AXIS_CONFIG, AxisModes, YScaleTypes } from '../constants';
import { YAxisConfigFn } from '../types';
import { commonAxisConfigArgs } from './common_axis_args';
export const yAxisConfigFunction: YConfigFn = {
name: Y_CONFIG,
export const yAxisConfigFunction: YAxisConfigFn = {
name: Y_AXIS_CONFIG,
aliases: [],
type: Y_CONFIG,
help: strings.getYConfigFnHelp(),
type: Y_AXIS_CONFIG,
help: strings.getYAxisConfigFnHelp(),
inputTypes: ['null'],
args: { ...commonYConfigArgs },
args: {
...commonAxisConfigArgs,
mode: {
types: ['string'],
options: [...Object.values(AxisModes)],
help: strings.getAxisModeHelp(),
},
boundsMargin: {
types: ['number'],
help: strings.getAxisBoundsMarginHelp(),
},
scaleType: {
options: [...Object.values(YScaleTypes)],
help: strings.getAxisScaleTypeHelp(),
default: YScaleTypes.LINEAR,
},
position: {
types: ['string'],
options: [Position.Right, Position.Left],
help: strings.getAxisPositionHelp(),
strict: true,
},
},
fn(input, args) {
return {
type: Y_CONFIG,
type: Y_AXIS_CONFIG,
...args,
position: args.position ?? Position.Left,
};
},
};

View file

@ -59,6 +59,9 @@ describe('#getDataLayers', () => {
seriesType: 'bar',
xScaleType: 'time',
isHistogram: false,
isHorizontal: false,
isPercentage: false,
isStacked: false,
table: { rows: [], columns: [], type: 'datatable' },
palette: { type: 'system_palette', name: 'system' },
},

View file

@ -14,7 +14,7 @@ import {
ExtendedDataLayerArgs,
DataLayerArgs,
} from '../types';
import { LayerTypes } from '../constants';
import { LayerTypes, SeriesTypes } from '../constants';
function isWithLayerId<T>(layer: T): layer is T & WithLayerId {
return (layer as T & WithLayerId).layerId ? true : false;
@ -35,9 +35,7 @@ export function appendLayerIds<T>(
}
export const getShowLines = (args: DataLayerArgs | ExtendedDataLayerArgs) =>
args.seriesType.includes('line') || args.seriesType.includes('area')
? args.showLines ?? true
: args.showLines;
args.showLines ?? (args.seriesType === SeriesTypes.LINE || args.seriesType !== SeriesTypes.AREA);
export function getDataLayers(layers: XYExtendedLayerConfigResult[]) {
return layers.filter<ExtendedDataLayerConfig>(

View file

@ -20,6 +20,9 @@ describe('#isTimeChart', () => {
seriesType: 'bar',
xScaleType: 'time',
isHistogram: false,
isHorizontal: false,
isPercentage: false,
isStacked: false,
table: {
rows: [],
columns: [

View file

@ -29,38 +29,6 @@ export const strings = {
i18n.translate('expressionXY.xyVis.logDatatable.breakDown', {
defaultMessage: 'Break down by',
}),
getXTitleHelp: () =>
i18n.translate('expressionXY.xyVis.xTitle.help', {
defaultMessage: 'X axis title',
}),
getYTitleHelp: () =>
i18n.translate('expressionXY.xyVis.yLeftTitle.help', {
defaultMessage: 'Y left axis title',
}),
getYRightTitleHelp: () =>
i18n.translate('expressionXY.xyVis.yRightTitle.help', {
defaultMessage: 'Y right axis title',
}),
getXExtentHelp: () =>
i18n.translate('expressionXY.xyVis.xExtent.help', {
defaultMessage: 'X axis extents',
}),
getYLeftExtentHelp: () =>
i18n.translate('expressionXY.xyVis.yLeftExtent.help', {
defaultMessage: 'Y left axis extents',
}),
getYRightExtentHelp: () =>
i18n.translate('expressionXY.xyVis.yRightExtent.help', {
defaultMessage: 'Y right axis extents',
}),
getYLeftScaleTypeHelp: () =>
i18n.translate('expressionXY.xyVis.yLeftScaleType.help', {
defaultMessage: 'The scale type of the left y axis',
}),
getYRightScaleTypeHelp: () =>
i18n.translate('expressionXY.xyVis.yRightScaleType.help', {
defaultMessage: 'The scale type of the right y axis',
}),
getLegendHelp: () =>
i18n.translate('expressionXY.xyVis.legend.help', {
defaultMessage: 'Configure the chart legend.',
@ -77,22 +45,6 @@ export const strings = {
i18n.translate('expressionXY.xyVis.valueLabels.help', {
defaultMessage: 'Value labels mode',
}),
getTickLabelsVisibilitySettingsHelp: () =>
i18n.translate('expressionXY.xyVis.tickLabelsVisibilitySettings.help', {
defaultMessage: 'Show x and y axes tick labels',
}),
getLabelsOrientationHelp: () =>
i18n.translate('expressionXY.xyVis.labelsOrientation.help', {
defaultMessage: 'Defines the rotation of the axis labels',
}),
getGridlinesVisibilitySettingsHelp: () =>
i18n.translate('expressionXY.xyVis.gridlinesVisibilitySettings.help', {
defaultMessage: 'Show x and y axes gridlines',
}),
getAxisTitlesVisibilitySettingsHelp: () =>
i18n.translate('expressionXY.xyVis.axisTitlesVisibilitySettings.help', {
defaultMessage: 'Show x and y axes titles',
}),
getDataLayerHelp: () =>
i18n.translate('expressionXY.xyVis.dataLayer.help', {
defaultMessage: 'Data layer of visual series',
@ -125,6 +77,14 @@ export const strings = {
i18n.translate('expressionXY.xyVis.ariaLabel.help', {
defaultMessage: 'Specifies the aria label of the xy chart',
}),
getXAxisConfigHelp: () =>
i18n.translate('expressionXY.xyVis.xAxisConfig.help', {
defaultMessage: 'Specifies x-axis config',
}),
getyAxisConfigsHelp: () =>
i18n.translate('expressionXY.xyVis.yAxisConfigs.help', {
defaultMessage: 'Specifies y-axes configs',
}),
getDetailedTooltipHelp: () =>
i18n.translate('expressionXY.xyVis.detailedTooltip.help', {
defaultMessage: 'Show detailed tooltip',
@ -185,6 +145,18 @@ export const strings = {
i18n.translate('expressionXY.dataLayer.isHistogram.help', {
defaultMessage: 'Whether to layout the chart as a histogram',
}),
getIsStackedHelp: () =>
i18n.translate('expressionXY.dataLayer.isStacked.help', {
defaultMessage: 'Layout of the chart in stacked mode',
}),
getIsPercentageHelp: () =>
i18n.translate('expressionXY.dataLayer.isPercentage.help', {
defaultMessage: 'Whether to layout the chart has percentage mode',
}),
getIsHorizontalHelp: () =>
i18n.translate('expressionXY.dataLayer.isHorizontal.help', {
defaultMessage: 'Layout of the chart is horizontal',
}),
getSplitAccessorHelp: () =>
i18n.translate('expressionXY.dataLayer.splitAccessor.help', {
defaultMessage: 'The column to split by',
@ -213,9 +185,9 @@ export const strings = {
i18n.translate('expressionXY.dataLayer.showLines.help', {
defaultMessage: 'Show lines between points',
}),
getYConfigHelp: () =>
i18n.translate('expressionXY.dataLayer.yConfig.help', {
defaultMessage: 'Additional configuration for y axes',
getDecorationsHelp: () =>
i18n.translate('expressionXY.dataLayer.decorations.help', {
defaultMessage: 'Additional decoration for data',
}),
getColumnToLabelHelp: () =>
i18n.translate('expressionXY.layer.columnToLabel.help', {
@ -237,30 +209,26 @@ export const strings = {
i18n.translate('expressionXY.referenceLineLayer.accessors.help', {
defaultMessage: 'The columns to display on the y axis.',
}),
getRLYConfigHelp: () =>
i18n.translate('expressionXY.referenceLineLayer.yConfig.help', {
defaultMessage: 'Additional configuration for y axes',
getRLDecorationConfigHelp: () =>
i18n.translate('expressionXY.referenceLineLayer.decorationConfig.help', {
defaultMessage: 'Additional decoration for reference line',
}),
getRLHelp: () =>
i18n.translate('expressionXY.referenceLineLayer.help', {
defaultMessage: `Configure a reference line in the xy chart`,
}),
getYConfigFnHelp: () =>
i18n.translate('expressionXY.yConfig.help', {
defaultMessage: `Configure the behavior of a xy chart's y axis metric`,
}),
getForAccessorHelp: () =>
i18n.translate('expressionXY.yConfig.forAccessor.help', {
i18n.translate('expressionXY.decorationConfig.forAccessor.help', {
defaultMessage: 'The accessor this configuration is for',
}),
getAxisModeHelp: () =>
i18n.translate('expressionXY.yConfig.axisMode.help', {
defaultMessage: 'The axis mode of the metric',
}),
getColorHelp: () =>
i18n.translate('expressionXY.yConfig.color.help', {
i18n.translate('expressionXY.decorationConfig.color.help', {
defaultMessage: 'The color of the series',
}),
getAxisIdHelp: () =>
i18n.translate('expressionXY.decorationConfig.axisId.help', {
defaultMessage: 'Id of axis',
}),
getAnnotationLayerFnHelp: () =>
i18n.translate('expressionXY.annotationLayer.help', {
defaultMessage: `Configure an annotation layer in the xy chart`,
@ -273,6 +241,74 @@ export const strings = {
i18n.translate('expressionXY.annotationLayer.annotations.help', {
defaultMessage: 'Annotations',
}),
getXAxisConfigFnHelp: () =>
i18n.translate('expressionXY.xAxisConfigFn.help', {
defaultMessage: `Configure the xy chart's x-axis config`,
}),
getYAxisConfigFnHelp: () =>
i18n.translate('expressionXY.yAxisConfigFn.help', {
defaultMessage: `Configure the xy chart's y-axis config`,
}),
getAxisModeHelp: () =>
i18n.translate('expressionXY.axisConfig.mode.help', {
defaultMessage: 'Scale mode. Can be normal, percentage, wiggle or silhouette',
}),
getAxisBoundsMarginHelp: () =>
i18n.translate('expressionXY.axisConfig.boundsMargin.help', {
defaultMessage: 'Margin of bounds',
}),
getAxisExtentHelp: () =>
i18n.translate('expressionXY.axisConfig.extent.help', {
defaultMessage: 'Axis extents',
}),
getAxisScaleTypeHelp: () =>
i18n.translate('expressionXY.axisConfig.scaleType.help', {
defaultMessage: 'The scale type of the axis',
}),
getAxisTitleHelp: () =>
i18n.translate('expressionXY.axisConfig.title.help', {
defaultMessage: 'Title of axis',
}),
getAxisPositionHelp: () =>
i18n.translate('expressionXY.axisConfig.position.help', {
defaultMessage: 'Position of axis',
}),
getAxisHideHelp: () =>
i18n.translate('expressionXY.axisConfig.hide.help', {
defaultMessage: 'Hide the axis',
}),
getAxisLabelColorHelp: () =>
i18n.translate('expressionXY.axisConfig.labelColor.help', {
defaultMessage: 'Color of the axis labels',
}),
getAxisShowOverlappingLabelsHelp: () =>
i18n.translate('expressionXY.axisConfig.showOverlappingLabels.help', {
defaultMessage: 'Show overlapping labels',
}),
getAxisShowDuplicatesHelp: () =>
i18n.translate('expressionXY.axisConfig.showDuplicates.help', {
defaultMessage: 'Show duplicated ticks',
}),
getAxisShowGridLinesHelp: () =>
i18n.translate('expressionXY.axisConfig.showGridLines.help', {
defaultMessage: 'Specifies whether or not the gridlines of the axis are visible',
}),
getAxisLabelsOrientationHelp: () =>
i18n.translate('expressionXY.axisConfig.labelsOrientation.help', {
defaultMessage: 'Specifies the labels orientation of the axis',
}),
getAxisShowLabelsHelp: () =>
i18n.translate('expressionXY.axisConfig.showLabels.help', {
defaultMessage: 'Show labels',
}),
getAxisShowTitleHelp: () =>
i18n.translate('expressionXY.axisConfig.showTitle.help', {
defaultMessage: 'Show title of the axis',
}),
getAxisTruncateHelp: () =>
i18n.translate('expressionXY.axisConfig.truncate.help', {
defaultMessage: 'The number of symbols before truncating',
}),
getReferenceLineNameHelp: () =>
i18n.translate('expressionXY.referenceLine.name.help', {
defaultMessage: 'Reference line name',

View file

@ -11,17 +11,17 @@ export const PLUGIN_NAME = 'expressionXy';
export type {
XYArgs,
YConfig,
EndValue,
XYRender,
LayerType,
YAxisMode,
LineStyle,
FillStyle,
SeriesType,
YScaleType,
XScaleType,
AxisMode,
AxisConfig,
YAxisConfig,
ValidLayer,
XYLayerArgs,
XYCurveType,
@ -33,30 +33,28 @@ export type {
AxisExtentMode,
DataLayerConfig,
FittingFunction,
ExtendedYConfig,
AxisExtentConfig,
CollectiveConfig,
LegendConfigResult,
AxesSettingsConfig,
XAxisConfigResult,
YAxisConfigResult,
CommonXYLayerConfig,
DataDecorationConfig,
AnnotationLayerArgs,
ExtendedYConfigResult,
GridlinesConfigResult,
DataLayerConfigResult,
TickLabelsConfigResult,
AxisExtentConfigResult,
ReferenceLineLayerArgs,
CommonXYDataLayerConfig,
LabelsOrientationConfig,
ReferenceLineLayerConfig,
AvailableReferenceLineIcon,
XYExtendedLayerConfigResult,
CommonXYAnnotationLayerConfig,
ExtendedDataLayerConfigResult,
LabelsOrientationConfigResult,
CommonXYDataLayerConfigResult,
ReferenceLineLayerConfigResult,
ReferenceLineDecorationConfig,
CommonXYReferenceLineLayerConfig,
AxisTitlesVisibilityConfigResult,
ReferenceLineDecorationConfigResult,
CommonXYReferenceLineLayerConfigResult,
} from './types';

View file

@ -25,14 +25,11 @@ import {
ValueLabelModes,
XScaleTypes,
XYCurveTypes,
YAxisModes,
YScaleTypes,
AxisModes,
REFERENCE_LINE,
Y_CONFIG,
AXIS_TITLES_VISIBILITY_CONFIG,
LABELS_ORIENTATION_CONFIG,
TICK_LABELS_CONFIG,
GRID_LINES_CONFIG,
DATA_DECORATION_CONFIG,
REFERENCE_LINE_DECORATION_CONFIG,
LEGEND_CONFIG,
DATA_LAYER,
AXIS_EXTENT_CONFIG,
@ -40,23 +37,24 @@ import {
REFERENCE_LINE_LAYER,
ANNOTATION_LAYER,
EndValues,
EXTENDED_Y_CONFIG,
X_AXIS_CONFIG,
Y_AXIS_CONFIG,
AvailableReferenceLineIcons,
XY_VIS,
LAYERED_XY_VIS,
EXTENDED_ANNOTATION_LAYER,
REFERENCE_LINE_Y_CONFIG,
EXTENDED_REFERENCE_LINE_DECORATION_CONFIG,
} from '../constants';
import { XYRender } from './expression_renderers';
export type EndValue = $Values<typeof EndValues>;
export type LayerType = $Values<typeof LayerTypes>;
export type YAxisMode = $Values<typeof YAxisModes>;
export type LineStyle = $Values<typeof LineStyles>;
export type FillStyle = $Values<typeof FillStyles>;
export type SeriesType = $Values<typeof SeriesTypes>;
export type YScaleType = $Values<typeof YScaleTypes>;
export type XScaleType = $Values<typeof XScaleTypes>;
export type AxisMode = $Values<typeof AxisModes>;
export type XYCurveType = $Values<typeof XYCurveTypes>;
export type IconPosition = $Values<typeof IconPositions>;
export type ValueLabelMode = $Values<typeof ValueLabelModes>;
@ -65,7 +63,6 @@ export type FittingFunction = $Values<typeof FittingFunctions>;
export type AvailableReferenceLineIcon = $Values<typeof AvailableReferenceLineIcons>;
export interface AxesSettingsConfig {
x: boolean;
yLeft: boolean;
yRight: boolean;
}
@ -77,23 +74,41 @@ export interface AxisExtentConfig {
}
export interface AxisConfig {
title: string;
title?: string;
hide?: boolean;
id?: string;
position?: Position;
labelColor?: string;
showOverlappingLabels?: boolean;
showDuplicates?: boolean;
labelsOrientation?: number;
truncate?: number;
showLabels?: boolean;
showTitle?: boolean;
showGridLines?: boolean;
extent?: AxisExtentConfigResult;
}
export interface ExtendedYConfig extends YConfig {
export interface YAxisConfig extends AxisConfig {
mode?: AxisMode;
boundsMargin?: number;
scaleType?: YScaleType;
}
export interface ReferenceLineDecorationConfig extends DataDecorationConfig {
icon?: AvailableReferenceLineIcon;
lineWidth?: number;
lineStyle?: LineStyle;
fill?: FillStyle;
iconPosition?: IconPosition;
textVisibility?: boolean;
position?: Position;
}
export interface YConfig {
export interface DataDecorationConfig {
forAccessor: string;
axisMode?: YAxisMode;
color?: string;
axisId?: string;
}
export interface DataLayerArgs {
@ -110,8 +125,11 @@ export interface DataLayerArgs {
columnToLabel?: string; // Actually a JSON key-value pair
xScaleType: XScaleType;
isHistogram: boolean;
isPercentage: boolean;
isStacked: boolean;
isHorizontal: boolean;
palette: PaletteOutput;
yConfig?: YConfigResult[];
decorations?: DataDecorationConfigResult[];
}
export interface ValidLayer extends DataLayerConfigResult {
@ -133,9 +151,12 @@ export interface ExtendedDataLayerArgs {
columnToLabel?: string; // Actually a JSON key-value pair
xScaleType: XScaleType;
isHistogram: boolean;
isPercentage: boolean;
isStacked: boolean;
isHorizontal: boolean;
palette: PaletteOutput;
// palette will always be set on the expression
yConfig?: YConfigResult[];
decorations?: DataDecorationConfigResult[];
table?: Datatable;
}
@ -185,22 +206,8 @@ export interface LegendConfig {
legendSize?: LegendSize;
}
export interface LabelsOrientationConfig {
x: number;
yLeft: number;
yRight: number;
}
// Arguments to XY chart expression, with computed properties
export interface XYArgs extends DataLayerArgs {
xTitle: string;
yTitle: string;
yRightTitle: string;
xExtent?: AxisExtentConfigResult;
yLeftExtent: AxisExtentConfigResult;
yRightExtent: AxisExtentConfigResult;
yLeftScale: YScaleType;
yRightScale: YScaleType;
legend: LegendConfigResult;
endValue?: EndValue;
emphasizeFitting?: boolean;
@ -208,15 +215,13 @@ export interface XYArgs extends DataLayerArgs {
referenceLines: ReferenceLineConfigResult[];
annotationLayers: AnnotationLayerConfigResult[];
fittingFunction?: FittingFunction;
axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult;
tickLabelsVisibilitySettings?: TickLabelsConfigResult;
gridlinesVisibilitySettings?: GridlinesConfigResult;
labelsOrientation?: LabelsOrientationConfigResult;
curveType?: XYCurveType;
fillOpacity?: number;
hideEndzones?: boolean;
valuesInLegend?: boolean;
ariaLabel?: string;
yAxisConfigs?: YAxisConfigResult[];
xAxisConfig?: XAxisConfigResult;
addTimeMarker?: boolean;
markSizeRatio?: number;
minTimeBarInterval?: string;
@ -228,29 +233,19 @@ export interface XYArgs extends DataLayerArgs {
}
export interface LayeredXYArgs {
xTitle: string;
yTitle: string;
yRightTitle: string;
xExtent?: AxisExtentConfigResult;
yLeftExtent: AxisExtentConfigResult;
yRightExtent: AxisExtentConfigResult;
yLeftScale: YScaleType;
yRightScale: YScaleType;
legend: LegendConfigResult;
endValue?: EndValue;
emphasizeFitting?: boolean;
valueLabels: ValueLabelMode;
layers?: XYExtendedLayerConfigResult[];
fittingFunction?: FittingFunction;
axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult;
tickLabelsVisibilitySettings?: TickLabelsConfigResult;
gridlinesVisibilitySettings?: GridlinesConfigResult;
labelsOrientation?: LabelsOrientationConfigResult;
curveType?: XYCurveType;
fillOpacity?: number;
hideEndzones?: boolean;
valuesInLegend?: boolean;
ariaLabel?: string;
yAxisConfigs?: YAxisConfigResult[];
xAxisConfig?: XAxisConfigResult;
detailedTooltip?: boolean;
addTimeMarker?: boolean;
markSizeRatio?: number;
@ -260,29 +255,19 @@ export interface LayeredXYArgs {
}
export interface XYProps {
xTitle: string;
yTitle: string;
yRightTitle: string;
xExtent?: AxisExtentConfigResult;
yLeftExtent: AxisExtentConfigResult;
yRightExtent: AxisExtentConfigResult;
yLeftScale: YScaleType;
yRightScale: YScaleType;
legend: LegendConfigResult;
endValue?: EndValue;
emphasizeFitting?: boolean;
valueLabels: ValueLabelMode;
layers: CommonXYLayerConfig[];
fittingFunction?: FittingFunction;
axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult;
tickLabelsVisibilitySettings?: TickLabelsConfigResult;
gridlinesVisibilitySettings?: GridlinesConfigResult;
labelsOrientation?: LabelsOrientationConfigResult;
curveType?: XYCurveType;
fillOpacity?: number;
hideEndzones?: boolean;
valuesInLegend?: boolean;
ariaLabel?: string;
yAxisConfigs?: YAxisConfigResult[];
xAxisConfig?: XAxisConfigResult;
addTimeMarker?: boolean;
markSizeRatio?: number;
minTimeBarInterval?: string;
@ -312,7 +297,8 @@ export type ExtendedAnnotationLayerConfigResult = ExtendedAnnotationLayerArgs &
layerType: typeof LayerTypes.ANNOTATIONS;
};
export interface ReferenceLineArgs extends Omit<ExtendedYConfig, 'forAccessor' | 'fill'> {
export interface ReferenceLineArgs
extends Omit<ReferenceLineDecorationConfig, 'forAccessor' | 'fill'> {
name?: string;
value: number;
fill: FillStyle;
@ -322,7 +308,7 @@ export interface ReferenceLineLayerArgs {
layerId?: string;
accessors: string[];
columnToLabel?: string;
yConfig?: ExtendedYConfigResult[];
decorations?: ReferenceLineDecorationConfigResult[];
table?: Datatable;
}
@ -338,15 +324,15 @@ export type XYExtendedLayerConfigResult =
| ReferenceLineLayerConfigResult
| ExtendedAnnotationLayerConfigResult;
export interface ReferenceLineYConfig extends ReferenceLineArgs {
type: typeof REFERENCE_LINE_Y_CONFIG;
export interface ExtendedReferenceLineDecorationConfig extends ReferenceLineArgs {
type: typeof EXTENDED_REFERENCE_LINE_DECORATION_CONFIG;
}
export interface ReferenceLineConfigResult {
type: typeof REFERENCE_LINE;
layerType: typeof LayerTypes.REFERENCELINE;
lineLength: number;
yConfig: [ReferenceLineYConfig];
decorations: [ExtendedReferenceLineDecorationConfig];
}
export type ReferenceLineLayerConfigResult = ReferenceLineLayerArgs & {
@ -381,21 +367,18 @@ export type ExtendedDataLayerConfigResult = Omit<ExtendedDataLayerArgs, 'palette
table: Datatable;
};
export type YConfigResult = YConfig & { type: typeof Y_CONFIG };
export type ExtendedYConfigResult = ExtendedYConfig & { type: typeof EXTENDED_Y_CONFIG };
export type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & {
type: typeof AXIS_TITLES_VISIBILITY_CONFIG;
export type DataDecorationConfigResult = DataDecorationConfig & {
type: typeof DATA_DECORATION_CONFIG;
};
export type ReferenceLineDecorationConfigResult = ReferenceLineDecorationConfig & {
type: typeof REFERENCE_LINE_DECORATION_CONFIG;
};
export type LabelsOrientationConfigResult = LabelsOrientationConfig & {
type: typeof LABELS_ORIENTATION_CONFIG;
};
export type XAxisConfigResult = AxisConfig & { type: typeof X_AXIS_CONFIG };
export type YAxisConfigResult = YAxisConfig & { type: typeof Y_AXIS_CONFIG };
export type LegendConfigResult = LegendConfig & { type: typeof LEGEND_CONFIG };
export type AxisExtentConfigResult = AxisExtentConfig & { type: typeof AXIS_EXTENT_CONFIG };
export type GridlinesConfigResult = AxesSettingsConfig & { type: typeof GRID_LINES_CONFIG };
export type TickLabelsConfigResult = AxesSettingsConfig & { type: typeof TICK_LABELS_CONFIG };
export type CommonXYLayerConfig = XYLayerConfig | XYExtendedLayerConfig;
export type CommonXYDataLayerConfigResult = DataLayerConfigResult | ExtendedDataLayerConfigResult;
@ -441,12 +424,17 @@ export type ReferenceLineLayerFn = ExpressionFunctionDefinition<
Promise<ReferenceLineLayerConfigResult>
>;
export type YConfigFn = ExpressionFunctionDefinition<typeof Y_CONFIG, null, YConfig, YConfigResult>;
export type ExtendedYConfigFn = ExpressionFunctionDefinition<
typeof EXTENDED_Y_CONFIG,
export type DataDecorationConfigFn = ExpressionFunctionDefinition<
typeof DATA_DECORATION_CONFIG,
null,
ExtendedYConfig,
ExtendedYConfigResult
DataDecorationConfig,
DataDecorationConfigResult
>;
export type ReferenceLineDecorationConfigFn = ExpressionFunctionDefinition<
typeof REFERENCE_LINE_DECORATION_CONFIG,
null,
ReferenceLineDecorationConfig,
ReferenceLineDecorationConfigResult
>;
export type LegendConfigFn = ExpressionFunctionDefinition<
@ -455,3 +443,17 @@ export type LegendConfigFn = ExpressionFunctionDefinition<
LegendConfig,
Promise<LegendConfigResult>
>;
export type XAxisConfigFn = ExpressionFunctionDefinition<
typeof X_AXIS_CONFIG,
null,
AxisConfig,
XAxisConfigResult
>;
export type YAxisConfigFn = ExpressionFunctionDefinition<
typeof Y_AXIS_CONFIG,
null,
YAxisConfig,
YAxisConfigResult
>;

View file

@ -26,7 +26,7 @@ export interface XYRender {
export interface CollectiveConfig extends Omit<ManualPointEventAnnotationArgs, 'icon'> {
roundedTimestamp: number;
axisMode: 'bottom';
position: 'bottom';
icon?: AvailableAnnotationIcon | string;
customTooltipDetails?: AnnotationTooltipFormatter | undefined;
}

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { Position } from '@elastic/charts';
import { Datatable } from '@kbn/expressions-plugin/common';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { LayerTypes } from '../../common/constants';
@ -167,8 +168,11 @@ export const dateHistogramLayer: DataLayerConfig = {
xAccessor: 'xAccessorId',
xScaleType: 'time',
isHistogram: true,
isStacked: true,
isPercentage: false,
isHorizontal: false,
splitAccessor: 'splitAccessorId',
seriesType: 'bar_stacked',
seriesType: 'bar',
accessors: ['yAccessorId'],
palette: mockPaletteOutput,
table: dateHistogramData,
@ -197,7 +201,13 @@ export function sampleArgsWithReferenceLine(value: number = 150) {
type: 'referenceLineLayer',
layerType: LayerTypes.REFERENCELINE,
accessors: ['referenceLine-a'],
yConfig: [{ axisMode: 'left', forAccessor: 'referenceLine-a', type: 'extendedYConfig' }],
decorations: [
{
forAccessor: 'referenceLine-a',
type: 'referenceLineDecorationConfig',
position: Position.Left,
},
],
table: data,
},
],

View file

@ -155,7 +155,7 @@ export const getAnnotationsGroupedByInterval = (
collectiveConfig = {
...configArr[0],
roundedTimestamp: Number(roundedTimestamp),
axisMode: 'bottom',
position: 'bottom',
};
if (configArr.length > 1) {
const commonStyles = getCommonStyles(configArr);

View file

@ -25,7 +25,7 @@ import {
XYCurveType,
XScaleType,
} from '../../common';
import { SeriesTypes, ValueLabelModes } from '../../common/constants';
import { SeriesTypes, ValueLabelModes, AxisModes } from '../../common/constants';
import {
getColorAssignments,
getFitOptions,
@ -90,12 +90,14 @@ export const DataLayers: FC<Props> = ({
// In order to do it we need to make a copy of the table as the raw one is required for more features (filters, etc...) later on
const formattedDatatableInfo = formattedDatatables[layerId];
const isPercentage = seriesType.includes('percentage');
const yAxis = yAxesConfiguration.find((axisConfiguration) =>
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === yColumnId)
);
const isPercentage = yAxis?.mode
? yAxis?.mode === AxisModes.PERCENTAGE
: layer.isPercentage;
const seriesProps = getSeriesProps({
layer,
titles: titles[layer.layerId],
@ -129,11 +131,6 @@ export const DataLayers: FC<Props> = ({
/>
);
case SeriesTypes.BAR:
case SeriesTypes.BAR_STACKED:
case SeriesTypes.BAR_PERCENTAGE_STACKED:
case SeriesTypes.BAR_HORIZONTAL:
case SeriesTypes.BAR_HORIZONTAL_STACKED:
case SeriesTypes.BAR_HORIZONTAL_PERCENTAGE_STACKED:
const valueLabelsSettings = {
displayValueSettings: {
// This format double fixes two issues in elastic-chart
@ -150,22 +147,12 @@ export const DataLayers: FC<Props> = ({
},
};
return <BarSeries key={index} {...seriesProps} {...valueLabelsSettings} />;
case SeriesTypes.AREA_STACKED:
case SeriesTypes.AREA_PERCENTAGE_STACKED:
return (
<AreaSeries
key={index}
{...seriesProps}
fit={isPercentage ? 'zero' : getFitOptions(fittingFunction, endValue)}
curve={curve}
/>
);
case SeriesTypes.AREA:
return (
<AreaSeries
key={index}
{...seriesProps}
fit={getFitOptions(fittingFunction, endValue)}
fit={isPercentage ? 'zero' : getFitOptions(fittingFunction, endValue)}
curve={curve}
/>
);

View file

@ -157,6 +157,9 @@ const sampleLayer: DataLayerConfig = {
type: 'dataLayer',
layerType: LayerTypes.DATA,
seriesType: 'line',
isStacked: false,
isPercentage: false,
isHorizontal: false,
showLines: true,
xAccessor: 'c',
accessors: ['a', 'b'],

View file

@ -10,47 +10,49 @@ 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';
import { AxesMap, GroupsConfiguration } from '../../helpers';
import { getAxisGroupForReferenceLine } from './utils';
interface ReferenceLineProps {
layer: ReferenceLineConfig;
paddingMap: Partial<Record<Position, number>>;
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
axesMap: Record<'left' | 'right', boolean>;
xAxisFormatter: FieldFormat;
axesConfiguration: GroupsConfiguration;
isHorizontal: boolean;
nextValue?: number;
yAxesMap: AxesMap;
}
export const ReferenceLine: FC<ReferenceLineProps> = ({
layer,
axesMap,
formatters,
axesConfiguration,
xAxisFormatter,
paddingMap,
isHorizontal,
nextValue,
yAxesMap,
}) => {
const {
yConfig: [yConfig],
decorations: [decorationConfig],
} = layer;
if (!yConfig) {
if (!decorationConfig) {
return null;
}
const { axisMode, value } = yConfig;
const { value } = decorationConfig;
// Find the formatter for the given axis
const groupId = getGroupId(axisMode);
const axisGroup = getAxisGroupForReferenceLine(axesConfiguration, decorationConfig, isHorizontal);
const formatter = formatters[groupId || 'bottom'];
const formatter = axisGroup?.formatter || xAxisFormatter;
const id = `${layer.layerId}-${value}`;
return (
<ReferenceLineAnnotations
config={{ id, ...yConfig, nextValue }}
config={{ id, ...decorationConfig, nextValue, axisGroup }}
paddingMap={paddingMap}
axesMap={axesMap}
axesMap={yAxesMap}
formatter={formatter}
isHorizontal={isHorizontal}
/>

View file

@ -10,18 +10,21 @@ import { AnnotationDomainType, LineAnnotation, Position, RectAnnotation } from '
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 {
AxesMap,
AxisConfiguration,
getOriginalAxisPosition,
LINES_MARKER_SIZE,
} from '../../helpers';
import {
AvailableReferenceLineIcon,
FillStyle,
IconPosition,
LineStyle,
YAxisMode,
} from '../../../common/types';
import {
getBaseIconPlacement,
getBottomRect,
getGroupId,
getHorizontalRect,
getLineAnnotationProps,
getSharedStyle,
@ -38,7 +41,7 @@ export interface ReferenceLineAnnotationConfig {
fill?: FillStyle;
iconPosition?: IconPosition;
textVisibility?: boolean;
axisMode?: YAxisMode;
axisGroup?: AxisConfiguration;
color?: string;
}
@ -46,18 +49,19 @@ interface Props {
config: ReferenceLineAnnotationConfig;
paddingMap: Partial<Record<Position, number>>;
formatter?: FieldFormat;
axesMap: Record<'left' | 'right', boolean>;
axesMap: AxesMap;
isHorizontal: boolean;
}
const getRectDataValue = (
annotationConfig: ReferenceLineAnnotationConfig,
formatter: FieldFormat | undefined
formatter: FieldFormat | undefined,
groupId: string
) => {
const { name, value, nextValue, fill, axisMode } = annotationConfig;
const { name, value, nextValue, fill } = annotationConfig;
const isFillAbove = fill === 'above';
if (axisMode === 'bottom') {
if (groupId === Position.Bottom) {
return getBottomRect(name, isFillAbove, formatter, value, nextValue);
}
@ -71,13 +75,15 @@ export const ReferenceLineAnnotations: FC<Props> = ({
paddingMap,
isHorizontal,
}) => {
const { id, axisMode, iconPosition, name, textVisibility, value, fill, color } = config;
const { id, axisGroup, 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);
const markerPositionVertical = getBaseIconPlacement(
iconPosition,
axesMap,
getOriginalAxisPosition(axisGroup?.position ?? Position.Bottom, isHorizontal)
);
// the padding map is built for vertical chart
const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE;
@ -89,7 +95,6 @@ export const ReferenceLineAnnotations: FC<Props> = ({
},
axesMap,
paddingMap,
groupId,
isHorizontal
);
@ -108,7 +113,9 @@ export const ReferenceLineAnnotations: FC<Props> = ({
key={`${id}-line`}
dataValues={[dataValues]}
domainType={
axisMode === 'bottom' ? AnnotationDomainType.XDomain : AnnotationDomainType.YDomain
props.groupId === Position.Bottom
? AnnotationDomainType.XDomain
: AnnotationDomainType.YDomain
}
style={{ line: { ...sharedStyle, opacity: 1 } }}
/>
@ -116,7 +123,7 @@ export const ReferenceLineAnnotations: FC<Props> = ({
let rect;
if (fill && fill !== 'none') {
const rectDataValues = getRectDataValue(config, formatter);
const rectDataValues = getRectDataValue(config, formatter, props.groupId);
rect = (
<RectAnnotation

View file

@ -11,67 +11,73 @@ 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';
import { LayerAccessorsTitles } from '../../helpers';
import { LayerAccessorsTitles, GroupsConfiguration, AxesMap } from '../../helpers';
import { getAxisGroupForReferenceLine } from './utils';
interface ReferenceLineLayerProps {
layer: ReferenceLineLayerConfig;
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
paddingMap: Partial<Record<Position, number>>;
axesMap: Record<'left' | 'right', boolean>;
isHorizontal: boolean;
titles?: LayerAccessorsTitles;
xAxisFormatter: FieldFormat;
axesConfiguration: GroupsConfiguration;
yAxesMap: AxesMap;
}
export const ReferenceLineLayer: FC<ReferenceLineLayerProps> = ({
layer,
formatters,
axesConfiguration,
xAxisFormatter,
paddingMap,
axesMap,
isHorizontal,
titles,
yAxesMap,
}) => {
if (!layer.yConfig) {
if (!layer.decorations) {
return null;
}
const { columnToLabel, yConfig: yConfigs, table } = layer;
const { columnToLabel, decorations, table } = layer;
const columnToLabelMap: Record<string, string> = columnToLabel ? JSON.parse(columnToLabel) : {};
const row = table.rows[0];
const yConfigByValue = yConfigs.sort(
const decorationConfigsByValue = decorations.sort(
({ forAccessor: idA }, { forAccessor: idB }) => row[idA] - row[idB]
);
const groupedByDirection = groupBy(yConfigByValue, 'fill');
const groupedByDirection = groupBy(decorationConfigsByValue, '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] ?? titles?.yTitles?.[yConfig.forAccessor];
const value = row[yConfig.forAccessor];
const yConfigsWithSameDirection = groupedByDirection[yConfig.fill!];
const indexFromSameType = yConfigsWithSameDirection.findIndex(
({ forAccessor }) => forAccessor === yConfig.forAccessor
const referenceLineElements = decorationConfigsByValue.flatMap((decorationConfig) => {
const axisGroup = getAxisGroupForReferenceLine(
axesConfiguration,
decorationConfig,
isHorizontal
);
const shouldCheckNextReferenceLine = indexFromSameType < yConfigsWithSameDirection.length - 1;
const formatter = axisGroup?.formatter || xAxisFormatter;
const name =
columnToLabelMap[decorationConfig.forAccessor] ??
titles?.yTitles?.[decorationConfig.forAccessor];
const value = row[decorationConfig.forAccessor];
const yDecorationsWithSameDirection = groupedByDirection[decorationConfig.fill!];
const indexFromSameType = yDecorationsWithSameDirection.findIndex(
({ forAccessor }) => forAccessor === decorationConfig.forAccessor
);
const shouldCheckNextReferenceLine =
indexFromSameType < yDecorationsWithSameDirection.length - 1;
const nextValue = shouldCheckNextReferenceLine
? row[yConfigsWithSameDirection[indexFromSameType + 1].forAccessor]
? row[yDecorationsWithSameDirection[indexFromSameType + 1].forAccessor]
: undefined;
const { forAccessor, type, ...restAnnotationConfig } = yConfig;
const id = `${layer.layerId}-${yConfig.forAccessor}`;
const { forAccessor, type, ...restAnnotationConfig } = decorationConfig;
const id = `${layer.layerId}-${decorationConfig.forAccessor}`;
return (
<ReferenceLineAnnotations
@ -82,9 +88,10 @@ export const ReferenceLineLayer: FC<ReferenceLineLayerProps> = ({
nextValue,
name,
...restAnnotationConfig,
axisGroup,
}}
axesMap={yAxesMap}
paddingMap={paddingMap}
axesMap={axesMap}
formatter={formatter}
isHorizontal={isHorizontal}
/>

View file

@ -15,7 +15,7 @@ import { LayerTypes } from '../../../common/constants';
import {
ReferenceLineLayerArgs,
ReferenceLineLayerConfig,
ExtendedYConfig,
ExtendedReferenceLineDecorationConfig,
ReferenceLineArgs,
ReferenceLineConfig,
} from '../../../common/types';
@ -46,12 +46,14 @@ const data: Datatable = {
})),
};
function createLayers(yConfigs: ReferenceLineLayerArgs['yConfig']): ReferenceLineLayerConfig[] {
function createLayers(
decorations: ReferenceLineLayerArgs['decorations']
): ReferenceLineLayerConfig[] {
return [
{
layerId: 'first',
accessors: (yConfigs || []).map(({ forAccessor }) => forAccessor),
yConfig: yConfigs,
accessors: (decorations || []).map(({ forAccessor }) => forAccessor),
decorations,
type: 'referenceLineLayer',
layerType: LayerTypes.REFERENCELINE,
table: data,
@ -69,7 +71,7 @@ function createReferenceLine(
type: 'referenceLine',
layerType: 'referenceLine',
lineLength,
yConfig: [{ type: 'referenceLineYConfig', ...args }],
decorations: [{ type: 'extendedReferenceLineDecorationConfig', ...args }],
};
}
@ -82,7 +84,7 @@ interface XCoords {
x1: number | undefined;
}
function getAxisFromId(layerPrefix: string): ExtendedYConfig['axisMode'] {
function getAxisFromId(layerPrefix: string): ExtendedReferenceLineDecorationConfig['position'] {
return /left/i.test(layerPrefix) ? 'left' : /right/i.test(layerPrefix) ? 'right' : 'bottom';
}
@ -90,21 +92,42 @@ 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,
xAxisFormatter: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
isHorizontal: false,
axesMap: { left: true, right: false },
axesConfiguration: [
{
groupId: 'left',
position: 'left',
series: [],
},
{
groupId: 'right',
position: 'right',
series: [],
},
{
groupId: 'bottom',
position: 'bottom',
series: [],
},
],
paddingMap: {},
yAxesMap: {
left: {
groupId: 'left',
position: 'left',
series: [],
},
right: {
groupId: 'right',
position: 'right',
series: [],
},
},
};
});
@ -113,20 +136,20 @@ describe('ReferenceLines', () => {
['yAccessorLeft', 'below'],
['yAccessorRight', 'above'],
['yAccessorRight', 'below'],
] as Array<[string, Exclude<ExtendedYConfig['fill'], undefined>]>)(
] as Array<[string, Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>]>)(
'should render a RectAnnotation for a reference line with fill set: %s %s',
(layerPrefix, fill) => {
const axisMode = getAxisFromId(layerPrefix);
const position = getAxisFromId(layerPrefix);
const wrapper = shallow(
<ReferenceLines
{...defaultProps}
layers={createLayers([
{
forAccessor: `${layerPrefix}FirstId`,
axisMode,
position,
lineStyle: 'solid',
fill,
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
},
])}
/>
@ -154,7 +177,7 @@ describe('ReferenceLines', () => {
it.each([
['xAccessor', 'above'],
['xAccessor', 'below'],
] as Array<[string, Exclude<ExtendedYConfig['fill'], undefined>]>)(
] as Array<[string, Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>]>)(
'should render a RectAnnotation for a reference line with fill set: %s %s',
(layerPrefix, fill) => {
const wrapper = shallow(
@ -163,9 +186,9 @@ describe('ReferenceLines', () => {
layers={createLayers([
{
forAccessor: `${layerPrefix}FirstId`,
axisMode: 'bottom',
position: 'bottom',
lineStyle: 'solid',
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
fill,
},
])}
@ -196,26 +219,26 @@ describe('ReferenceLines', () => {
['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, Exclude<ExtendedYConfig['fill'], undefined>, YCoords, YCoords]>)(
] as Array<[string, Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>, 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 position = getAxisFromId(layerPrefix);
const wrapper = shallow(
<ReferenceLines
{...defaultProps}
layers={createLayers([
{
forAccessor: `${layerPrefix}FirstId`,
axisMode,
position,
lineStyle: 'solid',
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
fill,
},
{
forAccessor: `${layerPrefix}SecondId`,
axisMode,
position,
lineStyle: 'solid',
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
fill,
},
])}
@ -252,7 +275,7 @@ describe('ReferenceLines', () => {
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, Exclude<ExtendedYConfig['fill'], undefined>, XCoords, XCoords]>)(
] as Array<[string, Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>, 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(
@ -261,16 +284,16 @@ describe('ReferenceLines', () => {
layers={createLayers([
{
forAccessor: `${layerPrefix}FirstId`,
axisMode: 'bottom',
position: 'bottom',
lineStyle: 'solid',
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
fill,
},
{
forAccessor: `${layerPrefix}SecondId`,
axisMode: 'bottom',
position: 'bottom',
lineStyle: 'solid',
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
fill,
},
])}
@ -307,7 +330,7 @@ describe('ReferenceLines', () => {
it.each(['yAccessorLeft', 'yAccessorRight', 'xAccessor'])(
'should let areas in different directions overlap: %s',
(layerPrefix) => {
const axisMode = getAxisFromId(layerPrefix);
const position = getAxisFromId(layerPrefix);
const wrapper = shallow(
<ReferenceLines
@ -315,17 +338,17 @@ describe('ReferenceLines', () => {
layers={createLayers([
{
forAccessor: `${layerPrefix}FirstId`,
axisMode,
position,
lineStyle: 'solid',
fill: 'above',
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
},
{
forAccessor: `${layerPrefix}SecondId`,
axisMode,
position,
lineStyle: 'solid',
fill: 'below',
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
},
])}
/>
@ -338,8 +361,8 @@ describe('ReferenceLines', () => {
).toEqual(
expect.arrayContaining([
{
coordinates: { ...emptyCoords, ...(axisMode === 'bottom' ? { x0: 1 } : { y0: 5 }) },
details: axisMode === 'bottom' ? 1 : 5,
coordinates: { ...emptyCoords, ...(position === 'bottom' ? { x0: 1 } : { y0: 5 }) },
details: position === 'bottom' ? 1 : 5,
header: undefined,
},
])
@ -349,8 +372,8 @@ describe('ReferenceLines', () => {
).toEqual(
expect.arrayContaining([
{
coordinates: { ...emptyCoords, ...(axisMode === 'bottom' ? { x1: 2 } : { y1: 10 }) },
details: axisMode === 'bottom' ? 2 : 10,
coordinates: { ...emptyCoords, ...(position === 'bottom' ? { x1: 2 } : { y1: 10 }) },
details: position === 'bottom' ? 2 : 10,
header: undefined,
},
])
@ -361,7 +384,7 @@ describe('ReferenceLines', () => {
it.each([
['above', { y0: 5, y1: 10 }, { y0: 10, y1: undefined }],
['below', { y0: undefined, y1: 5 }, { y0: 5, y1: 10 }],
] as Array<[Exclude<ExtendedYConfig['fill'], undefined>, YCoords, YCoords]>)(
] as Array<[Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>, 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(
@ -370,17 +393,17 @@ describe('ReferenceLines', () => {
layers={createLayers([
{
forAccessor: `yAccessorLeftFirstId`,
axisMode: 'left',
position: 'left',
lineStyle: 'solid',
fill,
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
},
{
forAccessor: `yAccessorRightSecondId`,
axisMode: 'right',
position: 'right',
lineStyle: 'solid',
fill,
type: 'extendedYConfig',
type: 'referenceLineDecorationConfig',
},
])}
/>
@ -415,21 +438,42 @@ describe('ReferenceLines', () => {
});
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,
xAxisFormatter: { convert: jest.fn((x) => x) } as unknown as FieldFormat,
isHorizontal: false,
axesMap: { left: true, right: false },
axesConfiguration: [
{
groupId: 'left',
position: 'left',
series: [],
},
{
groupId: 'right',
position: 'right',
series: [],
},
{
groupId: 'bottom',
position: 'bottom',
series: [],
},
],
paddingMap: {},
yAxesMap: {
left: {
groupId: 'left',
position: 'left',
series: [],
},
right: {
groupId: 'right',
position: 'right',
series: [],
},
},
};
});
@ -438,17 +482,17 @@ describe('ReferenceLines', () => {
['yAccessorLeft', 'below'],
['yAccessorRight', 'above'],
['yAccessorRight', 'below'],
] as Array<[string, Exclude<ExtendedYConfig['fill'], undefined>]>)(
] as Array<[string, Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>]>)(
'should render a RectAnnotation for a reference line with fill set: %s %s',
(layerPrefix, fill) => {
const axisMode = getAxisFromId(layerPrefix);
const position = getAxisFromId(layerPrefix);
const value = 5;
const wrapper = shallow(
<ReferenceLines
{...defaultProps}
layers={[
createReferenceLine(layerPrefix, 1, {
axisMode,
position,
lineStyle: 'solid',
fill,
value,
@ -479,7 +523,7 @@ describe('ReferenceLines', () => {
it.each([
['xAccessor', 'above'],
['xAccessor', 'below'],
] as Array<[string, Exclude<ExtendedYConfig['fill'], undefined>]>)(
] as Array<[string, Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>]>)(
'should render a RectAnnotation for a reference line with fill set: %s %s',
(layerPrefix, fill) => {
const value = 1;
@ -488,7 +532,7 @@ describe('ReferenceLines', () => {
{...defaultProps}
layers={[
createReferenceLine(layerPrefix, 1, {
axisMode: 'bottom',
position: 'bottom',
lineStyle: 'solid',
fill,
value,
@ -519,23 +563,23 @@ describe('ReferenceLines', () => {
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, Exclude<ExtendedYConfig['fill'], undefined>, YCoords, YCoords]>)(
] as Array<[string, Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>, 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 position = getAxisFromId(layerPrefix);
const value = coordsA.y0 ?? coordsA.y1!;
const wrapper = shallow(
<ReferenceLines
{...defaultProps}
layers={[
createReferenceLine(layerPrefix, 10, {
axisMode,
position,
lineStyle: 'solid',
fill,
value,
}),
createReferenceLine(layerPrefix, 10, {
axisMode,
position,
lineStyle: 'solid',
fill,
value,
@ -570,7 +614,7 @@ describe('ReferenceLines', () => {
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, Exclude<ExtendedYConfig['fill'], undefined>, XCoords, XCoords]>)(
] as Array<[string, Exclude<ExtendedReferenceLineDecorationConfig['fill'], undefined>, 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!;
@ -579,13 +623,13 @@ describe('ReferenceLines', () => {
{...defaultProps}
layers={[
createReferenceLine(layerPrefix, 10, {
axisMode: 'bottom',
position: 'bottom',
lineStyle: 'solid',
fill,
value,
}),
createReferenceLine(layerPrefix, 10, {
axisMode: 'bottom',
position: 'bottom',
lineStyle: 'solid',
fill,
value,
@ -624,7 +668,7 @@ describe('ReferenceLines', () => {
it.each(['yAccessorLeft', 'yAccessorRight', 'xAccessor'])(
'should let areas in different directions overlap: %s',
(layerPrefix) => {
const axisMode = getAxisFromId(layerPrefix);
const position = getAxisFromId(layerPrefix);
const value1 = 1;
const value2 = 10;
const wrapper = shallow(
@ -632,13 +676,13 @@ describe('ReferenceLines', () => {
{...defaultProps}
layers={[
createReferenceLine(layerPrefix, 10, {
axisMode,
position,
lineStyle: 'solid',
fill: 'above',
value: value1,
}),
createReferenceLine(layerPrefix, 10, {
axisMode,
position,
lineStyle: 'solid',
fill: 'below',
value: value2,
@ -654,7 +698,7 @@ describe('ReferenceLines', () => {
{
coordinates: {
...emptyCoords,
...(axisMode === 'bottom' ? { x0: value1 } : { y0: value1 }),
...(position === 'bottom' ? { x0: value1 } : { y0: value1 }),
},
details: value1,
header: undefined,
@ -670,7 +714,7 @@ describe('ReferenceLines', () => {
{
coordinates: {
...emptyCoords,
...(axisMode === 'bottom' ? { x1: value2 } : { y1: value2 }),
...(position === 'bottom' ? { x1: value2 } : { y1: value2 }),
},
details: value2,
header: undefined,

View file

@ -12,18 +12,24 @@ import React from 'react';
import { Position } from '@elastic/charts';
import type { FieldFormat } from '@kbn/field-formats-plugin/common';
import type { CommonXYReferenceLineLayerConfig, ReferenceLineConfig } from '../../../common/types';
import { isReferenceLine, LayersAccessorsTitles } from '../../helpers';
import {
AxesMap,
GroupsConfiguration,
isReferenceLine,
LayersAccessorsTitles,
} from '../../helpers';
import { ReferenceLineLayer } from './reference_line_layer';
import { ReferenceLine } from './reference_line';
import { getNextValuesForReferenceLines } from './utils';
export interface ReferenceLinesProps {
layers: CommonXYReferenceLineLayerConfig[];
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
axesMap: Record<'left' | 'right', boolean>;
xAxisFormatter: FieldFormat;
axesConfiguration: GroupsConfiguration;
isHorizontal: boolean;
paddingMap: Partial<Record<Position, number>>;
titles?: LayersAccessorsTitles;
yAxesMap: AxesMap;
}
export const ReferenceLines = ({ layers, titles = {}, ...rest }: ReferenceLinesProps) => {
@ -36,13 +42,13 @@ export const ReferenceLines = ({ layers, titles = {}, ...rest }: ReferenceLinesP
return (
<>
{layers.flatMap((layer) => {
if (!layer.yConfig) {
if (!layer.decorations) {
return null;
}
const key = `referenceLine-${layer.layerId}`;
if (isReferenceLine(layer)) {
const nextValue = referenceLinesNextValues[layer.yConfig[0].fill][layer.layerId];
const nextValue = referenceLinesNextValues[layer.decorations[0].fill][layer.layerId];
return <ReferenceLine key={key} layer={layer} {...rest} nextValue={nextValue} />;
}

View file

@ -11,13 +11,23 @@ import { Position } from '@elastic/charts';
import { euiLightVars } from '@kbn/ui-theme';
import { FieldFormat } from '@kbn/field-formats-plugin/common';
import { groupBy, orderBy } from 'lodash';
import { IconPosition, ReferenceLineConfig, YAxisMode, FillStyle } from '../../../common/types';
import {
IconPosition,
ReferenceLineConfig,
FillStyle,
ExtendedReferenceLineDecorationConfig,
ReferenceLineDecorationConfig,
} from '../../../common/types';
import { FillStyles } from '../../../common/constants';
import {
GroupsConfiguration,
LINES_MARKER_SIZE,
mapVerticalToHorizontalPlacement,
Marker,
MarkerBody,
getAxisPosition,
getOriginalAxisPosition,
AxesMap,
} from '../../helpers';
import { ReferenceLineAnnotationConfig } from './reference_line_annotations';
@ -26,17 +36,18 @@ import { ReferenceLineAnnotationConfig } from './reference_line_annotations';
// this function assume the chart is vertical
export function getBaseIconPlacement(
iconPosition: IconPosition | undefined,
axesMap?: Record<string, unknown>,
axisMode?: YAxisMode
axesMap?: AxesMap,
position?: Position
) {
if (iconPosition === 'auto') {
if (axisMode === 'bottom') {
if (position === Position.Bottom) {
return Position.Top;
}
if (axesMap) {
if (axisMode === 'left') {
if (position === Position.Left) {
return axesMap.right ? Position.Left : Position.Right;
}
return axesMap.left ? Position.Right : Position.Left;
}
}
@ -67,22 +78,26 @@ export const getSharedStyle = (config: ReferenceLineAnnotationConfig) => ({
export const getLineAnnotationProps = (
config: ReferenceLineAnnotationConfig,
labels: { markerLabel?: string; markerBodyLabel?: string },
axesMap: Record<'left' | 'right', boolean>,
axesMap: AxesMap,
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
getOriginalAxisPosition(config.axisGroup?.position ?? Position.Bottom, isHorizontal)
);
// the padding map is built for vertical chart
const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE;
const markerPosition = isHorizontal
? mapVerticalToHorizontalPlacement(markerPositionVertical)
: markerPositionVertical;
return {
groupId,
groupId: config.axisGroup?.groupId || 'bottom',
marker: (
<Marker
config={config}
@ -94,22 +109,14 @@ export const getLineAnnotationProps = (
markerBody: (
<MarkerBody
label={labels.markerBodyLabel}
isHorizontal={
(!isHorizontal && config.axisMode === 'bottom') ||
(isHorizontal && config.axisMode !== 'bottom')
}
isHorizontal={markerPosition === Position.Bottom || markerPosition === Position.Top}
/>
),
// rotate the position if required
markerPosition: isHorizontal
? mapVerticalToHorizontalPlacement(markerPositionVertical)
: markerPositionVertical,
markerPosition,
};
};
export const getGroupId = (axisMode: YAxisMode | undefined) =>
axisMode === 'bottom' ? undefined : axisMode === 'right' ? 'right' : 'left';
export const getBottomRect = (
headerLabel: string | undefined,
isFillAbove: boolean,
@ -147,13 +154,16 @@ export const getHorizontalRect = (
const sortReferenceLinesByGroup = (referenceLines: ReferenceLineConfig[], group: FillStyle) => {
if (group === FillStyles.ABOVE || group === FillStyles.BELOW) {
const order = group === FillStyles.ABOVE ? 'asc' : 'desc';
return orderBy(referenceLines, ({ yConfig: [{ value }] }) => value, [order]);
return orderBy(referenceLines, ({ decorations: [{ value }] }) => value, [order]);
}
return referenceLines;
};
export const getNextValuesForReferenceLines = (referenceLines: ReferenceLineConfig[]) => {
const grouppedReferenceLines = groupBy(referenceLines, ({ yConfig: [yConfig] }) => yConfig.fill);
const grouppedReferenceLines = groupBy(
referenceLines,
({ decorations: [decorationConfig] }) => decorationConfig.fill
);
const groups = Object.keys(grouppedReferenceLines) as FillStyle[];
return groups.reduce<Record<FillStyle, Record<string, number | undefined>>>(
@ -164,8 +174,8 @@ export const getNextValuesForReferenceLines = (referenceLines: ReferenceLineConf
(nextValues, referenceLine, index, lines) => {
let nextValue: number | undefined;
if (index < lines.length - 1) {
const [yConfig] = lines[index + 1].yConfig;
nextValue = yConfig.value;
const [decorationConfig] = lines[index + 1].decorations;
nextValue = decorationConfig.value;
}
return { ...nextValues, [referenceLine.layerId]: nextValue };
@ -183,7 +193,7 @@ 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>,
axesMap: AxesMap,
isHorizontal: boolean
) => {
const result: Partial<Record<Position, number>> = {};
@ -210,5 +220,18 @@ export const computeChartMargins = (
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('top') : 'top';
result[placement] = referenceLinePaddings.top;
}
return result;
};
export function getAxisGroupForReferenceLine(
axesConfiguration: GroupsConfiguration,
decorationConfig: ReferenceLineDecorationConfig | ExtendedReferenceLineDecorationConfig,
shouldRotate: boolean
) {
return axesConfiguration.find(
(axis) =>
(decorationConfig.axisId && axis.groupId.includes(decorationConfig.axisId)) ||
getAxisPosition(decorationConfig.position ?? Position.Left, shouldRotate) === axis.position
);
}

View file

@ -155,6 +155,9 @@ describe('XYChart component', () => {
type: 'dataLayer',
layerType: LayerTypes.DATA,
seriesType: 'line',
isPercentage: false,
isHorizontal: false,
isStacked: false,
xAccessor: 'c',
accessors: ['a', 'b'],
showLines: true,
@ -255,6 +258,9 @@ describe('XYChart component', () => {
layerType: LayerTypes.DATA,
showLines: true,
seriesType: 'line',
isHorizontal: false,
isStacked: false,
isPercentage: false,
xAccessor: 'c',
accessors: ['a', 'b'],
splitAccessor: 'd',
@ -339,7 +345,8 @@ describe('XYChart component', () => {
test('it should enable the new time axis for a stacked vertical bar with break down dimension', () => {
const timeLayer: DataLayerConfig = {
...defaultTimeLayer,
seriesType: 'bar_stacked',
seriesType: 'bar',
isStacked: true,
};
const timeLayerArgs = createArgsWithLayers([timeLayer]);
@ -514,11 +521,14 @@ describe('XYChart component', () => {
isHistogram: true,
},
],
xExtent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 123,
upperBound: 456,
xAxisConfig: {
type: 'xAxisConfig',
extent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 123,
upperBound: 456,
},
},
}}
/>
@ -541,12 +551,18 @@ describe('XYChart component', () => {
{...defaultProps}
args={{
...args,
yLeftExtent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 123,
upperBound: 456,
},
yAxisConfigs: [
{
type: 'yAxisConfig',
position: 'left',
extent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 123,
upperBound: 456,
},
},
],
}}
/>
);
@ -564,10 +580,16 @@ describe('XYChart component', () => {
{...defaultProps}
args={{
...args,
yLeftExtent: {
type: 'axisExtentConfig',
mode: 'dataBounds',
},
yAxisConfigs: [
{
type: 'yAxisConfig',
position: 'left',
extent: {
type: 'axisExtentConfig',
mode: 'dataBounds',
},
},
],
}}
/>
);
@ -585,10 +607,16 @@ describe('XYChart component', () => {
{...defaultProps}
args={{
...args,
yLeftExtent: {
type: 'axisExtentConfig',
mode: 'dataBounds',
},
yAxisConfigs: [
{
type: 'yAxisConfig',
position: 'left',
extent: {
type: 'axisExtentConfig',
mode: 'dataBounds',
},
},
],
layers: [
{
...(args.layers[0] as DataLayerConfig),
@ -612,16 +640,16 @@ describe('XYChart component', () => {
{...defaultProps}
args={{
...args,
yLeftExtent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 123,
upperBound: 456,
},
layers: [
yAxisConfigs: [
{
...(args.layers[0] as DataLayerConfig),
seriesType: 'bar',
type: 'yAxisConfig',
position: 'left',
extent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 123,
upperBound: 456,
},
},
],
}}
@ -629,8 +657,8 @@ describe('XYChart component', () => {
);
expect(component.find(Axis).find('[id="left"]').prop('domain')).toEqual({
fit: false,
min: NaN,
max: NaN,
min: 123,
max: 456,
includeDataFromIds: [],
});
});
@ -901,7 +929,9 @@ describe('XYChart component', () => {
{...defaultProps}
args={{
...args,
layers: [{ ...(args.layers[0] as DataLayerConfig), seriesType: 'bar_horizontal' }],
layers: [
{ ...(args.layers[0] as DataLayerConfig), isHorizontal: true, seriesType: 'bar' },
],
}}
/>
);
@ -1020,7 +1050,10 @@ describe('XYChart component', () => {
xAccessor: 'xAccessorId',
xScaleType: 'linear',
isHistogram: true,
seriesType: 'bar_stacked',
isHorizontal: false,
isStacked: true,
seriesType: 'bar',
isPercentage: false,
accessors: ['yAccessorId'],
palette: mockPaletteOutput,
table: numberHistogramData,
@ -1091,8 +1124,11 @@ describe('XYChart component', () => {
type: 'dataLayer',
layerType: LayerTypes.DATA,
isHistogram: true,
seriesType: 'bar',
isStacked: true,
isHorizontal: false,
isPercentage: false,
showLines: true,
seriesType: 'bar_stacked',
xAccessor: 'b',
xScaleType: 'time',
splitAccessor: 'b',
@ -1218,7 +1254,10 @@ describe('XYChart component', () => {
xAccessor: 'xAccessorId',
xScaleType: 'linear',
isHistogram: true,
seriesType: 'bar_stacked',
isPercentage: false,
seriesType: 'bar',
isStacked: true,
isHorizontal: false,
accessors: ['yAccessorId'],
palette: mockPaletteOutput,
table: numberHistogramData,
@ -1291,6 +1330,9 @@ describe('XYChart component', () => {
type: 'dataLayer',
layerType: LayerTypes.DATA,
seriesType: 'line',
isHorizontal: false,
isStacked: false,
isPercentage: false,
showLines: true,
xAccessor: 'd',
accessors: ['a', 'b'],
@ -1351,6 +1393,9 @@ describe('XYChart component', () => {
layerType: LayerTypes.DATA,
showLines: true,
seriesType: 'line',
isHorizontal: false,
isStacked: false,
isPercentage: false,
xAccessor: 'd',
accessors: ['a', 'b'],
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
@ -1382,6 +1427,9 @@ describe('XYChart component', () => {
layerType: LayerTypes.DATA,
showLines: true,
seriesType: 'line',
isHorizontal: false,
isStacked: false,
isPercentage: false,
xAccessor: 'd',
accessors: ['a', 'b'],
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
@ -1421,7 +1469,7 @@ describe('XYChart component', () => {
{...defaultProps}
args={{
...args,
layers: [{ ...(args.layers[0] as DataLayerConfig), seriesType: 'bar_stacked' }],
layers: [{ ...(args.layers[0] as DataLayerConfig), seriesType: 'bar', isStacked: true }],
}}
/>
);
@ -1440,7 +1488,7 @@ describe('XYChart component', () => {
{...defaultProps}
args={{
...args,
layers: [{ ...(args.layers[0] as DataLayerConfig), seriesType: 'area_stacked' }],
layers: [{ ...(args.layers[0] as DataLayerConfig), seriesType: 'area', isStacked: true }],
}}
/>
);
@ -1460,7 +1508,12 @@ describe('XYChart component', () => {
args={{
...args,
layers: [
{ ...(args.layers[0] as DataLayerConfig), seriesType: 'bar_horizontal_stacked' },
{
...(args.layers[0] as DataLayerConfig),
seriesType: 'bar',
isStacked: true,
isHorizontal: true,
},
],
}}
/>
@ -1487,7 +1540,8 @@ describe('XYChart component', () => {
...(args.layers[0] as DataLayerConfig),
xAccessor: undefined,
splitAccessor: 'e',
seriesType: 'bar_stacked',
seriesType: 'bar',
isStacked: true,
},
],
}}
@ -1574,7 +1628,8 @@ describe('XYChart component', () => {
layers: [
{
...(args.layers[0] as DataLayerConfig),
seriesType: 'bar_stacked',
seriesType: 'bar',
isStacked: true,
isHistogram: true,
},
],
@ -1630,21 +1685,33 @@ describe('XYChart component', () => {
{
...layer,
accessors: ['a', 'b'],
yConfig: [
decorations: [
{
type: 'yConfig',
type: 'dataDecorationConfig',
forAccessor: 'a',
axisMode: 'left',
axisId: '1',
},
{
type: 'yConfig',
type: 'dataDecorationConfig',
forAccessor: 'b',
axisMode: 'right',
axisId: '2',
},
],
table: dataWithoutFormats,
},
],
yAxisConfigs: [
{
type: 'yAxisConfig',
id: '1',
position: 'left',
},
{
type: 'yAxisConfig',
id: '2',
position: 'right',
},
],
};
const component = getRenderedComponent(newArgs);
@ -1684,21 +1751,28 @@ describe('XYChart component', () => {
{
...layer,
accessors: ['c', 'd'],
yConfig: [
decorations: [
{
type: 'yConfig',
type: 'dataDecorationConfig',
forAccessor: 'c',
axisMode: 'left',
axisId: '1',
},
{
type: 'yConfig',
type: 'dataDecorationConfig',
forAccessor: 'd',
axisMode: 'left',
axisId: '1',
},
],
table: dataWithoutFormats,
},
],
yAxisConfigs: [
{
type: 'yAxisConfig',
id: '1',
position: 'left',
},
],
};
const component = getRenderedComponent(newArgs);
@ -1720,14 +1794,14 @@ describe('XYChart component', () => {
type: 'extendedDataLayer',
accessors: ['a', 'b'],
splitAccessor: undefined,
yConfig: [
decorations: [
{
type: 'yConfig',
type: 'dataDecorationConfig',
forAccessor: 'a',
color: '#550000',
},
{
type: 'yConfig',
type: 'dataDecorationConfig',
forAccessor: 'b',
color: '#FFFF00',
},
@ -1739,9 +1813,9 @@ describe('XYChart component', () => {
type: 'extendedDataLayer',
accessors: ['c'],
splitAccessor: undefined,
yConfig: [
decorations: [
{
type: 'yConfig',
type: 'dataDecorationConfig',
forAccessor: 'c',
color: '#FEECDF',
},
@ -1772,16 +1846,16 @@ describe('XYChart component', () => {
})
).toEqual('#FEECDF');
});
test('color is not applied to chart when splitAccessor is defined or when yConfig is not configured', () => {
test('color is not applied to chart when splitAccessor is defined or when decorations is not configured', () => {
const newArgs: XYProps = {
...args,
layers: [
{
...layer,
accessors: ['a'],
yConfig: [
decorations: [
{
type: 'yConfig',
type: 'dataDecorationConfig',
forAccessor: 'a',
color: '#550000',
},
@ -2067,7 +2141,14 @@ describe('XYChart component', () => {
{...defaultProps}
args={{
...args,
yLeftScale: 'sqrt',
yAxisConfigs: [
{
type: 'yAxisConfig',
position: 'left',
showLabels: true,
scaleType: 'sqrt',
},
],
}}
/>
);
@ -2115,11 +2196,24 @@ describe('XYChart component', () => {
test('it should set the tickLabel visibility on the x axis if the tick labels is hidden', () => {
const { args } = sampleArgs();
args.tickLabelsVisibilitySettings = {
x: false,
yLeft: true,
yRight: true,
type: 'tickLabelsConfig',
args.yAxisConfigs = [
{
type: 'yAxisConfig',
position: 'left',
showLabels: true,
},
{
type: 'yAxisConfig',
position: 'right',
showLabels: true,
},
];
args.xAxisConfig = {
type: 'xAxisConfig',
id: 'x',
showLabels: false,
position: 'bottom',
};
const instance = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2136,12 +2230,18 @@ describe('XYChart component', () => {
test('it should set the tickLabel visibility on the y axis if the tick labels is hidden', () => {
const { args } = sampleArgs();
args.tickLabelsVisibilitySettings = {
x: true,
yLeft: false,
yRight: false,
type: 'tickLabelsConfig',
};
args.yAxisConfigs = [
{
type: 'yAxisConfig',
position: 'left',
showLabels: false,
},
{
type: 'yAxisConfig',
position: 'right',
showLabels: false,
},
];
const instance = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2157,11 +2257,24 @@ describe('XYChart component', () => {
test('it should set the tickLabel visibility on the x axis if the tick labels is shown', () => {
const { args } = sampleArgs();
args.tickLabelsVisibilitySettings = {
x: true,
yLeft: true,
yRight: true,
type: 'tickLabelsConfig',
args.yAxisConfigs = [
{
type: 'yAxisConfig',
position: 'left',
showLabels: true,
},
{
type: 'yAxisConfig',
position: 'right',
showLabels: true,
},
];
args.xAxisConfig = {
type: 'xAxisConfig',
id: 'x',
showLabels: true,
position: 'bottom',
};
const instance = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2178,11 +2291,25 @@ describe('XYChart component', () => {
test('it should set the tickLabel orientation on the x axis', () => {
const { args } = sampleArgs();
args.labelsOrientation = {
x: -45,
yLeft: 0,
yRight: -90,
type: 'labelsOrientationConfig',
args.yAxisConfigs = [
{
type: 'yAxisConfig',
position: 'left',
labelsOrientation: 0,
},
{
type: 'yAxisConfig',
position: 'right',
labelsOrientation: -90,
},
];
args.xAxisConfig = {
type: 'xAxisConfig',
id: 'x',
showLabels: true,
labelsOrientation: -45,
position: 'bottom',
};
const instance = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2199,11 +2326,24 @@ describe('XYChart component', () => {
test('it should set the tickLabel visibility on the y axis if the tick labels is shown', () => {
const { args } = sampleArgs();
args.tickLabelsVisibilitySettings = {
x: false,
yLeft: true,
yRight: true,
type: 'tickLabelsConfig',
args.yAxisConfigs = [
{
type: 'yAxisConfig',
position: 'left',
showLabels: true,
},
{
type: 'yAxisConfig',
position: 'right',
showLabels: true,
},
];
args.xAxisConfig = {
type: 'xAxisConfig',
id: 'x',
showLabels: true,
position: 'bottom',
};
const instance = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2220,11 +2360,25 @@ describe('XYChart component', () => {
test('it should set the tickLabel orientation on the y axis', () => {
const { args } = sampleArgs();
args.labelsOrientation = {
x: -45,
yLeft: -90,
yRight: -90,
type: 'labelsOrientationConfig',
args.yAxisConfigs = [
{
type: 'yAxisConfig',
position: 'left',
labelsOrientation: -90,
},
{
type: 'yAxisConfig',
position: 'right',
labelsOrientation: -90,
},
];
args.xAxisConfig = {
type: 'xAxisConfig',
id: 'x',
showLabels: true,
labelsOrientation: -45,
position: 'bottom',
};
const instance = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2266,43 +2420,47 @@ describe('XYChart component', () => {
};
const args: XYProps = {
xTitle: '',
yTitle: '',
yRightTitle: '',
yLeftScale: 'linear',
yRightScale: 'linear',
showTooltip: true,
legend: { type: 'legendConfig', isVisible: false, position: Position.Top },
valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'tickLabelsConfig',
x: true,
yLeft: true,
yRight: true,
},
gridlinesVisibilitySettings: {
type: 'gridlinesConfig',
x: true,
yLeft: false,
yRight: false,
},
labelsOrientation: {
type: 'labelsOrientationConfig',
x: 0,
yLeft: 0,
yRight: 0,
},
xExtent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
yLeftExtent: {
mode: 'full',
type: 'axisExtentConfig',
},
yRightExtent: {
mode: 'full',
type: 'axisExtentConfig',
yAxisConfigs: [
{
type: 'yAxisConfig',
position: 'left',
labelsOrientation: 0,
showGridLines: false,
showLabels: true,
title: '',
extent: {
mode: 'full',
type: 'axisExtentConfig',
},
},
{
type: 'yAxisConfig',
position: 'right',
labelsOrientation: 0,
showGridLines: false,
showLabels: true,
title: '',
extent: {
mode: 'full',
type: 'axisExtentConfig',
},
},
],
xAxisConfig: {
type: 'xAxisConfig',
id: 'x',
title: '',
showLabels: true,
showGridLines: true,
labelsOrientation: 0,
position: 'bottom',
extent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
},
markSizeRatio: 1,
layers: [
@ -2311,6 +2469,8 @@ describe('XYChart component', () => {
type: 'dataLayer',
layerType: LayerTypes.DATA,
seriesType: 'line',
isStacked: false,
isHorizontal: false,
showLines: true,
xAccessor: 'a',
accessors: ['c'],
@ -2318,6 +2478,7 @@ describe('XYChart component', () => {
columnToLabel: '',
xScaleType: 'ordinal',
isHistogram: false,
isPercentage: false,
palette: mockPaletteOutput,
table: data1,
},
@ -2333,6 +2494,9 @@ describe('XYChart component', () => {
columnToLabel: '',
xScaleType: 'ordinal',
isHistogram: false,
isStacked: false,
isHorizontal: false,
isPercentage: false,
palette: mockPaletteOutput,
table: data2,
},
@ -2363,45 +2527,51 @@ describe('XYChart component', () => {
};
const args: XYProps = {
xTitle: '',
yTitle: '',
yRightTitle: '',
legend: { type: 'legendConfig', isVisible: false, position: Position.Top },
valueLabels: 'hide',
yAxisConfigs: [
{
type: 'yAxisConfig',
position: 'left',
labelsOrientation: 0,
showGridLines: false,
showLabels: false,
title: '',
scaleType: 'linear',
extent: {
mode: 'full',
type: 'axisExtentConfig',
},
},
{
type: 'yAxisConfig',
position: 'right',
labelsOrientation: 0,
showGridLines: false,
showLabels: false,
scaleType: 'linear',
title: '',
extent: {
mode: 'full',
type: 'axisExtentConfig',
},
},
],
xAxisConfig: {
type: 'xAxisConfig',
id: 'x',
title: '',
showLabels: true,
showGridLines: true,
labelsOrientation: 0,
position: 'bottom',
extent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
},
showTooltip: true,
tickLabelsVisibilitySettings: {
type: 'tickLabelsConfig',
x: true,
yLeft: false,
yRight: false,
},
gridlinesVisibilitySettings: {
type: 'gridlinesConfig',
x: true,
yLeft: false,
yRight: false,
},
labelsOrientation: {
type: 'labelsOrientationConfig',
x: 0,
yLeft: 0,
yRight: 0,
},
xExtent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
yLeftExtent: {
mode: 'full',
type: 'axisExtentConfig',
},
yRightExtent: {
mode: 'full',
type: 'axisExtentConfig',
},
markSizeRatio: 1,
yLeftScale: 'linear',
yRightScale: 'linear',
layers: [
{
layerId: 'first',
@ -2415,6 +2585,9 @@ describe('XYChart component', () => {
columnToLabel: '',
xScaleType: 'ordinal',
isHistogram: false,
isStacked: false,
isHorizontal: false,
isPercentage: false,
palette: mockPaletteOutput,
table: data,
},
@ -2443,45 +2616,51 @@ describe('XYChart component', () => {
};
const args: XYProps = {
xTitle: '',
yTitle: '',
yRightTitle: '',
showTooltip: true,
legend: { type: 'legendConfig', isVisible: true, position: Position.Top },
valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'tickLabelsConfig',
x: true,
yLeft: false,
yRight: false,
},
gridlinesVisibilitySettings: {
type: 'gridlinesConfig',
x: true,
yLeft: false,
yRight: false,
},
labelsOrientation: {
type: 'labelsOrientationConfig',
x: 0,
yLeft: 0,
yRight: 0,
},
xExtent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
yLeftExtent: {
mode: 'full',
type: 'axisExtentConfig',
},
yRightExtent: {
mode: 'full',
type: 'axisExtentConfig',
yAxisConfigs: [
{
type: 'yAxisConfig',
position: 'left',
labelsOrientation: 0,
showGridLines: false,
showLabels: false,
title: '',
scaleType: 'linear',
extent: {
mode: 'full',
type: 'axisExtentConfig',
},
},
{
type: 'yAxisConfig',
position: 'right',
labelsOrientation: 0,
showGridLines: false,
showLabels: false,
scaleType: 'linear',
title: '',
extent: {
mode: 'full',
type: 'axisExtentConfig',
},
},
],
xAxisConfig: {
type: 'xAxisConfig',
id: 'x',
showLabels: true,
showGridLines: true,
labelsOrientation: 0,
title: '',
position: 'bottom',
extent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
},
markSizeRatio: 1,
yLeftScale: 'linear',
yRightScale: 'linear',
layers: [
{
layerId: 'first',
@ -2495,6 +2674,9 @@ describe('XYChart component', () => {
columnToLabel: '',
xScaleType: 'ordinal',
isHistogram: false,
isStacked: false,
isHorizontal: false,
isPercentage: false,
palette: mockPaletteOutput,
table: data,
},
@ -2631,7 +2813,7 @@ describe('XYChart component', () => {
{ ...sampleLayer, accessors: ['a'], table: data },
{ ...sampleLayer, seriesType: 'bar', accessors: ['a'], table: data },
{ ...sampleLayer, seriesType: 'area', accessors: ['a'], table: data },
{ ...sampleLayer, seriesType: 'area_stacked', accessors: ['a'], table: data },
{ ...sampleLayer, seriesType: 'area', isStacked: true, accessors: ['a'], table: data },
]);
const component = shallow(
@ -2661,7 +2843,13 @@ describe('XYChart component', () => {
test('it should apply the xTitle if is specified', () => {
const { args } = sampleArgs();
args.xTitle = 'My custom x-axis title';
args.xAxisConfig = {
type: 'xAxisConfig',
id: 'x',
showLabels: true,
title: 'My custom x-axis title',
position: 'bottom',
};
const component = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2671,11 +2859,26 @@ describe('XYChart component', () => {
test('it should hide the X axis title if the corresponding switch is off', () => {
const { args } = sampleArgs();
args.axisTitlesVisibilitySettings = {
x: false,
yLeft: true,
yRight: true,
type: 'axisTitlesVisibilityConfig',
args.yAxisConfigs = [
{
type: 'yAxisConfig',
position: 'left',
showTitle: true,
},
{
type: 'yAxisConfig',
position: 'right',
showTitle: true,
},
];
args.xAxisConfig = {
type: 'xAxisConfig',
id: 'x',
showLabels: true,
showTitle: false,
title: 'My custom x-axis title',
position: 'bottom',
};
const component = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2692,11 +2895,26 @@ describe('XYChart component', () => {
test('it should show the X axis gridlines if the setting is on', () => {
const { args } = sampleArgs();
args.gridlinesVisibilitySettings = {
x: true,
yLeft: false,
yRight: false,
type: 'gridlinesConfig',
args.yAxisConfigs = [
{
type: 'yAxisConfig',
position: 'left',
showGridLines: false,
},
{
type: 'yAxisConfig',
position: 'right',
showGridLines: false,
},
];
args.xAxisConfig = {
type: 'xAxisConfig',
id: 'x',
showLabels: true,
showGridLines: true,
title: 'My custom x-axis title',
position: 'bottom',
};
const component = shallow(<XYChart {...defaultProps} args={{ ...args }} />);
@ -2741,10 +2959,13 @@ describe('XYChart component', () => {
layerType: LayerTypes.DATA,
showLines: true,
seriesType: 'line',
isStacked: false,
isHorizontal: false,
xAccessor: 'c',
accessors: ['a', 'b'],
xScaleType: 'ordinal',
isHistogram: false,
isPercentage: false,
palette: mockPaletteOutput,
table: data,
};

View file

@ -47,20 +47,21 @@ import type { FilterEvent, BrushEvent, FormatFactory } from '../types';
import { isTimeChart } from '../../common/helpers';
import type {
CommonXYDataLayerConfig,
ExtendedYConfig,
ReferenceLineYConfig,
SeriesType,
ReferenceLineDecorationConfig,
ExtendedReferenceLineDecorationConfig,
XYChartProps,
AxisExtentConfigResult,
} from '../../common/types';
import {
isHorizontalChart,
getAnnotationsLayers,
getDataLayers,
Series,
AxisConfiguration,
getAxisPosition,
getFormattedTablesByLayers,
getLayersFormats,
getLayersTitles,
isReferenceLineYConfig,
isReferenceLineDecorationConfig,
getFilteredLayers,
getReferenceLayers,
isDataLayer,
@ -68,10 +69,16 @@ import {
GroupsConfiguration,
getLinesCausedPaddings,
validateExtent,
Series,
getOriginalAxisPosition,
} from '../helpers';
import { getXDomain, XyEndzones } from './x_domain';
import { getLegendAction } from './legend_action';
import { ReferenceLines, computeChartMargins } from './reference_lines';
import {
ReferenceLines,
computeChartMargins,
getAxisGroupForReferenceLine,
} from './reference_lines';
import { visualizationDefinitions } from '../definitions';
import { CommonXYLayerConfig } from '../../common/types';
import { SplitChart } from './split_chart';
@ -138,8 +145,16 @@ function getValueLabelsStyling(isHorizontal: boolean): {
};
}
function getIconForSeriesType(seriesType: SeriesType): IconType {
return visualizationDefinitions.find((c) => c.id === seriesType)!.icon || 'empty';
function getIconForSeriesType(layer: CommonXYDataLayerConfig): IconType {
return (
visualizationDefinitions.find(
(c) =>
c.id ===
`${layer.seriesType}${layer.isHorizontal ? '_horizontal' : ''}${
layer.isPercentage ? '_percentage' : ''
}${layer.isStacked ? '_stacked' : ''}`
)!.icon || 'empty'
);
}
export const XYChartReportable = React.memo(XYChart);
@ -166,15 +181,11 @@ export function XYChart({
fittingFunction,
endValue,
emphasizeFitting,
gridlinesVisibilitySettings,
valueLabels,
hideEndzones,
xExtent,
yLeftExtent,
yRightExtent,
valuesInLegend,
yLeftScale,
yRightScale,
yAxisConfigs,
xAxisConfig,
splitColumnAccessor,
splitRowAccessor,
} = args;
@ -204,9 +215,7 @@ export function XYChart({
);
if (dataLayers.length === 0) {
const icon: IconType = getIconForSeriesType(
getDataLayers(layers)?.[0]?.seriesType || SeriesTypes.BAR
);
const icon: IconType = getIconForSeriesType(getDataLayers(layers)?.[0]);
return <EmptyPlaceholder className="xyChart__empty" icon={icon} />;
}
@ -236,37 +245,35 @@ export function XYChart({
shouldRotate,
formatFactory,
fieldFormats,
yLeftScale,
yRightScale
yAxisConfigs
);
const xTitle = args.xTitle || (xAxisColumn && xAxisColumn.name);
const axesConfiguration = getAxesConfiguration(
dataLayers,
shouldRotate,
formatFactory,
fieldFormats,
[...(yAxisConfigs ?? []), ...(xAxisConfig ? [xAxisConfig] : [])]
);
const xTitle = xAxisConfig?.title || (xAxisColumn && xAxisColumn.name);
const yAxesMap = {
left: yAxesConfiguration.find(({ groupId }) => groupId === 'left'),
right: yAxesConfiguration.find(({ groupId }) => groupId === 'right'),
left: yAxesConfiguration.find(
({ position }) => position === getAxisPosition(Position.Left, shouldRotate)
),
right: yAxesConfiguration.find(
({ position }) => position === getAxisPosition(Position.Right, shouldRotate)
),
};
const titles = getLayersTitles(
dataLayers,
{ splitColumnAccessor, splitRowAccessor },
{ xTitle: args.xTitle, yTitle: args.yTitle, yRightTitle: args.yRightTitle },
{ xTitle },
yAxesConfiguration
);
const axisTitlesVisibilitySettings = args.axisTitlesVisibilitySettings || {
x: true,
yLeft: true,
yRight: true,
};
const tickLabelsVisibilitySettings = args.tickLabelsVisibilitySettings || {
x: true,
yLeft: true,
yRight: true,
};
const labelsOrientation = args.labelsOrientation || { x: 0, yLeft: 0, yRight: 0 };
const filteredBarLayers = dataLayers.filter((layer) => layer.seriesType.includes('bar'));
const filteredBarLayers = dataLayers.filter(({ seriesType }) => seriesType === SeriesTypes.BAR);
const chartHasMoreThanOneBarSeries =
filteredBarLayers.length > 1 ||
@ -285,9 +292,18 @@ export function XYChart({
minInterval,
isTimeViz,
isHistogramViz,
xExtent
xAxisConfig?.extent
);
const axisTitlesVisibilitySettings = {
yLeft: yAxesMap?.left?.showTitle ?? true,
yRight: yAxesMap?.right?.showTitle ?? true,
};
const tickLabelsVisibilitySettings = {
yLeft: yAxesMap?.left?.showLabels ?? true,
yRight: yAxesMap?.right?.showLabels ?? true,
};
const getYAxesTitles = (axisSeries: Series[]) => {
return axisSeries
.map(({ layer, accessor }) => titles?.[layer]?.yTitles?.[accessor])
@ -312,45 +328,48 @@ export function XYChart({
const rangeAnnotations = getRangeAnnotations(annotationsLayers);
const visualConfigs = [
...referenceLineLayers.flatMap<ExtendedYConfig | ReferenceLineYConfig | undefined>(
({ yConfig }) => yConfig
),
...referenceLineLayers
.flatMap<ReferenceLineDecorationConfig | ExtendedReferenceLineDecorationConfig | undefined>(
({ decorations }) => decorations
)
.map((config) => ({
...config,
position: config
? getAxisGroupForReferenceLine(axesConfiguration, config, shouldRotate)?.position ??
Position.Left
: Position.Bottom,
})),
...groupedLineAnnotations,
].filter(Boolean);
const shouldHideDetails = annotationsLayers.length > 0 ? annotationsLayers[0].hide : false;
const linesPaddings = !shouldHideDetails ? getLinesCausedPaddings(visualConfigs, yAxesMap) : {};
const linesPaddings = !shouldHideDetails
? getLinesCausedPaddings(visualConfigs, yAxesMap, shouldRotate)
: {};
const getYAxesStyle = (groupId: 'left' | 'right') => {
const tickVisible =
groupId === 'right'
? tickLabelsVisibilitySettings?.yRight
: tickLabelsVisibilitySettings?.yLeft;
const getYAxesStyle = (axis: AxisConfiguration) => {
const tickVisible = axis.showLabels;
const position = getOriginalAxisPosition(axis.position, shouldRotate);
const style = {
tickLabel: {
fill: axis.labelColor,
visible: tickVisible,
rotation:
groupId === 'right'
? args.labelsOrientation?.yRight || 0
: args.labelsOrientation?.yLeft || 0,
rotation: axis.labelsOrientation,
padding:
linesPaddings[groupId] != null
linesPaddings[position] != null
? {
inner: linesPaddings[groupId],
inner: linesPaddings[position],
}
: undefined,
},
axisTitle: {
visible:
groupId === 'right'
? axisTitlesVisibilitySettings?.yRight
: axisTitlesVisibilitySettings?.yLeft,
visible: axis.showTitle,
// if labels are not visible add the padding to the title
padding:
!tickVisible && linesPaddings[groupId] != null
!tickVisible && linesPaddings[position] != null
? {
inner: linesPaddings[groupId],
inner: linesPaddings[position],
}
: undefined,
},
@ -359,7 +378,10 @@ export function XYChart({
};
const getYAxisDomain = (axis: GroupsConfiguration[number]) => {
const extent = axis.groupId === 'left' ? yLeftExtent : yRightExtent;
const extent: AxisExtentConfigResult = axis.extent || {
type: 'axisExtentConfig',
mode: 'full',
};
const hasBarOrArea = Boolean(
axis.series.some((series) => {
const layer = layersById[series.layer];
@ -367,11 +389,12 @@ export function XYChart({
return false;
}
return layer.seriesType.includes('bar') || layer.seriesType.includes('area');
return layer.seriesType === SeriesTypes.BAR || layer.seriesType === SeriesTypes.AREA;
})
);
const fit = !hasBarOrArea && extent.mode === AxisExtentModes.DATA_BOUNDS;
const padding = axis.boundsMargin || undefined;
let min: number = NaN;
let max: number = NaN;
@ -387,22 +410,31 @@ export function XYChart({
fit,
min,
max,
padding,
includeDataFromIds: referenceLineLayers
.flatMap((l) =>
l.yConfig ? l.yConfig.map((yConfig) => ({ layerId: l.layerId, yConfig })) : []
.flatMap(
(l) => l.decorations?.map((decoration) => ({ layerId: l.layerId, decoration })) || []
)
.filter(({ yConfig }) => yConfig.axisMode === axis.groupId)
.map(({ layerId, yConfig }) =>
isReferenceLineYConfig(yConfig)
? `${layerId}-${yConfig.value}-${yConfig.fill !== 'none' ? 'rect' : 'line'}`
: `${layerId}-${yConfig.forAccessor}-${yConfig.fill !== 'none' ? 'rect' : 'line'}`
.filter(({ decoration }) => {
if (decoration.axisId) {
return axis.groupId.includes(decoration.axisId);
}
return (
axis.position === getAxisPosition(decoration.position ?? Position.Left, shouldRotate)
);
})
.map(({ layerId, decoration }) =>
isReferenceLineDecorationConfig(decoration)
? `${layerId}-${decoration.value}-${decoration.fill !== 'none' ? 'rect' : 'line'}`
: `${layerId}-${decoration.forAccessor}-${decoration.fill !== 'none' ? 'rect' : 'line'}`
),
};
};
const shouldShowValueLabels =
// No stacked bar charts
dataLayers.every((layer) => !layer.seriesType.includes('stacked')) &&
dataLayers.every((layer) => !layer.isStacked) &&
// No histogram charts
!isHistogramViz;
@ -516,18 +548,17 @@ export function XYChart({
};
const isHistogramModeEnabled = dataLayers.some(
({ isHistogram, seriesType }) =>
isHistogram &&
(seriesType.includes('stacked') ||
!seriesType.includes('bar') ||
!chartHasMoreThanOneBarSeries)
({ isHistogram, seriesType, isStacked }) =>
isHistogram && (isStacked || seriesType !== SeriesTypes.BAR || !chartHasMoreThanOneBarSeries)
);
const shouldUseNewTimeAxis =
isTimeViz && isHistogramModeEnabled && !useLegacyTimeAxis && !shouldRotate;
const defaultXAxisPosition = shouldRotate ? Position.Left : Position.Bottom;
const gridLineStyle = {
visible: gridlinesVisibilitySettings?.x,
visible: xAxisConfig?.showGridLines,
strokeWidth: 1,
};
const xAxisStyle: RecursivePartial<AxisStyle> = shouldUseNewTimeAxis
@ -535,26 +566,28 @@ export function XYChart({
...MULTILAYER_TIME_AXIS_STYLE,
tickLabel: {
...MULTILAYER_TIME_AXIS_STYLE.tickLabel,
visible: Boolean(tickLabelsVisibilitySettings?.x),
visible: Boolean(xAxisConfig?.showLabels),
fill: xAxisConfig?.labelColor,
},
tickLine: {
...MULTILAYER_TIME_AXIS_STYLE.tickLine,
visible: Boolean(tickLabelsVisibilitySettings?.x),
visible: Boolean(xAxisConfig?.showLabels),
},
axisTitle: {
visible: axisTitlesVisibilitySettings.x,
visible: xAxisConfig?.showTitle,
},
}
: {
tickLabel: {
visible: tickLabelsVisibilitySettings?.x,
rotation: labelsOrientation?.x,
visible: xAxisConfig?.showLabels,
rotation: xAxisConfig?.labelsOrientation,
padding: linesPaddings.bottom != null ? { inner: linesPaddings.bottom } : undefined,
fill: xAxisConfig?.labelColor,
},
axisTitle: {
visible: axisTitlesVisibilitySettings.x,
visible: xAxisConfig?.showTitle,
padding:
!tickLabelsVisibilitySettings?.x && linesPaddings.bottom != null
!xAxisConfig?.showLabels && linesPaddings.bottom != null
? { inner: linesPaddings.bottom }
: undefined,
},
@ -610,8 +643,8 @@ export function XYChart({
...chartTheme.chartPaddings,
...computeChartMargins(
linesPaddings,
tickLabelsVisibilitySettings,
axisTitlesVisibilitySettings,
{ ...tickLabelsVisibilitySettings, x: xAxisConfig?.showLabels },
{ ...axisTitlesVisibilitySettings, x: xAxisConfig?.showTitle },
yAxesMap,
shouldRotate
),
@ -678,12 +711,24 @@ export function XYChart({
<Axis
id="x"
position={shouldRotate ? Position.Left : Position.Bottom}
position={
xAxisConfig?.position
? getOriginalAxisPosition(xAxisConfig?.position, shouldRotate)
: defaultXAxisPosition
}
title={xTitle}
gridLine={gridLineStyle}
hide={dataLayers[0]?.hide || !dataLayers[0]?.xAccessor}
tickFormat={(d) => safeXAccessorLabelRenderer(d)}
hide={xAxisConfig?.hide || dataLayers[0]?.hide || !dataLayers[0]?.xAccessor}
tickFormat={(d) => {
let value = safeXAccessorLabelRenderer(d) || '';
if (xAxisConfig?.truncate && value.length > xAxisConfig.truncate) {
value = `${value.slice(0, xAxisConfig.truncate)}...`;
}
return value;
}}
style={xAxisStyle}
showOverlappingLabels={xAxisConfig?.showOverlappingLabels}
showDuplicatedTicks={xAxisConfig?.showDuplicates}
timeAxisLayerCount={shouldUseNewTimeAxis ? 3 : 0}
/>
{isSplitChart && splitTable && (
@ -704,15 +749,20 @@ export function XYChart({
position={axis.position}
title={getYAxesTitles(axis.series)}
gridLine={{
visible:
axis.groupId === 'right'
? gridlinesVisibilitySettings?.yRight
: gridlinesVisibilitySettings?.yLeft,
visible: axis.showGridLines,
}}
hide={dataLayers[0]?.hide}
tickFormat={(d) => axis.formatter?.convert(d) || ''}
style={getYAxesStyle(axis.groupId)}
hide={axis.hide || dataLayers[0]?.hide}
tickFormat={(d) => {
let value = axis.formatter?.convert(d) || '';
if (axis.truncate && value.length > axis.truncate) {
value = `${value.slice(0, axis.truncate)}...`;
}
return value;
}}
style={getYAxesStyle(axis)}
domain={getYAxisDomain(axis)}
showOverlappingLabels={axis.showOverlappingLabels}
showDuplicatedTicks={axis.showDuplicates}
ticks={5}
/>
);
@ -726,9 +776,9 @@ export function XYChart({
histogramMode={dataLayers.every(
(layer) =>
layer.isHistogram &&
(layer.seriesType.includes('stacked') || !layer.splitAccessor) &&
(layer.seriesType.includes('stacked') ||
!layer.seriesType.includes('bar') ||
(layer.isStacked || !layer.splitAccessor) &&
(layer.isStacked ||
layer.seriesType !== SeriesTypes.BAR ||
!chartHasMoreThanOneBarSeries)
)}
/>
@ -758,18 +808,12 @@ export function XYChart({
{referenceLineLayers.length ? (
<ReferenceLines
layers={referenceLineLayers}
formatters={{
left: yAxesMap.left?.formatter,
right: yAxesMap.right?.formatter,
bottom: xAxisFormatter,
}}
axesMap={{
left: Boolean(yAxesMap.left),
right: Boolean(yAxesMap.right),
}}
xAxisFormatter={xAxisFormatter}
axesConfiguration={axesConfiguration}
isHorizontal={shouldRotate}
paddingMap={linesPaddings}
titles={titles}
yAxesMap={yAxesMap}
/>
) : null}
{rangeAnnotations.length || groupedLineAnnotations.length ? (

View file

@ -22,13 +22,13 @@ import {
export const visualizationDefinitions = [
{ id: SeriesTypes.BAR, icon: BarIcon },
{ id: SeriesTypes.BAR_STACKED, icon: BarStackedIcon },
{ id: SeriesTypes.BAR_HORIZONTAL, icon: BarHorizontalIcon },
{ id: SeriesTypes.BAR_PERCENTAGE_STACKED, icon: BarPercentageIcon },
{ id: SeriesTypes.BAR_HORIZONTAL_STACKED, icon: BarHorizontalStackedIcon },
{ id: SeriesTypes.BAR_HORIZONTAL_PERCENTAGE_STACKED, icon: BarHorizontalPercentageIcon },
{ id: `${SeriesTypes.BAR}_stacked`, icon: BarStackedIcon },
{ id: `${SeriesTypes.BAR}_horizontal`, icon: BarHorizontalIcon },
{ id: `${SeriesTypes.BAR}_percentage_stacked`, icon: BarPercentageIcon },
{ id: `${SeriesTypes.BAR}_horizontal_stacked`, icon: BarHorizontalStackedIcon },
{ id: `${SeriesTypes.BAR}_horizontal_percentage_stacked`, icon: BarHorizontalPercentageIcon },
{ id: SeriesTypes.LINE, icon: LineIcon },
{ id: SeriesTypes.AREA, icon: AreaIcon },
{ id: SeriesTypes.AREA_STACKED, icon: AreaStackedIcon },
{ id: SeriesTypes.AREA_PERCENTAGE_STACKED, icon: AreaPercentageIcon },
{ id: `${SeriesTypes.AREA}_stacked`, icon: AreaStackedIcon },
{ id: `${SeriesTypes.AREA}_percentage_stacked`, icon: AreaPercentageIcon },
];

View file

@ -11,31 +11,34 @@ import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText } from '@elastic/eui';
import classnames from 'classnames';
import type {
IconPosition,
YAxisMode,
ExtendedYConfig,
ReferenceLineDecorationConfig,
CollectiveConfig,
} from '../../common/types';
import { getBaseIconPlacement } from '../components';
import { hasIcon, iconSet } from './icon';
import { AxesMap, getOriginalAxisPosition } from './axes_configuration';
export const LINES_MARKER_SIZE = 20;
type PartialExtendedYConfig = Pick<
ExtendedYConfig,
'axisMode' | 'icon' | 'iconPosition' | 'textVisibility'
>;
type PartialReferenceLineDecorationConfig = Pick<
ReferenceLineDecorationConfig,
'icon' | 'iconPosition' | 'textVisibility'
> & {
position?: Position;
};
type PartialCollectiveConfig = Pick<CollectiveConfig, 'axisMode' | 'icon' | 'textVisibility'>;
type PartialCollectiveConfig = Pick<CollectiveConfig, 'position' | 'icon' | 'textVisibility'>;
const isExtendedYConfig = (
config: PartialExtendedYConfig | PartialCollectiveConfig | undefined
): config is PartialExtendedYConfig =>
(config as PartialExtendedYConfig)?.iconPosition ? true : false;
const isExtendedDecorationConfig = (
config: PartialReferenceLineDecorationConfig | PartialCollectiveConfig | undefined
): config is PartialReferenceLineDecorationConfig =>
(config as PartialReferenceLineDecorationConfig)?.iconPosition ? true : false;
// Note: it does not take into consideration whether the reference line is in view or not
export const getLinesCausedPaddings = (
visualConfigs: Array<PartialExtendedYConfig | PartialCollectiveConfig | undefined>,
axesMap: Record<'left' | 'right', unknown>
visualConfigs: Array<PartialReferenceLineDecorationConfig | PartialCollectiveConfig | undefined>,
axesMap: AxesMap,
shouldRotate: boolean
) => {
// collect all paddings for the 4 axis: if any text is detected double it.
const paddings: Partial<Record<Position, number>> = {};
@ -44,11 +47,15 @@ export const getLinesCausedPaddings = (
if (!config) {
return;
}
const { axisMode, icon, textVisibility } = config;
const iconPosition = isExtendedYConfig(config) ? config.iconPosition : undefined;
const { position, icon, textVisibility } = config;
const iconPosition = isExtendedDecorationConfig(config) ? config.iconPosition : undefined;
if (axisMode && (hasIcon(icon) || textVisibility)) {
const placement = getBaseIconPlacement(iconPosition, axesMap, axisMode);
if (position && (hasIcon(icon) || textVisibility)) {
const placement = getBaseIconPlacement(
iconPosition,
axesMap,
getOriginalAxisPosition(position, shouldRotate)
);
paddings[placement] = Math.max(
paddings[placement] || 0,
LINES_MARKER_SIZE * (textVisibility ? 2 : 1) // double the padding size if there's text
@ -174,7 +181,7 @@ export const AnnotationIcon = ({
};
interface MarkerConfig {
axisMode?: YAxisMode;
position?: Position;
icon?: string;
textVisibility?: boolean;
iconPosition?: IconPosition;

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { DataLayerConfig } from '../../common';
import { DataLayerConfig, YAxisConfigResult } from '../../common';
import { LayerTypes } from '../../common/constants';
import { Datatable } from '@kbn/expressions-plugin/public';
import { getAxesConfiguration } from './axes_configuration';
@ -221,6 +221,14 @@ describe('axes_configuration', () => {
},
};
const yAxisConfigs: YAxisConfigResult[] = [
{
id: '1',
position: 'right',
type: 'yAxisConfig',
},
];
const sampleLayer: DataLayerConfig = {
layerId: 'first',
type: 'dataLayer',
@ -233,6 +241,9 @@ describe('axes_configuration', () => {
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
xScaleType: 'ordinal',
isHistogram: false,
isPercentage: false,
isStacked: false,
isHorizontal: false,
palette: { type: 'palette', name: 'default' },
table: tables.first,
};
@ -253,7 +264,7 @@ describe('axes_configuration', () => {
it('should map auto series to left axis', () => {
const formatFactory = jest.fn();
const groups = getAxesConfiguration([sampleLayer], false, formatFactory, fieldFormats);
const groups = getAxesConfiguration([sampleLayer], false, formatFactory, fieldFormats, []);
expect(groups.length).toEqual(1);
expect(groups[0].position).toEqual('left');
expect(groups[0].series[0].accessor).toEqual('yAccessorId');
@ -263,7 +274,7 @@ describe('axes_configuration', () => {
it('should map auto series to right axis if formatters do not match', () => {
const formatFactory = jest.fn();
const twoSeriesLayer = { ...sampleLayer, accessors: ['yAccessorId', 'yAccessorId2'] };
const groups = getAxesConfiguration([twoSeriesLayer], false, formatFactory, fieldFormats);
const groups = getAxesConfiguration([twoSeriesLayer], false, formatFactory, fieldFormats, []);
expect(groups.length).toEqual(2);
expect(groups[0].position).toEqual('left');
expect(groups[1].position).toEqual('right');
@ -277,7 +288,7 @@ describe('axes_configuration', () => {
...sampleLayer,
accessors: ['yAccessorId', 'yAccessorId2', 'yAccessorId3'],
};
const groups = getAxesConfiguration([threeSeriesLayer], false, formatFactory, fieldFormats);
const groups = getAxesConfiguration([threeSeriesLayer], false, formatFactory, fieldFormats, []);
expect(groups.length).toEqual(2);
expect(groups[0].position).toEqual('left');
expect(groups[1].position).toEqual('right');
@ -292,12 +303,13 @@ describe('axes_configuration', () => {
[
{
...sampleLayer,
yConfig: [{ type: 'yConfig', forAccessor: 'yAccessorId', axisMode: 'right' }],
decorations: [{ type: 'dataDecorationConfig', forAccessor: 'yAccessorId', axisId: '1' }],
},
],
false,
formatFactory,
fieldFormats
fieldFormats,
yAxisConfigs
);
expect(groups.length).toEqual(1);
expect(groups[0].position).toEqual('right');
@ -312,19 +324,20 @@ describe('axes_configuration', () => {
{
...sampleLayer,
accessors: ['yAccessorId', 'yAccessorId3', 'yAccessorId4'],
yConfig: [{ type: 'yConfig', forAccessor: 'yAccessorId', axisMode: 'right' }],
decorations: [{ type: 'dataDecorationConfig', forAccessor: 'yAccessorId', axisId: '1' }],
},
],
false,
formatFactory,
fieldFormats
fieldFormats,
yAxisConfigs
);
expect(groups.length).toEqual(2);
expect(groups[0].position).toEqual('left');
expect(groups[0].series[0].accessor).toEqual('yAccessorId3');
expect(groups[0].series[1].accessor).toEqual('yAccessorId4');
expect(groups[1].position).toEqual('right');
expect(groups[1].series[0].accessor).toEqual('yAccessorId');
expect(groups[0].position).toEqual('right');
expect(groups[0].series[0].accessor).toEqual('yAccessorId');
expect(groups[1].position).toEqual('left');
expect(groups[1].series[0].accessor).toEqual('yAccessorId3');
expect(groups[1].series[1].accessor).toEqual('yAccessorId4');
expect(formatFactory).toHaveBeenCalledWith({ id: 'number', params: {} });
expect(formatFactory).toHaveBeenCalledWith({ id: 'currency', params: {} });
});
@ -336,12 +349,13 @@ describe('axes_configuration', () => {
{
...sampleLayer,
accessors: ['yAccessorId', 'yAccessorId3', 'yAccessorId4'],
yConfig: [{ type: 'yConfig', forAccessor: 'yAccessorId', axisMode: 'right' }],
decorations: [{ type: 'dataDecorationConfig', forAccessor: 'yAccessorId', axisId: '1' }],
},
],
false,
formatFactory,
fieldFormats
fieldFormats,
yAxisConfigs
);
expect(formatFactory).toHaveBeenCalledTimes(2);
expect(formatFactory).toHaveBeenCalledWith({ id: 'number', params: {} });

View file

@ -6,15 +6,18 @@
* Side Public License, v 1.
*/
import { Position } from '@elastic/charts';
import type { IFieldFormat, SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils';
import { FormatFactory } from '../types';
import {
AxisExtentConfig,
CommonXYDataLayerConfig,
ExtendedYConfig,
YConfig,
YScaleType,
DataDecorationConfig,
YAxisConfig,
ReferenceLineDecorationConfig,
YAxisConfigResult,
XAxisConfigResult,
} from '../../common';
import { LayersFieldFormats } from './layers';
@ -25,15 +28,26 @@ export interface Series {
interface FormattedMetric extends Series {
fieldFormat: SerializedFieldFormat;
axisId?: string;
}
export type GroupsConfiguration = Array<{
groupId: 'left' | 'right';
position: 'left' | 'right' | 'bottom' | 'top';
interface AxesSeries {
[key: string]: FormattedMetric[];
}
export interface AxisConfiguration extends Omit<YAxisConfig, 'id'> {
/**
* Axis group identificator. Format: `axis-${axis.id}` or just `left`/`right`.
*/
groupId: string;
position: Position;
formatter?: IFieldFormat;
series: Series[];
scale?: YScaleType;
}>;
}
export type GroupsConfiguration = AxisConfiguration[];
export type AxesMap = Record<'left' | 'right', AxisConfiguration | undefined>;
export function isFormatterCompatible(
formatter1: SerializedFieldFormat,
@ -42,91 +56,203 @@ export function isFormatterCompatible(
return formatter1?.id === formatter2?.id;
}
const LEFT_GLOBAL_AXIS_ID = 'left';
const RIGHT_GLOBAL_AXIS_ID = 'right';
function isAxisSeriesAppliedForFormatter(
series: FormattedMetric[],
currentSeries: FormattedMetric
) {
return series.every((leftSeries) =>
isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
);
}
export function groupAxesByType(
layers: CommonXYDataLayerConfig[],
fieldFormats: LayersFieldFormats
fieldFormats: LayersFieldFormats,
yAxisConfigs?: YAxisConfig[]
) {
const series: {
auto: FormattedMetric[];
left: FormattedMetric[];
right: FormattedMetric[];
bottom: FormattedMetric[];
} = {
const series: AxesSeries = {
auto: [],
left: [],
right: [],
bottom: [],
};
const leftSeriesKeys: string[] = [];
const rightSeriesKeys: string[] = [];
layers.forEach((layer) => {
const { layerId, table } = layer;
layer.accessors.forEach((accessor) => {
const yConfig: Array<YConfig | ExtendedYConfig> | undefined = layer.yConfig;
const dataDecorations:
| Array<DataDecorationConfig | ReferenceLineDecorationConfig>
| undefined = layer.decorations;
const yAccessor = getAccessorByDimension(accessor, table.columns);
const mode =
yConfig?.find(({ forAccessor }) => forAccessor === yAccessor)?.axisMode || 'auto';
const decorationByAccessor = dataDecorations?.find(
(decorationConfig) => decorationConfig.forAccessor === yAccessor
);
const axisConfigById = yAxisConfigs?.find(
(axis) =>
decorationByAccessor?.axisId && axis.id && axis.id === decorationByAccessor?.axisId
);
const key = axisConfigById?.id ? `axis-${axisConfigById?.id}` : 'auto';
const fieldFormat = fieldFormats[layerId].yAccessors[yAccessor]!;
series[mode].push({ layer: layer.layerId, accessor: yAccessor, fieldFormat });
if (!series[key]) {
series[key] = [];
}
series[key].push({ layer: layer.layerId, accessor: yAccessor, fieldFormat });
if (axisConfigById?.position === Position.Left) {
leftSeriesKeys.push(key);
} else if (axisConfigById?.position === Position.Right) {
rightSeriesKeys.push(key);
}
});
});
const tablesExist = layers.filter(({ table }) => Boolean(table)).length > 0;
leftSeriesKeys.push(LEFT_GLOBAL_AXIS_ID);
rightSeriesKeys.push(RIGHT_GLOBAL_AXIS_ID);
series.auto.forEach((currentSeries) => {
if (
series.left.length === 0 ||
(tablesExist &&
series.left.every((leftSeries) =>
isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
))
) {
series.left.push(currentSeries);
} else if (
series.right.length === 0 ||
(tablesExist &&
series.left.every((leftSeries) =>
isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
))
) {
series.right.push(currentSeries);
} else if (series.right.length >= series.left.length) {
series.left.push(currentSeries);
const leftAxisGroupId = tablesExist
? leftSeriesKeys.find((leftSeriesKey) =>
isAxisSeriesAppliedForFormatter(series[leftSeriesKey], currentSeries)
)
: undefined;
const rightAxisGroupId = tablesExist
? rightSeriesKeys.find((rightSeriesKey) =>
isAxisSeriesAppliedForFormatter(series[rightSeriesKey], currentSeries)
)
: undefined;
let axisGroupId = LEFT_GLOBAL_AXIS_ID;
if (series[LEFT_GLOBAL_AXIS_ID].length === 0 || leftAxisGroupId) {
axisGroupId = leftAxisGroupId || LEFT_GLOBAL_AXIS_ID;
} else if (series[RIGHT_GLOBAL_AXIS_ID].length === 0 || rightAxisGroupId) {
axisGroupId = rightAxisGroupId || RIGHT_GLOBAL_AXIS_ID;
} else if (series[RIGHT_GLOBAL_AXIS_ID].length >= series[LEFT_GLOBAL_AXIS_ID].length) {
axisGroupId = LEFT_GLOBAL_AXIS_ID;
} else {
series.right.push(currentSeries);
axisGroupId = RIGHT_GLOBAL_AXIS_ID;
}
series[axisGroupId].push(currentSeries);
});
return series;
}
export function getAxisPosition(position: Position, shouldRotate: boolean) {
if (shouldRotate) {
switch (position) {
case Position.Bottom: {
return Position.Right;
}
case Position.Right: {
return Position.Top;
}
case Position.Top: {
return Position.Left;
}
case Position.Left: {
return Position.Bottom;
}
}
}
return position;
}
export function getOriginalAxisPosition(position: Position, shouldRotate: boolean) {
if (shouldRotate) {
switch (position) {
case Position.Bottom: {
return Position.Left;
}
case Position.Right: {
return Position.Bottom;
}
case Position.Top: {
return Position.Right;
}
case Position.Left: {
return Position.Top;
}
}
}
return position;
}
function axisGlobalConfig(position: Position, yAxisConfigs?: YAxisConfig[]) {
return yAxisConfigs?.find((axis) => !axis.id && axis.position === position) || {};
}
const getXAxisConfig = (axisConfigs: Array<XAxisConfigResult | YAxisConfigResult> = []) =>
axisConfigs.find(({ type }) => type === 'xAxisConfig');
export function getAxesConfiguration(
layers: CommonXYDataLayerConfig[],
shouldRotate: boolean,
formatFactory: FormatFactory | undefined,
fieldFormats: LayersFieldFormats,
yLeftScale?: YScaleType,
yRightScale?: YScaleType
axisConfigs?: Array<XAxisConfigResult | YAxisConfigResult>
): GroupsConfiguration {
const series = groupAxesByType(layers, fieldFormats);
const series = groupAxesByType(layers, fieldFormats, axisConfigs);
const axisGroups: GroupsConfiguration = [];
let position: Position;
if (series.left.length > 0) {
axisConfigs?.forEach((axis) => {
const groupId = axis.id ? `axis-${axis.id}` : undefined;
if (groupId && series[groupId] && series[groupId].length > 0) {
position = getAxisPosition(axis.position || Position.Left, shouldRotate);
axisGroups.push({
groupId,
formatter: formatFactory?.(series[groupId][0].fieldFormat),
series: series[groupId].map(({ fieldFormat, ...currentSeries }) => currentSeries),
...axisGlobalConfig(axis.position || Position.Left, axisConfigs),
...axis,
position,
});
}
});
if (series[LEFT_GLOBAL_AXIS_ID].length > 0) {
position = shouldRotate ? Position.Bottom : Position.Left;
axisGroups.push({
groupId: 'left',
position: shouldRotate ? 'bottom' : 'left',
groupId: LEFT_GLOBAL_AXIS_ID,
formatter: formatFactory?.(series.left[0].fieldFormat),
series: series.left.map(({ fieldFormat, ...currentSeries }) => currentSeries),
scale: yLeftScale,
...axisGlobalConfig(Position.Left, axisConfigs),
position,
});
}
if (series.right.length > 0) {
if (series[RIGHT_GLOBAL_AXIS_ID].length > 0) {
position = shouldRotate ? Position.Top : Position.Right;
axisGroups.push({
groupId: 'right',
position: shouldRotate ? 'top' : 'right',
groupId: RIGHT_GLOBAL_AXIS_ID,
formatter: formatFactory?.(series.right[0].fieldFormat),
series: series.right.map(({ fieldFormat, ...currentSeries }) => currentSeries),
scale: yRightScale,
...axisGlobalConfig(Position.Right, axisConfigs),
position,
});
}
const xAxisConfig = getXAxisConfig(axisConfigs);
if (xAxisConfig) {
position = getAxisPosition(xAxisConfig.position || Position.Bottom, shouldRotate);
axisGroups.push({
groupId: 'bottom',
series: [],
...xAxisConfig,
position,
});
}

View file

@ -54,8 +54,11 @@ describe('color_assignment', () => {
type: 'dataLayer',
showLines: true,
isHistogram: true,
isPercentage: false,
xScaleType: 'linear',
seriesType: 'bar',
isStacked: false,
isHorizontal: false,
palette: { type: 'palette', name: 'palette1' },
layerType: LayerTypes.DATA,
splitAccessor: 'split1',
@ -68,7 +71,10 @@ describe('color_assignment', () => {
showLines: true,
xScaleType: 'linear',
isHistogram: true,
isPercentage: false,
seriesType: 'bar',
isStacked: false,
isHorizontal: false,
palette: { type: 'palette', name: 'palette2' },
layerType: LayerTypes.DATA,
splitAccessor: 'split2',

View file

@ -27,6 +27,7 @@ import {
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
import { PaletteRegistry, SeriesLayer } from '@kbn/coloring';
import { CommonXYDataLayerConfig, XScaleType } from '../../common';
import { AxisModes, SeriesTypes } from '../../common/constants';
import { FormatFactory } from '../types';
import { getSeriesColor } from './state';
import { ColorAssignments } from './color_assignment';
@ -350,10 +351,14 @@ export const getSeriesProps: GetSeriesPropsFn = ({
formattedDatatableInfo,
defaultXScaleType,
}): SeriesSpec => {
const { table, markSizeAccessor } = layer;
const isStacked = layer.seriesType.includes('stacked');
const isPercentage = layer.seriesType.includes('percentage');
const isBarChart = layer.seriesType.includes('bar');
const { table, isStacked, markSizeAccessor } = layer;
const isPercentage = layer.isPercentage;
let stackMode: StackMode | undefined = isPercentage ? AxisModes.PERCENTAGE : undefined;
if (yAxis?.mode) {
stackMode = yAxis?.mode === AxisModes.NORMAL ? undefined : yAxis?.mode;
}
const scaleType = yAxis?.scaleType || ScaleType.Linear;
const isBarChart = layer.seriesType === SeriesTypes.BAR;
const xColumnId = layer.xAccessor && getAccessorByDimension(layer.xAccessor, table.columns);
const splitColumnId =
layer.splitAccessor && getAccessorByDimension(layer.splitAccessor, table.columns);
@ -413,9 +418,9 @@ export const getSeriesProps: GetSeriesPropsFn = ({
data: rows,
xScaleType: xColumnId ? layer.xScaleType ?? defaultXScaleType : 'ordinal',
yScaleType:
formatter?.id === 'bytes' && yAxis?.scale === ScaleType.Linear
formatter?.id === 'bytes' && scaleType === ScaleType.Linear
? ScaleType.LinearBinary
: yAxis?.scale || ScaleType.Linear,
: scaleType,
color: (series) =>
getColor(
series,
@ -431,7 +436,7 @@ export const getSeriesProps: GetSeriesPropsFn = ({
),
groupId: yAxis?.groupId,
enableHistogramMode,
stackMode: isPercentage ? StackMode.Percentage : undefined,
stackMode,
timeZone,
areaSeriesStyle: {
point: getPointConfig({

View file

@ -17,7 +17,6 @@ import {
CommonXYDataLayerConfig,
CommonXYLayerConfig,
ReferenceLineLayerConfig,
SeriesType,
} from '../../common/types';
import { GroupsConfiguration } from './axes_configuration';
import { getFormat } from './format';
@ -110,7 +109,7 @@ const getAccessorWithFieldFormat = (
const getYAccessorWithFieldFormat = (
dimension: string | ExpressionValueVisDimension | undefined,
columns: Datatable['columns'],
seriesType: SeriesType
isPercentage: boolean
) => {
if (!dimension) {
return {};
@ -118,7 +117,7 @@ const getYAccessorWithFieldFormat = (
const accessor = getAccessorByDimension(dimension, columns);
let format = getFormat(columns, dimension) ?? { id: 'number' };
if (format?.id !== 'percent' && seriesType.includes('percentage')) {
if (format?.id !== 'percent' && isPercentage) {
format = { id: 'percent', params: { pattern: '0.[00]%' } };
}
@ -126,7 +125,7 @@ const getYAccessorWithFieldFormat = (
};
export const getLayerFormats = (
{ xAccessor, accessors, splitAccessor, table, seriesType }: CommonXYDataLayerConfig,
{ xAccessor, accessors, splitAccessor, table, isPercentage }: CommonXYDataLayerConfig,
{ splitColumnAccessor, splitRowAccessor }: SplitAccessors
): LayerFieldFormats => {
const yAccessors: Array<string | ExpressionValueVisDimension> = accessors;
@ -135,7 +134,7 @@ export const getLayerFormats = (
yAccessors: yAccessors.reduce(
(formatters, a) => ({
...formatters,
...getYAccessorWithFieldFormat(a, table.columns, seriesType),
...getYAccessorWithFieldFormat(a, table.columns, isPercentage),
}),
{}
),
@ -160,28 +159,21 @@ export const getLayersFormats = (
const getTitleForYAccessor = (
layerId: string,
yAccessor: string | ExpressionValueVisDimension,
{ yTitle, yRightTitle }: Omit<CustomTitles, 'xTitle'>,
groups: GroupsConfiguration,
columns: Datatable['columns']
) => {
const column = getColumnByAccessor(yAccessor, columns);
const isRight = groups.some((group) =>
group.series.some(
({ accessor, layer }) =>
accessor === yAccessor && layer === layerId && group.groupId === 'right'
)
const axisGroup = groups.find((group) =>
group.series.some(({ accessor, layer }) => accessor === yAccessor && layer === layerId)
);
if (isRight) {
return yRightTitle || column!.name;
}
return yTitle || column!.name;
return axisGroup?.title || column!.name;
};
export const getLayerTitles = (
{ xAccessor, accessors, splitAccessor, table, layerId }: CommonXYDataLayerConfig,
{ splitColumnAccessor, splitRowAccessor }: SplitAccessors,
{ xTitle, yTitle, yRightTitle }: CustomTitles,
{ xTitle }: CustomTitles,
groups: GroupsConfiguration
): LayerAccessorsTitles => {
const mapTitle = (dimension?: string | ExpressionValueVisDimension) => {
@ -194,13 +186,7 @@ export const getLayerTitles = (
};
const getYTitle = (accessor: string) => ({
[accessor]: getTitleForYAccessor(
layerId,
accessor,
{ yTitle, yRightTitle },
groups,
table.columns
),
[accessor]: getTitleForYAccessor(layerId, accessor, groups, table.columns),
});
const xColumnId = xAccessor && getAccessorByDimension(xAccessor, table.columns);

View file

@ -6,23 +6,15 @@
* Side Public License, v 1.
*/
import type { CommonXYLayerConfig, SeriesType, ExtendedYConfig, YConfig } from '../../common';
import type {
CommonXYLayerConfig,
ReferenceLineDecorationConfig,
DataDecorationConfig,
} from '../../common';
import { getDataLayers, isAnnotationsLayer, isDataLayer, isReferenceLine } from './visualization';
export function isHorizontalSeries(seriesType: SeriesType) {
return (
seriesType === 'bar_horizontal' ||
seriesType === 'bar_horizontal_stacked' ||
seriesType === 'bar_horizontal_percentage_stacked'
);
}
export function isStackedChart(seriesType: SeriesType) {
return seriesType.includes('stacked');
}
export function isHorizontalChart(layers: CommonXYLayerConfig[]) {
return getDataLayers(layers).every((l) => isHorizontalSeries(l.seriesType));
return getDataLayers(layers).every((l) => l.isHorizontal);
}
export const getSeriesColor = (layer: CommonXYLayerConfig, accessor: string) => {
@ -33,6 +25,10 @@ export const getSeriesColor = (layer: CommonXYLayerConfig, accessor: string) =>
) {
return null;
}
const yConfig: Array<YConfig | ExtendedYConfig> | undefined = layer?.yConfig;
return yConfig?.find((yConf) => yConf.forAccessor === accessor)?.color || null;
const decorations: Array<DataDecorationConfig | ReferenceLineDecorationConfig> | undefined =
layer?.decorations;
return (
decorations?.find((decorationConfig) => decorationConfig.forAccessor === accessor)?.color ||
null
);
};

View file

@ -10,7 +10,7 @@ import {
LayerTypes,
REFERENCE_LINE,
REFERENCE_LINE_LAYER,
REFERENCE_LINE_Y_CONFIG,
EXTENDED_REFERENCE_LINE_DECORATION_CONFIG,
} from '../../common/constants';
import {
CommonXYLayerConfig,
@ -19,8 +19,8 @@ import {
CommonXYAnnotationLayerConfig,
ReferenceLineLayerConfig,
ReferenceLineConfig,
ExtendedYConfigResult,
ReferenceLineYConfig,
ReferenceLineDecorationConfigResult,
ExtendedReferenceLineDecorationConfig,
} from '../../common/types';
export const isDataLayer = (layer: CommonXYLayerConfig): layer is CommonXYDataLayerConfig =>
@ -35,9 +35,10 @@ export const isReferenceLayer = (layer: CommonXYLayerConfig): layer is Reference
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 isReferenceLineDecorationConfig = (
decoration: ExtendedReferenceLineDecorationConfig | ReferenceLineDecorationConfigResult
): decoration is ExtendedReferenceLineDecorationConfig =>
decoration.type === EXTENDED_REFERENCE_LINE_DECORATION_CONFIG;
export const isReferenceLineOrLayer = (
layer: CommonXYLayerConfig

View file

@ -18,18 +18,16 @@ import {
xyVisFunction,
layeredXyVisFunction,
extendedDataLayerFunction,
dataDecorationConfigFunction,
xAxisConfigFunction,
yAxisConfigFunction,
extendedYAxisConfigFunction,
legendConfigFunction,
gridlinesConfigFunction,
axisExtentConfigFunction,
tickLabelsConfigFunction,
referenceLineFunction,
referenceLineLayerFunction,
annotationLayerFunction,
labelsOrientationConfigFunction,
axisTitlesVisibilityConfigFunction,
extendedAnnotationLayerFunction,
referenceLineDecorationConfigFunction,
} from '../common/expression_functions';
import { GetStartDepsFn, getXyChartRenderer } from './expression_renderers';
@ -55,18 +53,16 @@ export class ExpressionXyPlugin {
{ expressions, charts }: SetupDeps
): ExpressionXyPluginSetup {
expressions.registerFunction(yAxisConfigFunction);
expressions.registerFunction(extendedYAxisConfigFunction);
expressions.registerFunction(dataDecorationConfigFunction);
expressions.registerFunction(referenceLineDecorationConfigFunction);
expressions.registerFunction(legendConfigFunction);
expressions.registerFunction(gridlinesConfigFunction);
expressions.registerFunction(extendedDataLayerFunction);
expressions.registerFunction(axisExtentConfigFunction);
expressions.registerFunction(tickLabelsConfigFunction);
expressions.registerFunction(xAxisConfigFunction);
expressions.registerFunction(annotationLayerFunction);
expressions.registerFunction(extendedAnnotationLayerFunction);
expressions.registerFunction(labelsOrientationConfigFunction);
expressions.registerFunction(referenceLineFunction);
expressions.registerFunction(referenceLineLayerFunction);
expressions.registerFunction(axisTitlesVisibilityConfigFunction);
expressions.registerFunction(xyVisFunction);
expressions.registerFunction(layeredXyVisFunction);

View file

@ -11,16 +11,14 @@ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/server';
import { ExpressionXyPluginSetup, ExpressionXyPluginStart } from './types';
import {
xyVisFunction,
yAxisConfigFunction,
extendedYAxisConfigFunction,
legendConfigFunction,
gridlinesConfigFunction,
dataDecorationConfigFunction,
xAxisConfigFunction,
yAxisConfigFunction,
referenceLineDecorationConfigFunction,
axisExtentConfigFunction,
tickLabelsConfigFunction,
annotationLayerFunction,
labelsOrientationConfigFunction,
referenceLineFunction,
axisTitlesVisibilityConfigFunction,
extendedDataLayerFunction,
referenceLineLayerFunction,
layeredXyVisFunction,
@ -33,18 +31,16 @@ export class ExpressionXyPlugin
{
public setup(core: CoreSetup, { expressions }: SetupDeps) {
expressions.registerFunction(yAxisConfigFunction);
expressions.registerFunction(extendedYAxisConfigFunction);
expressions.registerFunction(dataDecorationConfigFunction);
expressions.registerFunction(xAxisConfigFunction);
expressions.registerFunction(referenceLineDecorationConfigFunction);
expressions.registerFunction(legendConfigFunction);
expressions.registerFunction(gridlinesConfigFunction);
expressions.registerFunction(extendedDataLayerFunction);
expressions.registerFunction(axisExtentConfigFunction);
expressions.registerFunction(tickLabelsConfigFunction);
expressions.registerFunction(annotationLayerFunction);
expressions.registerFunction(extendedAnnotationLayerFunction);
expressions.registerFunction(labelsOrientationConfigFunction);
expressions.registerFunction(referenceLineFunction);
expressions.registerFunction(referenceLineLayerFunction);
expressions.registerFunction(axisTitlesVisibilityConfigFunction);
expressions.registerFunction(xyVisFunction);
expressions.registerFunction(layeredXyVisFunction);
}

View file

@ -18,6 +18,9 @@ export type {
ValidLayer,
XYDataLayerConfig,
XYAnnotationLayerConfig,
YAxisMode,
SeriesType,
YConfig,
} from './xy_visualization/types';
export type {
DatasourcePublicAPI,
@ -74,13 +77,10 @@ export type {
} from './indexpattern_datasource/types';
export type {
XYArgs,
ExtendedYConfig,
XYRender,
LayerType,
YAxisMode,
LineStyle,
FillStyle,
SeriesType,
YScaleType,
XScaleType,
AxisConfig,
@ -88,7 +88,6 @@ export type {
XYChartProps,
LegendConfig,
IconPosition,
ExtendedYConfigResult,
DataLayerArgs,
ValueLabelMode,
AxisExtentMode,
@ -97,14 +96,9 @@ export type {
AxisExtentConfig,
LegendConfigResult,
AxesSettingsConfig,
GridlinesConfigResult,
TickLabelsConfigResult,
AxisExtentConfigResult,
ReferenceLineLayerArgs,
LabelsOrientationConfig,
ReferenceLineLayerConfig,
LabelsOrientationConfigResult,
AxisTitlesVisibilityConfigResult,
} from '@kbn/expression-xy-plugin/common';
export type { LensEmbeddableInput, LensSavedObjectAttributes, Embeddable } from './embeddable';

View file

@ -8,7 +8,7 @@
import React, { useCallback, useMemo } from 'react';
import { EuiSpacer, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AxesSettingsConfig } from '@kbn/expression-xy-plugin/common';
import { AxesSettingsConfig } from '../xy_visualization/types';
import { LabelMode, useDebouncedValue, VisLabel } from '.';
type AxesSettingsConfigKeys = keyof AxesSettingsConfig;

View file

@ -5,28 +5,6 @@ Object {
"chain": Array [
Object {
"arguments": Object {
"axisTitlesVisibilitySettings": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"x": Array [
true,
],
"yLeft": Array [
true,
],
"yRight": Array [
true,
],
},
"function": "axisTitlesVisibilityConfig",
"type": "function",
},
],
"type": "expression",
},
],
"curveType": Array [
"LINEAR",
],
@ -42,53 +20,9 @@ Object {
"fittingFunction": Array [
"Carry",
],
"gridlinesVisibilitySettings": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"x": Array [
false,
],
"yLeft": Array [
true,
],
"yRight": Array [
true,
],
},
"function": "gridlinesConfig",
"type": "function",
},
],
"type": "expression",
},
],
"hideEndzones": Array [
true,
],
"labelsOrientation": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"x": Array [
0,
],
"yLeft": Array [
-90,
],
"yRight": Array [
-45,
],
},
"function": "labelsOrientationConfig",
"type": "function",
},
],
"type": "expression",
},
],
"layers": Array [
Object {
"chain": Array [
@ -101,12 +35,16 @@ Object {
"columnToLabel": Array [
"{\\"b\\":\\"col_b\\",\\"c\\":\\"col_c\\",\\"d\\":\\"col_d\\"}",
],
"decorations": Array [],
"hide": Array [
false,
],
"isHistogram": Array [
false,
],
"isHorizontal": Array [],
"isPercentage": Array [],
"isStacked": Array [],
"layerId": Array [
"first",
],
@ -144,7 +82,6 @@ Object {
"xScaleType": Array [
"linear",
],
"yConfig": Array [],
},
"function": "extendedDataLayer",
"type": "function",
@ -182,103 +119,138 @@ Object {
"type": "expression",
},
],
"tickLabelsVisibilitySettings": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"x": Array [
false,
],
"yLeft": Array [
true,
],
"yRight": Array [
true,
],
},
"function": "tickLabelsConfig",
"type": "function",
},
],
"type": "expression",
},
],
"valueLabels": Array [
"hide",
],
"valuesInLegend": Array [
false,
],
"xExtent": Array [
"xAxisConfig": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"lowerBound": Array [],
"mode": Array [],
"upperBound": Array [],
"extent": Array [],
"id": Array [
"x",
],
"labelsOrientation": Array [
0,
],
"position": Array [
"bottom",
],
"showGridLines": Array [
false,
],
"showLabels": Array [
false,
],
"showTitle": Array [
true,
],
"title": Array [
"",
],
},
"function": "axisExtentConfig",
"function": "xAxisConfig",
"type": "function",
},
],
"type": "expression",
},
],
"xTitle": Array [
"",
],
"yLeftExtent": Array [
"yAxisConfigs": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"lowerBound": Array [],
"mode": Array [],
"upperBound": Array [],
"extent": Array [],
"id": Array [],
"labelsOrientation": Array [
-90,
],
"position": Array [
"left",
],
"scaleType": Array [
"linear",
],
"showGridLines": Array [
true,
],
"showLabels": Array [
true,
],
"showTitle": Array [
true,
],
"title": Array [
"",
],
},
"function": "axisExtentConfig",
"function": "yAxisConfig",
"type": "function",
},
],
"type": "expression",
},
],
"yLeftScale": Array [
"linear",
],
"yRightExtent": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"lowerBound": Array [
123,
"extent": Array [
Object {
"chain": Array [
Object {
"arguments": Object {
"lowerBound": Array [
123,
],
"mode": Array [
"custom",
],
"upperBound": Array [
456,
],
},
"function": "axisExtentConfig",
"type": "function",
},
],
"type": "expression",
},
],
"mode": Array [
"custom",
"id": Array [],
"labelsOrientation": Array [
-45,
],
"upperBound": Array [
456,
"position": Array [
"right",
],
"scaleType": Array [
"linear",
],
"showGridLines": Array [
true,
],
"showLabels": Array [
true,
],
"showTitle": Array [
true,
],
"title": Array [
"",
],
},
"function": "axisExtentConfig",
"function": "yAxisConfig",
"type": "function",
},
],
"type": "expression",
},
],
"yRightScale": Array [
"linear",
],
"yRightTitle": Array [
"",
],
"yTitle": Array [
"",
],
},
"function": "layeredXyVis",
"type": "function",

View file

@ -7,13 +7,18 @@
import { groupBy, partition } from 'lodash';
import { i18n } from '@kbn/i18n';
import type { YAxisMode, ExtendedYConfig } from '@kbn/expression-xy-plugin/common';
import { Datatable } from '@kbn/expressions-plugin/public';
import { layerTypes } from '../../common';
import type { DatasourceLayers, FramePublicAPI, Visualization } from '../types';
import { groupAxesByType } from './axes_configuration';
import { isHorizontalChart, isPercentageSeries, isStackedChart } from './state_helpers';
import type { XYState, XYDataLayerConfig, XYReferenceLineLayerConfig } from './types';
import type {
XYState,
XYDataLayerConfig,
XYReferenceLineLayerConfig,
YAxisMode,
YConfig,
} from './types';
import {
checkScaleOperation,
getAxisName,
@ -35,7 +40,7 @@ export interface ReferenceLineBase {
* * what groups are current defined in data layers
* * what existing reference line are currently defined in reference layers
*/
export function getGroupsToShow<T extends ReferenceLineBase & { config?: ExtendedYConfig[] }>(
export function getGroupsToShow<T extends ReferenceLineBase & { config?: YConfig[] }>(
referenceLayers: T[],
state: XYState | undefined,
datasourceLayers: DatasourceLayers,

View file

@ -6,13 +6,14 @@
*/
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import type { SeriesType, ExtendedYConfig } from '@kbn/expression-xy-plugin/common';
import type { FramePublicAPI, DatasourcePublicAPI } from '../types';
import {
visualizationTypes,
XYLayerConfig,
XYDataLayerConfig,
XYReferenceLineLayerConfig,
SeriesType,
YConfig,
} from './types';
import { getDataLayers, isAnnotationsLayer, isDataLayer } from './visualization_helpers';
@ -58,8 +59,7 @@ export const getSeriesColor = (layer: XYLayerConfig, accessor: string) => {
return null;
}
return (
layer?.yConfig?.find((yConfig: ExtendedYConfig) => yConfig.forAccessor === accessor)?.color ||
null
layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null
);
};

View file

@ -159,13 +159,23 @@ describe('#toExpression', () => {
undefined,
datasourceExpressionsByLayers
) as Ast;
expect(
(expression.chain[0].arguments.axisTitlesVisibilitySettings[0] as Ast).chain[0].arguments
).toEqual({
x: [true],
yLeft: [true],
yRight: [true],
});
expect((expression.chain[0].arguments.yAxisConfigs[0] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showTitle: [true],
position: ['left'],
})
);
expect((expression.chain[0].arguments.yAxisConfigs[1] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showTitle: [true],
position: ['right'],
})
);
expect((expression.chain[0].arguments.xAxisConfig[0] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showTitle: [true],
})
);
});
it('should generate an expression without x accessor', () => {
@ -244,9 +254,6 @@ describe('#toExpression', () => {
expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('b');
expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('c');
expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('d');
expect(expression.chain[0].arguments.xTitle).toEqual(['']);
expect(expression.chain[0].arguments.yTitle).toEqual(['']);
expect(expression.chain[0].arguments.yRightTitle).toEqual(['']);
expect(
(expression.chain[0].arguments.layers[0] as Ast).chain[0].arguments.columnToLabel
).toEqual([
@ -279,13 +286,23 @@ describe('#toExpression', () => {
undefined,
datasourceExpressionsByLayers
) as Ast;
expect(
(expression.chain[0].arguments.tickLabelsVisibilitySettings[0] as Ast).chain[0].arguments
).toEqual({
x: [true],
yLeft: [true],
yRight: [true],
});
expect((expression.chain[0].arguments.yAxisConfigs[0] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showLabels: [true],
position: ['left'],
})
);
expect((expression.chain[0].arguments.yAxisConfigs[1] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showLabels: [true],
position: ['right'],
})
);
expect((expression.chain[0].arguments.xAxisConfig[0] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showLabels: [true],
})
);
});
it('should default the tick labels orientation settings to 0', () => {
@ -309,11 +326,23 @@ describe('#toExpression', () => {
undefined,
datasourceExpressionsByLayers
) as Ast;
expect((expression.chain[0].arguments.labelsOrientation[0] as Ast).chain[0].arguments).toEqual({
x: [0],
yLeft: [0],
yRight: [0],
});
expect((expression.chain[0].arguments.yAxisConfigs[0] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
labelsOrientation: [0],
position: ['left'],
})
);
expect((expression.chain[0].arguments.yAxisConfigs[1] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
labelsOrientation: [0],
position: ['right'],
})
);
expect((expression.chain[0].arguments.xAxisConfig[0] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
labelsOrientation: [0],
})
);
});
it('should default the gridlines visibility settings to true', () => {
@ -337,13 +366,23 @@ describe('#toExpression', () => {
undefined,
datasourceExpressionsByLayers
) as Ast;
expect(
(expression.chain[0].arguments.gridlinesVisibilitySettings[0] as Ast).chain[0].arguments
).toEqual({
x: [true],
yLeft: [true],
yRight: [true],
});
expect((expression.chain[0].arguments.yAxisConfigs[0] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showGridLines: [true],
position: ['left'],
})
);
expect((expression.chain[0].arguments.yAxisConfigs[1] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showGridLines: [true],
position: ['right'],
})
);
expect((expression.chain[0].arguments.xAxisConfig[0] as Ast).chain[0].arguments).toEqual(
expect.objectContaining({
showGridLines: [true],
})
);
});
it('should correctly report the valueLabels visibility settings', () => {
@ -488,8 +527,9 @@ describe('#toExpression', () => {
) as Ast;
function getYConfigColorForLayer(ast: Ast, index: number) {
return ((ast.chain[0].arguments.layers[index] as Ast).chain[0].arguments.yConfig[0] as Ast)
.chain[0].arguments.color;
return (
(ast.chain[0].arguments.layers[index] as Ast).chain[0].arguments.decorations[0] as Ast
).chain[0].arguments.color;
}
expect(getYConfigColorForLayer(expression, 0)).toEqual([]);
expect(getYConfigColorForLayer(expression, 1)).toEqual([defaultReferenceLineColor]);

View file

@ -8,15 +8,15 @@
import { Ast, AstFunction } from '@kbn/interpreter';
import { Position, ScaleType } from '@elastic/charts';
import type { PaletteRegistry } from '@kbn/coloring';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
import type { YConfig, ExtendedYConfig } from '@kbn/expression-xy-plugin/common';
import { LegendSize } from '@kbn/visualizations-plugin/public';
import {
State,
YConfig,
XYDataLayerConfig,
XYReferenceLineLayerConfig,
XYAnnotationLayerConfig,
AxisConfig,
} from './types';
import type { ValidXYDataLayerConfig } from './types';
import { OperationMetadata, DatasourcePublicAPI, DatasourceLayers } from '../types';
@ -191,6 +191,54 @@ export const buildExpression = (
return null;
}
const isLeftAxis = validDataLayers.some(({ yConfig }) =>
yConfig?.some((config) => config.axisMode === Position.Left)
);
const isRightAxis = validDataLayers.some(({ yConfig }) =>
yConfig?.some((config) => config.axisMode === Position.Right)
);
const yAxisConfigs: AxisConfig[] = [
{
position: Position.Left,
extent: state?.yLeftExtent,
showTitle: state?.axisTitlesVisibilitySettings?.yLeft ?? true,
title: state.yTitle || '',
showLabels: state?.tickLabelsVisibilitySettings?.yLeft ?? true,
showGridLines: state?.gridlinesVisibilitySettings?.yLeft ?? true,
labelsOrientation: state?.labelsOrientation?.yLeft ?? 0,
scaleType: state.yLeftScale || 'linear',
},
{
position: Position.Right,
extent: state?.yRightExtent,
showTitle: state?.axisTitlesVisibilitySettings?.yRight ?? true,
title: state.yRightTitle || '',
showLabels: state?.tickLabelsVisibilitySettings?.yRight ?? true,
showGridLines: state?.gridlinesVisibilitySettings?.yRight ?? true,
labelsOrientation: state?.labelsOrientation?.yRight ?? 0,
scaleType: state.yRightScale || 'linear',
},
];
if (isLeftAxis) {
yAxisConfigs.push({
id: Position.Left,
position: Position.Left,
// we need also settings from global config here so that default's doesn't override it
...yAxisConfigs[0],
});
}
if (isRightAxis) {
yAxisConfigs.push({
id: Position.Right,
position: Position.Right,
// we need also settings from global config here so that default's doesn't override it
...yAxisConfigs[1],
});
}
return {
type: 'expression',
chain: [
@ -198,9 +246,6 @@ export const buildExpression = (
type: 'function',
function: 'layeredXyVis',
arguments: {
xTitle: [state.xTitle || ''],
yTitle: [state.yTitle || ''],
yRightTitle: [state.yRightTitle || ''],
legend: [
{
type: 'expression',
@ -252,82 +297,36 @@ export const buildExpression = (
emphasizeFitting: [state.emphasizeFitting || false],
curveType: [state.curveType || 'LINEAR'],
fillOpacity: [state.fillOpacity || 0.3],
xExtent: [axisExtentConfigToExpression(state.xExtent)],
yLeftExtent: [axisExtentConfigToExpression(state.yLeftExtent)],
yRightExtent: [axisExtentConfigToExpression(state.yRightExtent)],
yLeftScale: [state.yLeftScale || 'linear'],
yRightScale: [state.yRightScale || 'linear'],
axisTitlesVisibilitySettings: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'axisTitlesVisibilityConfig',
arguments: {
x: [state?.axisTitlesVisibilitySettings?.x ?? true],
yLeft: [state?.axisTitlesVisibilitySettings?.yLeft ?? true],
yRight: [state?.axisTitlesVisibilitySettings?.yRight ?? true],
},
},
],
},
],
tickLabelsVisibilitySettings: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'tickLabelsConfig',
arguments: {
x: [state?.tickLabelsVisibilitySettings?.x ?? true],
yLeft: [state?.tickLabelsVisibilitySettings?.yLeft ?? true],
yRight: [state?.tickLabelsVisibilitySettings?.yRight ?? true],
},
},
],
},
],
gridlinesVisibilitySettings: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'gridlinesConfig',
arguments: {
x: [state?.gridlinesVisibilitySettings?.x ?? true],
yLeft: [state?.gridlinesVisibilitySettings?.yLeft ?? true],
yRight: [state?.gridlinesVisibilitySettings?.yRight ?? true],
},
},
],
},
],
labelsOrientation: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'labelsOrientationConfig',
arguments: {
x: [state?.labelsOrientation?.x ?? 0],
yLeft: [state?.labelsOrientation?.yLeft ?? 0],
yRight: [state?.labelsOrientation?.yRight ?? 0],
},
},
],
},
],
valueLabels: [state?.valueLabels || 'hide'],
hideEndzones: [state?.hideEndzones || false],
valuesInLegend: [state?.valuesInLegend || false],
yAxisConfigs: [...yAxisConfigsToExpression(yAxisConfigs)],
xAxisConfig: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'xAxisConfig',
arguments: {
id: ['x'],
position: ['bottom'],
title: [state.xTitle || ''],
showTitle: [state?.axisTitlesVisibilitySettings?.x ?? true],
showLabels: [state?.tickLabelsVisibilitySettings?.x ?? true],
showGridLines: [state?.gridlinesVisibilitySettings?.x ?? true],
labelsOrientation: [state?.labelsOrientation?.x ?? 0],
extent: state.xExtent ? [axisExtentConfigToExpression(state.xExtent)] : [],
},
},
],
},
],
layers: [
...validDataLayers.map((layer) =>
dataLayerToExpression(
layer,
yAxisConfigs,
datasourceLayers[layer.layerId],
metadata,
paletteService,
@ -351,6 +350,29 @@ export const buildExpression = (
};
};
const yAxisConfigsToExpression = (yAxisConfigs: AxisConfig[]): Ast[] => {
return yAxisConfigs.map((axis) => ({
type: 'expression',
chain: [
{
type: 'function',
function: 'yAxisConfig',
arguments: {
id: axis.id ? [axis.id] : [],
position: axis.position ? [axis.position] : [],
extent: axis.extent ? [axisExtentConfigToExpression(axis.extent)] : [],
showTitle: [axis.showTitle ?? true],
title: axis.title !== undefined ? [axis.title] : [],
showLabels: [axis.showLabels ?? true],
showGridLines: [axis.showGridLines ?? true],
labelsOrientation: axis.labelsOrientation !== undefined ? [axis.labelsOrientation] : [],
scaleType: axis.scaleType ? [axis.scaleType] : [],
},
},
],
}));
};
const referenceLineLayerToExpression = (
layer: XYReferenceLineLayerConfig,
datasourceLayer: DatasourcePublicAPI,
@ -364,9 +386,9 @@ const referenceLineLayerToExpression = (
function: 'referenceLineLayer',
arguments: {
layerId: [layer.layerId],
yConfig: layer.yConfig
decorations: layer.yConfig
? layer.yConfig.map((yConfig) =>
extendedYConfigToExpression(yConfig, defaultReferenceLineColor)
extendedYConfigToRLDecorationConfigExpression(yConfig, defaultReferenceLineColor)
)
: [],
accessors: layer.accessors,
@ -402,6 +424,7 @@ const annotationLayerToExpression = (
const dataLayerToExpression = (
layer: ValidXYDataLayerConfig,
yAxisConfigs: AxisConfig[],
datasourceLayer: DatasourcePublicAPI,
metadata: Record<string, Record<string, OperationMetadata | null>>,
paletteService: PaletteRegistry,
@ -418,6 +441,12 @@ const dataLayerToExpression = (
xAxisOperation.scale !== 'ordinal'
);
const dataFromType = layer.seriesType.split('_');
const seriesType = dataFromType[0];
const isPercentage = dataFromType.includes('percentage');
const isStacked = dataFromType.includes('stacked');
const isHorizontal = dataFromType.includes('horizontal');
return {
type: 'expression',
chain: [
@ -430,11 +459,16 @@ const dataLayerToExpression = (
xAccessor: layer.xAccessor ? [layer.xAccessor] : [],
xScaleType: [getScaleType(metadata[layer.layerId][layer.xAccessor], ScaleType.Linear)],
isHistogram: [isHistogramDimension],
isPercentage: isPercentage ? [isPercentage] : [],
isStacked: isStacked ? [isStacked] : [],
isHorizontal: isHorizontal ? [isHorizontal] : [],
splitAccessor: layer.collapseFn || !layer.splitAccessor ? [] : [layer.splitAccessor],
yConfig: layer.yConfig
? layer.yConfig.map((yConfig) => yConfigToExpression(yConfig))
decorations: layer.yConfig
? layer.yConfig.map((yConfig) =>
yConfigToDataDecorationConfigExpression(yConfig, yAxisConfigs)
)
: [],
seriesType: [layer.seriesType],
seriesType: [seriesType],
accessors: layer.accessors,
columnToLabel: [JSON.stringify(columnToLabel)],
...(datasourceExpression
@ -493,16 +527,21 @@ const dataLayerToExpression = (
};
};
const yConfigToExpression = (yConfig: YConfig, defaultColor?: string): Ast => {
const yConfigToDataDecorationConfigExpression = (
yConfig: YConfig,
yAxisConfigs: AxisConfig[],
defaultColor?: string
): Ast => {
const axisId = yAxisConfigs.find((axis) => axis.id && axis.position === yConfig.axisMode)?.id;
return {
type: 'expression',
chain: [
{
type: 'function',
function: 'yConfig',
function: 'dataDecorationConfig',
arguments: {
axisId: axisId ? [axisId] : [],
forAccessor: [yConfig.forAccessor],
axisMode: yConfig.axisMode ? [yConfig.axisMode] : [],
color: yConfig.color ? [yConfig.color] : defaultColor ? [defaultColor] : [],
},
},
@ -510,16 +549,19 @@ const yConfigToExpression = (yConfig: YConfig, defaultColor?: string): Ast => {
};
};
const extendedYConfigToExpression = (yConfig: ExtendedYConfig, defaultColor?: string): Ast => {
const extendedYConfigToRLDecorationConfigExpression = (
yConfig: YConfig,
defaultColor?: string
): Ast => {
return {
type: 'expression',
chain: [
{
type: 'function',
function: 'extendedYConfig',
function: 'referenceLineDecorationConfig',
arguments: {
forAccessor: [yConfig.forAccessor],
axisMode: yConfig.axisMode ? [yConfig.axisMode] : [],
position: yConfig.axisMode ? [yConfig.axisMode] : [],
color: yConfig.color ? [yConfig.color] : defaultColor ? [defaultColor] : [],
lineStyle: [yConfig.lineStyle || 'solid'],
lineWidth: [yConfig.lineWidth || 1],

View file

@ -6,20 +6,20 @@
*/
import { i18n } from '@kbn/i18n';
import { $Values } from '@kbn/utility-types';
import type { PaletteOutput } from '@kbn/coloring';
import type {
SeriesType,
LegendConfig,
AxisExtentConfig,
XYCurveType,
AxesSettingsConfig,
FittingFunction,
LabelsOrientationConfig,
EndValue,
ExtendedYConfig,
YConfig,
YScaleType,
XScaleType,
LineStyle,
IconPosition,
FillStyle,
YAxisConfig,
} from '@kbn/expression-xy-plugin/common';
import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common';
import { LensIconChartArea } from '../assets/chart_area';
@ -36,6 +36,56 @@ import { LensIconChartLine } from '../assets/chart_line';
import type { VisualizationType, Suggestion } from '../types';
import type { ValueLabelConfig } from '../../common/types';
export const YAxisModes = {
AUTO: 'auto',
LEFT: 'left',
RIGHT: 'right',
BOTTOM: 'bottom',
} as const;
export const SeriesTypes = {
BAR: 'bar',
LINE: 'line',
AREA: 'area',
BAR_STACKED: 'bar_stacked',
AREA_STACKED: 'area_stacked',
BAR_HORIZONTAL: 'bar_horizontal',
BAR_PERCENTAGE_STACKED: 'bar_percentage_stacked',
BAR_HORIZONTAL_STACKED: 'bar_horizontal_stacked',
AREA_PERCENTAGE_STACKED: 'area_percentage_stacked',
BAR_HORIZONTAL_PERCENTAGE_STACKED: 'bar_horizontal_percentage_stacked',
} as const;
export type YAxisMode = $Values<typeof YAxisModes>;
export type SeriesType = $Values<typeof SeriesTypes>;
export interface AxesSettingsConfig {
x: boolean;
yRight: boolean;
yLeft: boolean;
}
export interface AxisConfig extends Omit<YAxisConfig, 'extent'> {
extent?: AxisExtentConfig;
}
export interface LabelsOrientationConfig {
x: number;
yLeft: number;
yRight: number;
}
export interface YConfig {
forAccessor: string;
color?: string;
icon?: string;
lineWidth?: number;
lineStyle?: LineStyle;
fill?: FillStyle;
iconPosition?: IconPosition;
textVisibility?: boolean;
axisMode?: YAxisMode;
}
export interface XYDataLayerConfig {
layerId: string;
accessors: string[];
@ -55,7 +105,7 @@ export interface XYDataLayerConfig {
export interface XYReferenceLineLayerConfig {
layerId: string;
accessors: string[];
yConfig?: ExtendedYConfig[];
yConfig?: YConfig[];
layerType: 'referenceLine';
}

View file

@ -15,9 +15,9 @@ import type {
XYLayerConfig,
XYDataLayerConfig,
XYReferenceLineLayerConfig,
SeriesType,
} from './types';
import { createDatatableUtilitiesMock } from '@kbn/data-plugin/common/mocks';
import type { SeriesType } from '@kbn/expression-xy-plugin/common';
import { layerTypes } from '../../common';
import { createMockDatasource, createMockFramePublicAPI } from '../mocks';
import { LensIconChartBar } from '../assets/chart_bar';

View file

@ -17,18 +17,22 @@ import { ThemeServiceStart } from '@kbn/core/public';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public';
import {
FillStyle,
SeriesType,
YAxisMode,
ExtendedYConfig,
} from '@kbn/expression-xy-plugin/common';
import { FillStyle } from '@kbn/expression-xy-plugin/common';
import { getSuggestions } from './xy_suggestions';
import { XyToolbar } from './xy_config_panel';
import { DimensionEditor } from './xy_config_panel/dimension_editor';
import { LayerHeader } from './xy_config_panel/layer_header';
import { Visualization, AccessorConfig, FramePublicAPI } from '../types';
import { State, visualizationTypes, XYSuggestion, XYLayerConfig, XYDataLayerConfig } from './types';
import type { Visualization, AccessorConfig, FramePublicAPI } from '../types';
import {
State,
visualizationTypes,
XYSuggestion,
XYLayerConfig,
XYDataLayerConfig,
YConfig,
YAxisMode,
SeriesType,
} from './types';
import { layerTypes } from '../../common';
import { isHorizontalChart } from './state_helpers';
import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression';
@ -360,7 +364,7 @@ export const getXyVisualization = ({
}
const isReferenceLine = metrics.some((metric) => metric.agg === 'static_value');
const axisMode = axisPosition as YAxisMode;
const yConfig = metrics.map<ExtendedYConfig>((metric, idx) => {
const yConfig = metrics.map<YConfig>((metric, idx) => {
return {
color: metric.color,
forAccessor: metric.accessor ?? foundLayer.accessors[idx],

View file

@ -7,7 +7,6 @@
import { i18n } from '@kbn/i18n';
import { uniq } from 'lodash';
import { SeriesType } from '@kbn/expression-xy-plugin/common';
import { DatasourceLayers, OperationMetadata, VisualizationType } from '../types';
import {
State,
@ -17,6 +16,7 @@ import {
XYLayerConfig,
XYDataLayerConfig,
XYReferenceLineLayerConfig,
SeriesType,
} from './types';
import { isHorizontalChart } from './state_helpers';
import { layerTypes } from '..';

View file

@ -16,9 +16,9 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isEqual } from 'lodash';
import { AxesSettingsConfig, AxisExtentConfig, YScaleType } from '@kbn/expression-xy-plugin/common';
import { AxisExtentConfig, YScaleType } from '@kbn/expression-xy-plugin/common';
import { ToolbarButtonProps } from '@kbn/kibana-react-plugin/public';
import { XYLayerConfig } from '../types';
import { XYLayerConfig, AxesSettingsConfig } from '../types';
import {
ToolbarPopover,
useDebouncedValue,

View file

@ -10,9 +10,8 @@ import { i18n } from '@kbn/i18n';
import { EuiButtonGroup, EuiFormRow, htmlIdGenerator } from '@elastic/eui';
import type { PaletteRegistry } from '@kbn/coloring';
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
import { YAxisMode, ExtendedYConfig } from '@kbn/expression-xy-plugin/common';
import type { VisualizationDimensionEditorProps } from '../../types';
import { State, XYState, XYDataLayerConfig } from '../types';
import { State, XYState, XYDataLayerConfig, YConfig, YAxisMode } from '../types';
import { FormatFactory } from '../../../common';
import { getSeriesColor, isHorizontalChart } from '../state_helpers';
import { ColorPicker } from './color_picker';
@ -81,7 +80,7 @@ export function DataDimensionEditor(
const axisMode = localYConfig?.axisMode || 'auto';
const setConfig = useCallback(
(yConfig: Partial<ExtendedYConfig> | undefined) => {
(yConfig: Partial<YConfig> | undefined) => {
if (yConfig == null) {
return;
}

View file

@ -9,10 +9,10 @@ import React, { memo, useCallback, useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { Position, ScaleType } from '@elastic/charts';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { AxesSettingsConfig, AxisExtentConfig } from '@kbn/expression-xy-plugin/common';
import { AxisExtentConfig } from '@kbn/expression-xy-plugin/common';
import { LegendSize } from '@kbn/visualizations-plugin/public';
import type { VisualizationToolbarProps, FramePublicAPI } from '../../types';
import { State, XYState } from '../types';
import { State, XYState, AxesSettingsConfig } from '../types';
import { isHorizontalChart } from '../state_helpers';
import { hasNumericHistogramDimension, LegendSettingsPopover } from '../../shared_components';
import { AxisSettingsPopover } from './axis_settings_popover';

View file

@ -8,10 +8,9 @@
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiIcon, EuiPopover, EuiSelectable, EuiText, EuiPopoverTitle } from '@elastic/eui';
import { SeriesType } from '@kbn/expression-xy-plugin/common';
import { ToolbarButton } from '@kbn/kibana-react-plugin/public';
import type { VisualizationLayerWidgetProps, VisualizationType } from '../../types';
import { State, visualizationTypes } from '../types';
import { State, visualizationTypes, SeriesType } from '../types';
import { isHorizontalChart, isHorizontalSeries } from '../state_helpers';
import { trackUiEvent } from '../../lens_ui_telemetry';
import { StaticHeader } from '../../shared_components';

View file

@ -9,9 +9,9 @@ import React, { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
import type { PaletteRegistry } from '@kbn/coloring';
import { FillStyle, ExtendedYConfig } from '@kbn/expression-xy-plugin/common';
import { FillStyle } from '@kbn/expression-xy-plugin/common';
import type { VisualizationDimensionEditorProps } from '../../../types';
import { State, XYState, XYReferenceLineLayerConfig } from '../../types';
import { State, XYState, XYReferenceLineLayerConfig, YConfig } from '../../types';
import { FormatFactory } from '../../../../common';
import { ColorPicker } from '../color_picker';
@ -52,7 +52,7 @@ export const ReferenceLinePanel = (
);
const setConfig = useCallback(
(yConfig: Partial<ExtendedYConfig> | undefined) => {
(yConfig: Partial<YConfig> | undefined) => {
if (yConfig == null) {
return;
}
@ -108,7 +108,7 @@ export const ReferenceLinePanel = (
interface LabelConfigurationOptions {
isHorizontal: boolean;
axisMode: ExtendedYConfig['axisMode'];
axisMode: YConfig['axisMode'];
}
function getFillPositionOptions({ isHorizontal, axisMode }: LabelConfigurationOptions) {
@ -154,8 +154,8 @@ export const FillSetting = ({
setConfig,
isHorizontal,
}: {
currentConfig?: ExtendedYConfig;
setConfig: (yConfig: Partial<ExtendedYConfig> | undefined) => void;
currentConfig?: YConfig;
setConfig: (yConfig: Partial<YConfig> | undefined) => void;
isHorizontal: boolean;
}) => {
return (

View file

@ -8,7 +8,8 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
import { IconPosition, YAxisMode } from '@kbn/expression-xy-plugin/common';
import { IconPosition } from '@kbn/expression-xy-plugin/common';
import { YAxisMode } from '../../types';
import { TooltipWrapper } from '../../../shared_components';
import { hasIcon, IconSelect, IconSet } from './icon_select';

View file

@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n';
import { partition } from 'lodash';
import { Position } from '@elastic/charts';
import type { PaletteOutput } from '@kbn/coloring';
import type { SeriesType } from '@kbn/expression-xy-plugin/common';
import {
SuggestionRequest,
VisualizationSuggestion,
@ -17,7 +16,14 @@ import {
TableSuggestion,
TableChangeType,
} from '../types';
import { State, XYState, visualizationTypes, XYLayerConfig, XYDataLayerConfig } from './types';
import {
State,
XYState,
visualizationTypes,
XYLayerConfig,
XYDataLayerConfig,
SeriesType,
} from './types';
import { layerTypes } from '../../common';
import { getIconForSeries } from './state_helpers';
import { getDataLayers, isDataLayer } from './visualization_helpers';

View file

@ -29,6 +29,7 @@ import {
TypedLensByValueInput,
XYCurveType,
XYState,
YAxisMode,
} from '@kbn/lens-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { PersistableFilter } from '@kbn/lens-plugin/common';
@ -900,8 +901,8 @@ export class LensAttributes {
this.layerConfigs[0].indexPattern.fieldFormatMap[
this.layerConfigs[0].selectedMetricField
]?.id
? 'left'
: 'right',
? ('left' as YAxisMode)
: ('right' as YAxisMode),
},
],
xAccessor: `x-axis-column-layer${index}`,

View file

@ -13,7 +13,7 @@ import type {
FieldBasedIndexPatternColumn,
SeriesType,
OperationType,
ExtendedYConfig,
YConfig,
} from '@kbn/lens-plugin/public';
import type { PersistableFilter } from '@kbn/lens-plugin/common';
@ -84,7 +84,7 @@ export interface SeriesConfig {
hasOperationType: boolean;
palette?: PaletteOutput;
yTitle?: string;
yConfig?: ExtendedYConfig[];
yConfig?: YConfig[];
query?: { query: string; language: 'kuery' };
}

View file

@ -3402,10 +3402,6 @@
"expressionXY.axisExtentConfig.help": "Configurer les étendues daxe du graphique xy",
"expressionXY.axisExtentConfig.lowerBound.help": "Limite inférieure",
"expressionXY.axisExtentConfig.upperBound.help": "Limite supérieure",
"expressionXY.axisTitlesVisibilityConfig.help": "Configurer laspect des titres daxe du graphique xy",
"expressionXY.axisTitlesVisibilityConfig.x.help": "Spécifie si le titre de l'axe X est visible ou non.",
"expressionXY.axisTitlesVisibilityConfig.yLeft.help": "Spécifie si le titre de l'axe Y de gauche est visible ou non.",
"expressionXY.axisTitlesVisibilityConfig.yRight.help": "Spécifie si le titre de l'axe Y de droite est visible ou non.",
"expressionXY.dataLayer.accessors.help": "Les colonnes à afficher sur laxe y.",
"expressionXY.layer.columnToLabel.help": "Paires clé-valeur JSON de lID de colonne pour létiquette",
"expressionXY.dataLayer.help": "Configurer un calque dans le graphique xy",
@ -3417,15 +3413,6 @@
"expressionXY.dataLayer.splitAccessor.help": "Colonne selon laquelle effectuer la division",
"expressionXY.dataLayer.xAccessor.help": "Axe X",
"expressionXY.dataLayer.xScaleType.help": "Type déchelle de laxe x",
"expressionXY.dataLayer.yConfig.help": "Configuration supplémentaire pour les axes y",
"expressionXY.gridlinesConfig.help": "Configurer laspect du quadrillage du graphique xy",
"expressionXY.gridlinesConfig.x.help": "Spécifie si le quadrillage de l'axe X est visible ou non.",
"expressionXY.gridlinesConfig.yLeft.help": "Spécifie si le quadrillage de l'axe Y de gauche est visible ou non.",
"expressionXY.gridlinesConfig.yRight.help": "Spécifie si le quadrillage de l'axe Y de droite est visible ou non.",
"expressionXY.labelsOrientationConfig.help": "Configurer lorientation des étiquettes de coche du graphique xy",
"expressionXY.labelsOrientationConfig.x.help": "Spécifie l'orientation des étiquettes de l'axe X.",
"expressionXY.labelsOrientationConfig.yLeft.help": "Spécifie l'orientation des étiquettes de l'axe Y de gauche.",
"expressionXY.labelsOrientationConfig.yRight.help": "Spécifie l'orientation des étiquettes de l'axe Y de droite.",
"expressionXY.legend.filterForValueButtonAriaLabel": "Filtrer sur la valeur",
"expressionXY.legend.filterOptionsLegend": "{legendDataLabel}, options de filtre",
"expressionXY.legend.filterOutValueButtonAriaLabel": "Exclure la valeur",
@ -3442,11 +3429,6 @@
"expressionXY.legendConfig.verticalAlignment.help": "Spécifie l'alignement vertical de la légende lorsqu'elle est affichée à l'intérieur du graphique.",
"expressionXY.referenceLineLayer.accessors.help": "Les colonnes à afficher sur laxe y.",
"expressionXY.referenceLineLayer.help": "Configurer une ligne de référence dans le graphique xy",
"expressionXY.referenceLineLayer.yConfig.help": "Configuration supplémentaire pour les axes y",
"expressionXY.tickLabelsConfig.help": "Configurer laspect des étiquettes de coche du graphique xy",
"expressionXY.tickLabelsConfig.x.help": "Spécifie si les étiquettes de graduation de l'axe X sont visibles ou non.",
"expressionXY.tickLabelsConfig.yLeft.help": "Spécifie si les étiquettes de graduation de l'axe Y de gauche sont visibles ou non.",
"expressionXY.tickLabelsConfig.yRight.help": "Spécifie si les étiquettes de graduation de l'axe Y de droite sont visibles ou non.",
"expressionXY.xyChart.emptyXLabel": "(vide)",
"expressionXY.xyChart.iconSelect.alertIconLabel": "Alerte",
"expressionXY.xyChart.iconSelect.asteriskIconLabel": "Astérisque",
@ -3463,39 +3445,20 @@
"expressionXY.xyChart.iconSelect.tagIconLabel": "Balise",
"expressionXY.xyChart.iconSelect.triangleIconLabel": "Triangle",
"expressionXY.xyVis.ariaLabel.help": "Spécifie lattribut aria-label du graphique xy",
"expressionXY.xyVis.axisTitlesVisibilitySettings.help": "Afficher les titres des axes X et Y",
"expressionXY.xyVis.curveType.help": "Définir de quelle façon le type de courbe est rendu pour un graphique linéaire",
"expressionXY.xyVis.endValue.help": "Valeur de fin",
"expressionXY.xyVis.fillOpacity.help": "Définir l'opacité du remplissage du graphique en aires",
"expressionXY.xyVis.fittingFunction.help": "Définir le mode de traitement des valeurs manquantes",
"expressionXY.xyVis.gridlinesVisibilitySettings.help": "Afficher le quadrillage des axes X et Y",
"expressionXY.xyVis.help": "Graphique X/Y",
"expressionXY.xyVis.hideEndzones.help": "Masquer les marqueurs de zone de fin pour les données partielles",
"expressionXY.xyVis.labelsOrientation.help": "Définit la rotation des étiquettes des axes",
"expressionXY.layeredXyVis.layers.help": "Calques de série visuelle",
"expressionXY.xyVis.legend.help": "Configurez la légende du graphique.",
"expressionXY.xyVis.logDatatable.breakDown": "Répartir par",
"expressionXY.xyVis.logDatatable.metric": "Axe vertical",
"expressionXY.xyVis.logDatatable.x": "Axe horizontal",
"expressionXY.xyVis.renderer.help": "Outil de rendu de graphique X/Y",
"expressionXY.xyVis.tickLabelsVisibilitySettings.help": "Afficher les étiquettes de graduation des axes X et Y",
"expressionXY.xyVis.valueLabels.help": "Mode des étiquettes de valeur",
"expressionXY.xyVis.valuesInLegend.help": "Afficher les valeurs dans la légende",
"expressionXY.xyVis.xTitle.help": "Titre de l'axe X",
"expressionXY.xyVis.yLeftExtent.help": "Portée de l'axe Y de gauche",
"expressionXY.xyVis.yLeftTitle.help": "Titre de l'axe Y de gauche",
"expressionXY.xyVis.yRightExtent.help": "Portée de l'axe Y de droite",
"expressionXY.xyVis.yRightTitle.help": "Titre de l'axe Y de droite",
"expressionXY.yConfig.axisMode.help": "Le mode axe de lindicateur",
"expressionXY.yConfig.color.help": "La couleur des séries",
"expressionXY.yConfig.fill.help": "Remplir",
"expressionXY.yConfig.forAccessor.help": "Laccesseur auquel cette configuration sapplique",
"expressionXY.yConfig.help": "Configurer le comportement de lindicateur daxe y dun graphique xy",
"expressionXY.yConfig.icon.help": "Icône facultative utilisée pour les lignes de référence",
"expressionXY.yConfig.iconPosition.help": "Le placement de licône pour la ligne de référence",
"expressionXY.yConfig.lineStyle.help": "Le style de la ligne de référence",
"expressionXY.yConfig.lineWidth.help": "La largeur de la ligne de référence",
"expressionXY.yConfig.textVisibility.help": "Visibilité de létiquette sur la ligne de référence",
"fieldFormats.advancedSettings.format.bytesFormat.numeralFormatLinkText": "Format numérique",
"fieldFormats.advancedSettings.format.bytesFormatText": "{numeralFormatLink} par défaut pour le format \"octets\"",
"fieldFormats.advancedSettings.format.bytesFormatTitle": "Format octets",

View file

@ -3501,10 +3501,6 @@
"expressionXY.axisExtentConfig.help": "xyグラフの軸範囲を構成",
"expressionXY.axisExtentConfig.lowerBound.help": "下界",
"expressionXY.axisExtentConfig.upperBound.help": "上界",
"expressionXY.axisTitlesVisibilityConfig.help": "xyグラフの軸タイトル表示を構成",
"expressionXY.axisTitlesVisibilityConfig.x.help": "x軸のタイトルを表示するかどうかを指定します。",
"expressionXY.axisTitlesVisibilityConfig.yLeft.help": "左y軸のタイトルを表示するかどうかを指定します。",
"expressionXY.axisTitlesVisibilityConfig.yRight.help": "右y軸のタイトルを表示するかどうかを指定します。",
"expressionXY.dataLayer.accessors.help": "y軸に表示する列。",
"expressionXY.layer.columnToLabel.help": "ラベリングする列IDのJSONキー値のペア",
"expressionXY.dataLayer.help": "xyグラフでレイヤーを構成",
@ -3516,15 +3512,6 @@
"expressionXY.dataLayer.splitAccessor.help": "分割の基準となる列",
"expressionXY.dataLayer.xAccessor.help": "X 軸",
"expressionXY.dataLayer.xScaleType.help": "x軸の目盛タイプ",
"expressionXY.dataLayer.yConfig.help": "y軸の詳細構成",
"expressionXY.gridlinesConfig.help": "xyグラフのグリッド線表示を構成",
"expressionXY.gridlinesConfig.x.help": "x 軸のグリッド線を表示するかどうかを指定します。",
"expressionXY.gridlinesConfig.yLeft.help": "左y軸のグリッド線を表示するかどうかを指定します。",
"expressionXY.gridlinesConfig.yRight.help": "右y軸のグリッド線を表示するかどうかを指定します。",
"expressionXY.labelsOrientationConfig.help": "xyグラフのティックラベルの向きを構成",
"expressionXY.labelsOrientationConfig.x.help": "x軸のラベルの向きを指定します。",
"expressionXY.labelsOrientationConfig.yLeft.help": "左y軸のラベルの向きを指定します。",
"expressionXY.labelsOrientationConfig.yRight.help": "右y軸のラベルの向きを指定します。",
"expressionXY.legend.filterForValueButtonAriaLabel": "値でフィルター",
"expressionXY.legend.filterOptionsLegend": "{legendDataLabel}、フィルターオプション",
"expressionXY.legend.filterOutValueButtonAriaLabel": "値を除外",
@ -3541,11 +3528,6 @@
"expressionXY.legendConfig.verticalAlignment.help": "凡例がグラフ内に表示されるときに凡例の縦の配置を指定します。",
"expressionXY.referenceLineLayer.accessors.help": "y軸に表示する列。",
"expressionXY.referenceLineLayer.help": "xyグラフで基準線を構成",
"expressionXY.referenceLineLayer.yConfig.help": "y軸の詳細構成",
"expressionXY.tickLabelsConfig.help": "xyグラフのティックラベルの表示を構成",
"expressionXY.tickLabelsConfig.x.help": "x軸の目盛ラベルを表示するかどうかを指定します。",
"expressionXY.tickLabelsConfig.yLeft.help": "左y軸の目盛ラベルを表示するかどうかを指定します。",
"expressionXY.tickLabelsConfig.yRight.help": "右y軸の目盛ラベルを表示するかどうかを指定します。",
"expressionXY.xyChart.emptyXLabel": "(空)",
"expressionXY.xyChart.iconSelect.alertIconLabel": "アラート",
"expressionXY.xyChart.iconSelect.asteriskIconLabel": "アスタリスク",
@ -3562,39 +3544,20 @@
"expressionXY.xyChart.iconSelect.tagIconLabel": "タグ",
"expressionXY.xyChart.iconSelect.triangleIconLabel": "三角形",
"expressionXY.xyVis.ariaLabel.help": "xyグラフのariaラベルを指定します",
"expressionXY.xyVis.axisTitlesVisibilitySettings.help": "xおよびy軸のタイトルを表示",
"expressionXY.xyVis.curveType.help": "折れ線グラフで曲線タイプをレンダリングする方法を定義します",
"expressionXY.xyVis.endValue.help": "終了値",
"expressionXY.xyVis.fillOpacity.help": "エリアグラフの塗りつぶしの透明度を定義",
"expressionXY.xyVis.fittingFunction.help": "欠測値の処理方法を定義",
"expressionXY.xyVis.gridlinesVisibilitySettings.help": "xおよびy軸のグリッド線を表示",
"expressionXY.xyVis.help": "X/Y チャート",
"expressionXY.xyVis.hideEndzones.help": "部分データの終了ゾーンマーカーを非表示",
"expressionXY.xyVis.labelsOrientation.help": "軸ラベルの回転を定義します",
"expressionXY.layeredXyVis.layers.help": "視覚的な系列のレイヤー",
"expressionXY.xyVis.legend.help": "チャートの凡例を構成します。",
"expressionXY.xyVis.logDatatable.breakDown": "内訳の基準",
"expressionXY.xyVis.logDatatable.metric": "縦軸",
"expressionXY.xyVis.logDatatable.x": "横軸",
"expressionXY.xyVis.renderer.help": "X/Y チャートを再レンダリング",
"expressionXY.xyVis.tickLabelsVisibilitySettings.help": "xおよびy軸の目盛ラベルを表示",
"expressionXY.xyVis.valueLabels.help": "値ラベルモード",
"expressionXY.xyVis.valuesInLegend.help": "凡例に値を表示",
"expressionXY.xyVis.xTitle.help": "x軸のタイトル",
"expressionXY.xyVis.yLeftExtent.help": "Y左軸範囲",
"expressionXY.xyVis.yLeftTitle.help": "左y軸のタイトル",
"expressionXY.xyVis.yRightExtent.help": "Y右軸範囲",
"expressionXY.xyVis.yRightTitle.help": "右 y 軸のタイトル",
"expressionXY.yConfig.axisMode.help": "メトリックの軸モード",
"expressionXY.yConfig.color.help": "系列の色",
"expressionXY.yConfig.fill.help": "塗りつぶし",
"expressionXY.yConfig.forAccessor.help": "この構成のアクセサー",
"expressionXY.yConfig.help": "xyグラフのy軸メトリックの動作を構成",
"expressionXY.yConfig.icon.help": "基準線で使用される任意のアイコン",
"expressionXY.yConfig.iconPosition.help": "基準線のアイコンの配置",
"expressionXY.yConfig.lineStyle.help": "基準線のスタイル",
"expressionXY.yConfig.lineWidth.help": "基準線の幅",
"expressionXY.yConfig.textVisibility.help": "基準線のラベルの表示",
"fieldFormats.advancedSettings.format.bytesFormat.numeralFormatLinkText": "数字フォーマット",
"fieldFormats.advancedSettings.format.bytesFormatText": "「バイト」フォーマットのデフォルト{numeralFormatLink}です",
"fieldFormats.advancedSettings.format.bytesFormatTitle": "バイトフォーマット",

View file

@ -3506,10 +3506,6 @@
"expressionXY.axisExtentConfig.help": "配置 xy 图表的轴范围",
"expressionXY.axisExtentConfig.lowerBound.help": "下边界",
"expressionXY.axisExtentConfig.upperBound.help": "上边界",
"expressionXY.axisTitlesVisibilityConfig.help": "配置 xy 图表的轴标题外观",
"expressionXY.axisTitlesVisibilityConfig.x.help": "指定 x 轴的标题是否可见。",
"expressionXY.axisTitlesVisibilityConfig.yLeft.help": "指定左侧 y 轴的标题是否可见。",
"expressionXY.axisTitlesVisibilityConfig.yRight.help": "指定右侧 y 轴的标题是否可见。",
"expressionXY.dataLayer.accessors.help": "要在 y 轴上显示的列。",
"expressionXY.layer.columnToLabel.help": "要标记的列 ID 的 JSON 键值对",
"expressionXY.dataLayer.help": "配置 xy 图表中的图层",
@ -3521,15 +3517,6 @@
"expressionXY.dataLayer.splitAccessor.help": "拆分要依据的列",
"expressionXY.dataLayer.xAccessor.help": "X 轴",
"expressionXY.dataLayer.xScaleType.help": "x 轴的缩放类型",
"expressionXY.dataLayer.yConfig.help": "y 轴的其他配置",
"expressionXY.gridlinesConfig.help": "配置 xy 图表的网格线外观",
"expressionXY.gridlinesConfig.x.help": "指定 x 轴的网格线是否可见。",
"expressionXY.gridlinesConfig.yLeft.help": "指定左侧 y 轴的网格线是否可见。",
"expressionXY.gridlinesConfig.yRight.help": "指定右侧 y 轴的网格线是否可见。",
"expressionXY.labelsOrientationConfig.help": "配置 xy 图表的刻度标签方向",
"expressionXY.labelsOrientationConfig.x.help": "指定 x 轴的标签方向。",
"expressionXY.labelsOrientationConfig.yLeft.help": "指定左 y 轴的标签方向。",
"expressionXY.labelsOrientationConfig.yRight.help": "指定右 y 轴的标签方向。",
"expressionXY.legend.filterForValueButtonAriaLabel": "筛留值",
"expressionXY.legend.filterOptionsLegend": "{legendDataLabel}, 筛选选项",
"expressionXY.legend.filterOutValueButtonAriaLabel": "筛除值",
@ -3546,11 +3533,6 @@
"expressionXY.legendConfig.verticalAlignment.help": "指定图例显示在图表内时垂直对齐。",
"expressionXY.referenceLineLayer.accessors.help": "要在 y 轴上显示的列。",
"expressionXY.referenceLineLayer.help": "配置 xy 图表中的参考线",
"expressionXY.referenceLineLayer.yConfig.help": "y 轴的其他配置",
"expressionXY.tickLabelsConfig.help": "配置 xy 图表的刻度标签外观",
"expressionXY.tickLabelsConfig.x.help": "指定 x 轴的刻度标签是否可见。",
"expressionXY.tickLabelsConfig.yLeft.help": "指定左侧 y 轴的刻度标签是否可见。",
"expressionXY.tickLabelsConfig.yRight.help": "指定右侧 y 轴的刻度标签是否可见。",
"expressionXY.xyChart.emptyXLabel": "(空)",
"expressionXY.xyChart.iconSelect.alertIconLabel": "告警",
"expressionXY.xyChart.iconSelect.asteriskIconLabel": "星号",
@ -3567,39 +3549,20 @@
"expressionXY.xyChart.iconSelect.tagIconLabel": "标签",
"expressionXY.xyChart.iconSelect.triangleIconLabel": "三角形",
"expressionXY.xyVis.ariaLabel.help": "指定 xy 图表的 aria 标签",
"expressionXY.xyVis.axisTitlesVisibilitySettings.help": "显示 x 和 y 轴标题",
"expressionXY.xyVis.curveType.help": "定义为折线图渲染曲线类型的方式",
"expressionXY.xyVis.endValue.help": "结束值",
"expressionXY.xyVis.fillOpacity.help": "定义面积图填充透明度",
"expressionXY.xyVis.fittingFunction.help": "定义处理缺失值的方式",
"expressionXY.xyVis.gridlinesVisibilitySettings.help": "显示 x 和 y 轴网格线",
"expressionXY.xyVis.help": "X/Y 图表",
"expressionXY.xyVis.hideEndzones.help": "隐藏部分数据的末日区域标记",
"expressionXY.xyVis.labelsOrientation.help": "定义轴标签的旋转",
"expressionXY.layeredXyVis.layers.help": "可视序列的图层",
"expressionXY.xyVis.legend.help": "配置图表图例。",
"expressionXY.xyVis.logDatatable.breakDown": "细分方式",
"expressionXY.xyVis.logDatatable.metric": "垂直轴",
"expressionXY.xyVis.logDatatable.x": "水平轴",
"expressionXY.xyVis.renderer.help": "X/Y 图表呈现器",
"expressionXY.xyVis.tickLabelsVisibilitySettings.help": "显示 x 和 y 轴刻度标签",
"expressionXY.xyVis.valueLabels.help": "值标签模式",
"expressionXY.xyVis.valuesInLegend.help": "在图例中显示值",
"expressionXY.xyVis.xTitle.help": "X 轴标题",
"expressionXY.xyVis.yLeftExtent.help": "左侧 Y 轴范围",
"expressionXY.xyVis.yLeftTitle.help": "左侧 Y 轴标题",
"expressionXY.xyVis.yRightExtent.help": "右侧 Y 轴范围",
"expressionXY.xyVis.yRightTitle.help": "右侧 Y 轴标题",
"expressionXY.yConfig.axisMode.help": "指标的轴模式",
"expressionXY.yConfig.color.help": "序列的颜色",
"expressionXY.yConfig.fill.help": "填充",
"expressionXY.yConfig.forAccessor.help": "此配置针对的访问器",
"expressionXY.yConfig.help": "配置 xy 图表的 y 轴指标的行为",
"expressionXY.yConfig.icon.help": "用于参考线的可选图标",
"expressionXY.yConfig.iconPosition.help": "参考线图标的位置",
"expressionXY.yConfig.lineStyle.help": "参考线的样式",
"expressionXY.yConfig.lineWidth.help": "参考线的宽度",
"expressionXY.yConfig.textVisibility.help": "参考线上标签的可见性",
"fieldFormats.advancedSettings.format.bytesFormat.numeralFormatLinkText": "数值格式",
"fieldFormats.advancedSettings.format.bytesFormatText": "“字节”格式的默认{numeralFormatLink}",
"fieldFormats.advancedSettings.format.bytesFormatTitle": "字节格式",

View file

@ -274,13 +274,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
let data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart');
expect(data?.axes?.y?.[0].title).to.eql(axisTitle);
expect(data?.axes?.y?.[1].title).to.eql(axisTitle);
// hide the gridlines
await testSubjects.click('lnsshowyLeftAxisGridlines');
data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart');
expect(data?.axes?.y?.[0].gridlines.length).to.eql(0);
expect(data?.axes?.y?.[1].gridlines.length).to.eql(0);
});
it('should transition from a multi-layer stacked bar to donut chart using suggestions', async () => {