[Lens] data layer & reference layer as separate types (#126885)

* [Lens] data layer & reference layer as separate types

* types better (thanks Marco )

* CR comment
This commit is contained in:
Marta Bondyra 2022-03-08 21:06:23 +01:00 committed by GitHub
parent 79905bac04
commit 849542e9f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 534 additions and 363 deletions

View file

@ -7,7 +7,6 @@
import rison from 'rison-node';
import type { TimeRange } from '../../../../src/plugins/data/common/query';
import { LayerType } from './types';
export const PLUGIN_ID = 'lens';
export const APP_ID = 'lens';
@ -43,10 +42,10 @@ export const LegendDisplay = {
HIDE: 'hide',
} as const;
export const layerTypes: Record<string, LayerType> = {
export const layerTypes = {
DATA: 'data',
REFERENCELINE: 'referenceLine',
};
} as const;
// might collide with user-supplied field names, try to make as unique as possible
export const DOCUMENT_FIELD_NAME = '___records___';

View file

@ -5,30 +5,28 @@
* 2.0.
*/
import type { PaletteOutput } from '../../../../../../src/plugins/charts/common';
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
import type { LayerType } from '../../types';
import { layerTypes } from '../../constants';
import { axisConfig, YConfig } from './axis_config';
import type { SeriesType } from './series_type';
import { layerTypes } from '../../../constants';
import type { PaletteOutput } from '../../../../../../../src/plugins/charts/common';
import type { ExpressionFunctionDefinition } from '../../../../../../../src/plugins/expressions/common';
import { axisConfig, YConfig } from '../axis_config';
import type { SeriesType } from '../series_type';
export interface XYLayerConfig {
hide?: boolean;
export interface XYDataLayerConfig {
layerId: string;
xAccessor?: string;
layerType: typeof layerTypes.DATA;
accessors: string[];
yConfig?: YConfig[];
seriesType: SeriesType;
xAccessor?: string;
hide?: boolean;
yConfig?: YConfig[];
splitAccessor?: string;
palette?: PaletteOutput;
layerType: LayerType;
}
export interface ValidLayer extends XYDataLayerConfig {
xAccessor: NonNullable<XYDataLayerConfig['xAccessor']>;
}
export interface ValidLayer extends XYLayerConfig {
xAccessor: NonNullable<XYLayerConfig['xAccessor']>;
}
export type LayerArgs = XYLayerConfig & {
export type DataLayerArgs = XYDataLayerConfig & {
columnToLabel?: string; // Actually a JSON key-value pair
yScaleType: 'time' | 'linear' | 'log' | 'sqrt';
xScaleType: 'time' | 'linear' | 'ordinal';
@ -37,17 +35,17 @@ export type LayerArgs = XYLayerConfig & {
palette: PaletteOutput;
};
export type LayerConfigResult = LayerArgs & { type: 'lens_xy_layer' };
export type DataLayerConfigResult = DataLayerArgs & { type: 'lens_xy_data_layer' };
export const layerConfig: ExpressionFunctionDefinition<
'lens_xy_layer',
export const dataLayerConfig: ExpressionFunctionDefinition<
'lens_xy_data_layer',
null,
LayerArgs,
LayerConfigResult
DataLayerArgs,
DataLayerConfigResult
> = {
name: 'lens_xy_layer',
name: 'lens_xy_data_layer',
aliases: [],
type: 'lens_xy_layer',
type: 'lens_xy_data_layer',
help: `Configure a layer in the xy chart`,
inputTypes: ['null'],
args: {
@ -60,7 +58,7 @@ export const layerConfig: ExpressionFunctionDefinition<
types: ['string'],
help: '',
},
layerType: { types: ['string'], options: Object.values(layerTypes), help: '' },
layerType: { types: ['string'], options: [layerTypes.DATA], help: '' },
seriesType: {
types: ['string'],
options: [
@ -115,9 +113,9 @@ export const layerConfig: ExpressionFunctionDefinition<
types: ['palette'],
},
},
fn: function fn(input: unknown, args: LayerArgs) {
fn: function fn(input: unknown, args: DataLayerArgs) {
return {
type: 'lens_xy_layer',
type: 'lens_xy_data_layer',
...args,
};
},

View file

@ -0,0 +1,12 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { XYDataLayerConfig } from './data_layer_config';
import { XYReferenceLineLayerConfig } from './reference_line_layer_config';
export * from './data_layer_config';
export * from './reference_line_layer_config';
export type XYLayerConfig = XYDataLayerConfig | XYReferenceLineLayerConfig;

View file

@ -0,0 +1,64 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ExpressionFunctionDefinition } from '../../../../../../../src/plugins/expressions/common';
import { layerTypes } from '../../../constants';
import { YConfig } from '../axis_config';
export interface XYReferenceLineLayerConfig {
layerId: string;
layerType: typeof layerTypes.REFERENCELINE;
accessors: string[];
yConfig?: YConfig[];
}
export type ReferenceLineLayerArgs = XYReferenceLineLayerConfig & {
columnToLabel?: string;
};
export type ReferenceLineLayerConfigResult = ReferenceLineLayerArgs & {
type: 'lens_xy_referenceLine_layer';
};
export const referenceLineLayerConfig: ExpressionFunctionDefinition<
'lens_xy_referenceLine_layer',
null,
ReferenceLineLayerArgs,
ReferenceLineLayerConfigResult
> = {
name: 'lens_xy_referenceLine_layer',
aliases: [],
type: 'lens_xy_referenceLine_layer',
help: `Configure a layer in the xy chart`,
inputTypes: ['null'],
args: {
layerId: {
types: ['string'],
help: '',
},
layerType: { types: ['string'], options: [layerTypes.REFERENCELINE], help: '' },
accessors: {
types: ['string'],
help: 'The columns to display on the y axis.',
multi: true,
},
yConfig: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
types: ['lens_xy_yConfig' as any],
help: 'Additional configuration for y axes',
multi: true,
},
columnToLabel: {
types: ['string'],
help: 'JSON key-value pairs of column ID to label',
},
},
fn: function fn(input: unknown, args: ReferenceLineLayerArgs) {
return {
type: 'lens_xy_referenceLine_layer',
...args,
};
},
};

View file

@ -8,7 +8,7 @@
import type { AxisExtentConfigResult, AxisTitlesVisibilityConfigResult } from './axis_config';
import type { FittingFunction } from './fitting_function';
import type { GridlinesConfigResult } from './grid_lines_config';
import type { LayerArgs } from './layer_config';
import type { DataLayerArgs } from './layer_config';
import type { LegendConfigResult } from './legend_config';
import type { TickLabelsConfigResult } from './tick_labels_config';
import type { LabelsOrientationConfigResult } from './labels_orientation_config';
@ -27,7 +27,7 @@ export interface XYArgs {
yRightExtent: AxisExtentConfigResult;
legend: LegendConfigResult;
valueLabels: ValueLabelConfig;
layers: LayerArgs[];
layers: DataLayerArgs[];
fittingFunction?: FittingFunction;
axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult;
tickLabelsVisibilitySettings?: TickLabelsConfigResult;

View file

@ -117,7 +117,7 @@ export const xyChart: ExpressionFunctionDefinition<
},
layers: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
types: ['lens_xy_layer'] as any,
types: ['lens_xy_data_layer', 'lens_xy_referenceLine_layer'] as any,
help: 'Layers of visual series',
multi: true,
},

View file

@ -15,7 +15,13 @@ import type {
import type { Datatable } from '../../../../src/plugins/expressions/common';
import type { PaletteContinuity } from '../../../../src/plugins/charts/common';
import type { PaletteOutput } from '../../../../src/plugins/charts/common';
import { CategoryDisplay, LegendDisplay, NumberDisplay, PieChartTypes } from './constants';
import {
CategoryDisplay,
layerTypes,
LegendDisplay,
NumberDisplay,
PieChartTypes,
} from './constants';
export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
@ -73,7 +79,7 @@ export type RequiredPaletteParamTypes = Required<CustomPaletteParams> & {
maxSteps?: number;
};
export type LayerType = 'data' | 'referenceLine';
export type LayerType = typeof layerTypes[keyof typeof layerTypes];
// Shared by XY Chart and Heatmap as for now
export type ValueLabelConfig = 'hide' | 'inside' | 'outside';

View file

@ -14,7 +14,10 @@ import {
} from '../common/expressions/xy_chart/axis_config';
import { gridlinesConfig } from '../common/expressions/xy_chart/grid_lines_config';
import { labelsOrientationConfig } from '../common/expressions/xy_chart/labels_orientation_config';
import { layerConfig } from '../common/expressions/xy_chart/layer_config';
import {
dataLayerConfig,
referenceLineLayerConfig,
} from '../common/expressions/xy_chart/layer_config';
import { legendConfig } from '../common/expressions/xy_chart/legend_config';
import { tickLabelsConfig } from '../common/expressions/xy_chart/tick_labels_config';
import { xyChart } from '../common/expressions/xy_chart/xy_chart';
@ -43,7 +46,8 @@ export const setupExpressions = (
counterRate,
metricChart,
yAxisConfig,
layerConfig,
dataLayerConfig,
referenceLineLayerConfig,
formatColumn,
legendConfig,
renameColumns,

View file

@ -127,7 +127,7 @@ Object {
"linear",
],
},
"function": "lens_xy_layer",
"function": "lens_xy_data_layer",
"type": "function",
},
],

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { LayerArgs } from '../../common/expressions';
import { DataLayerArgs } from '../../common/expressions';
import { layerTypes } from '../../common';
import { Datatable } from '../../../../../src/plugins/expressions/public';
import { getAxesConfiguration } from './axes_configuration';
@ -219,7 +219,7 @@ describe('axes_configuration', () => {
},
};
const sampleLayer: LayerArgs = {
const sampleLayer: DataLayerArgs = {
layerId: 'first',
layerType: layerTypes.DATA,
seriesType: 'line',

View file

@ -6,7 +6,7 @@
*/
import { FormatFactory } from '../../common';
import { AxisExtentConfig, XYLayerConfig } from '../../common/expressions';
import { AxisExtentConfig, XYDataLayerConfig } from '../../common/expressions';
import { Datatable } from '../../../../../src/plugins/expressions/public';
import type {
IFieldFormat,
@ -33,7 +33,7 @@ export function isFormatterCompatible(
return formatter1.id === formatter2.id;
}
export function groupAxesByType(layers: XYLayerConfig[], tables?: Record<string, Datatable>) {
export function groupAxesByType(layers: XYDataLayerConfig[], tables?: Record<string, Datatable>) {
const series: {
auto: FormattedMetric[];
left: FormattedMetric[];
@ -97,7 +97,7 @@ export function groupAxesByType(layers: XYLayerConfig[], tables?: Record<string,
}
export function getAxesConfiguration(
layers: XYLayerConfig[],
layers: XYDataLayerConfig[],
shouldRotate: boolean,
tables?: Record<string, Datatable>,
formatFactory?: FormatFactory

View file

@ -7,11 +7,11 @@
import { getColorAssignments } from './color_assignment';
import type { FormatFactory, LensMultiTable } from '../../common';
import type { LayerArgs } from '../../common/expressions';
import type { DataLayerArgs } from '../../common/expressions';
import { layerTypes } from '../../common';
describe('color_assignment', () => {
const layers: LayerArgs[] = [
const layers: DataLayerArgs[] = [
{
yScaleType: 'linear',
xScaleType: 'linear',

View file

@ -122,6 +122,7 @@ export function getAccessorColorConfig(
if (isReferenceLayer(layer)) {
return getReferenceLineAccessorColorConfig(layer);
}
const layerContainsSplits = Boolean(layer.splitAccessor);
const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' };
const totalSeriesCount = colorAssignments[currentPalette.name]?.totalSeriesCount;

View file

@ -27,13 +27,13 @@ import type { LensMultiTable } from '../../common';
import { layerTypes } from '../../common';
import { xyChart } from '../../common/expressions';
import {
layerConfig,
dataLayerConfig,
legendConfig,
tickLabelsConfig,
gridlinesConfig,
XYArgs,
LegendConfig,
LayerArgs,
DataLayerArgs,
AxesSettingsConfig,
XYChartProps,
labelsOrientationConfig,
@ -212,7 +212,7 @@ const dateHistogramData: LensMultiTable = {
},
};
const dateHistogramLayer: LayerArgs = {
const dateHistogramLayer: DataLayerArgs = {
layerId: 'timeLayer',
layerType: layerTypes.DATA,
hide: false,
@ -254,7 +254,7 @@ const createSampleDatatableWithRows = (rows: DatatableRow[]): Datatable => ({
rows,
});
const sampleLayer: LayerArgs = {
const sampleLayer: DataLayerArgs = {
layerId: 'first',
layerType: layerTypes.DATA,
seriesType: 'line',
@ -268,7 +268,7 @@ const sampleLayer: LayerArgs = {
palette: mockPaletteOutput,
};
const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
const createArgsWithLayers = (layers: DataLayerArgs[] = [sampleLayer]): XYArgs => ({
xTitle: '',
yTitle: '',
yRightTitle: '',
@ -392,8 +392,8 @@ describe('xy_expression', () => {
});
});
test('layerConfig produces the correct arguments', () => {
const args: LayerArgs = {
test('dataLayerConfig produces the correct arguments', () => {
const args: DataLayerArgs = {
layerId: 'first',
layerType: layerTypes.DATA,
seriesType: 'line',
@ -406,10 +406,10 @@ describe('xy_expression', () => {
palette: mockPaletteOutput,
};
const result = layerConfig.fn(null, args, createMockExecutionContext());
const result = dataLayerConfig.fn(null, args, createMockExecutionContext());
expect(result).toEqual({
type: 'lens_xy_layer',
type: 'lens_xy_data_layer',
...args,
});
});
@ -556,7 +556,7 @@ describe('xy_expression', () => {
});
describe('date range', () => {
const timeSampleLayer: LayerArgs = {
const timeSampleLayer: DataLayerArgs = {
layerId: 'first',
layerType: layerTypes.DATA,
seriesType: 'line',
@ -649,7 +649,7 @@ describe('xy_expression', () => {
});
describe('axis time', () => {
const defaultTimeLayer: LayerArgs = {
const defaultTimeLayer: DataLayerArgs = {
layerId: 'first',
layerType: layerTypes.DATA,
seriesType: 'line',
@ -707,7 +707,7 @@ describe('xy_expression', () => {
});
test('it should disable the new time axis for a vertical bar with break down dimension', () => {
const { data } = sampleArgs();
const timeLayer: LayerArgs = {
const timeLayer: DataLayerArgs = {
...defaultTimeLayer,
seriesType: 'bar',
};
@ -734,7 +734,7 @@ describe('xy_expression', () => {
test('it should enable the new time axis for a stacked vertical bar with break down dimension', () => {
const { data } = sampleArgs();
const timeLayer: LayerArgs = {
const timeLayer: DataLayerArgs = {
...defaultTimeLayer,
seriesType: 'bar_stacked',
};
@ -1227,7 +1227,7 @@ describe('xy_expression', () => {
test('onBrushEnd returns correct context data for number histogram data', () => {
const { args } = sampleArgs();
const numberLayer: LayerArgs = {
const numberLayer: DataLayerArgs = {
layerId: 'numberLayer',
layerType: layerTypes.DATA,
hide: false,
@ -1436,7 +1436,7 @@ describe('xy_expression', () => {
test('onElementClick returns correct context data for numeric histogram', () => {
const { args } = sampleArgs();
const numberLayer: LayerArgs = {
const numberLayer: DataLayerArgs = {
layerId: 'numberLayer',
layerType: layerTypes.DATA,
hide: false,
@ -1757,7 +1757,7 @@ describe('xy_expression', () => {
test('it applies histogram mode to the series for single series', () => {
const { data, args } = sampleArgs();
const firstLayer: LayerArgs = {
const firstLayer: DataLayerArgs = {
...args.layers[0],
accessors: ['b'],
seriesType: 'bar',
@ -1772,7 +1772,7 @@ describe('xy_expression', () => {
test('it does not apply histogram mode to more than one bar series for unstacked bar chart', () => {
const { data, args } = sampleArgs();
const firstLayer: LayerArgs = { ...args.layers[0], seriesType: 'bar', isHistogram: true };
const firstLayer: DataLayerArgs = { ...args.layers[0], seriesType: 'bar', isHistogram: true };
delete firstLayer.splitAccessor;
const component = shallow(
<XYChart {...defaultProps} data={data} args={{ ...args, layers: [firstLayer] }} />
@ -1783,9 +1783,17 @@ describe('xy_expression', () => {
test('it applies histogram mode to more than one the series for unstacked line/area chart', () => {
const { data, args } = sampleArgs();
const firstLayer: LayerArgs = { ...args.layers[0], seriesType: 'line', isHistogram: true };
const firstLayer: DataLayerArgs = {
...args.layers[0],
seriesType: 'line',
isHistogram: true,
};
delete firstLayer.splitAccessor;
const secondLayer: LayerArgs = { ...args.layers[0], seriesType: 'line', isHistogram: true };
const secondLayer: DataLayerArgs = {
...args.layers[0],
seriesType: 'line',
isHistogram: true,
};
delete secondLayer.splitAccessor;
const component = shallow(
<XYChart
@ -2874,7 +2882,7 @@ describe('xy_expression', () => {
toDate: new Date('2019-01-03T05:00:00.000Z'),
},
};
const timeSampleLayer: LayerArgs = {
const timeSampleLayer: DataLayerArgs = {
layerId: 'first',
layerType: layerTypes.DATA,
seriesType: 'line',

View file

@ -53,7 +53,7 @@ import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public';
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
import type { ILensInterpreterRenderHandlers, LensFilterEvent, LensBrushEvent } from '../types';
import type { LensMultiTable, FormatFactory } from '../../common';
import type { LayerArgs, SeriesType, XYChartProps } from '../../common/expressions';
import type { DataLayerArgs, SeriesType, XYChartProps } from '../../common/expressions';
import { visualizationTypes } from './types';
import { VisualizationContainer } from '../visualization_container';
import { isHorizontalChart, getSeriesColor } from './state_helpers';
@ -77,7 +77,7 @@ import {
ReferenceLineAnnotations,
} from './expression_reference_lines';
import { computeOverallDataDomain } from './reference_line_helpers';
import { isDataLayer, isReferenceLayer } from './visualization_helpers';
import { getReferenceLayers, isDataLayer } from './visualization_helpers';
declare global {
interface Window {
@ -255,7 +255,7 @@ export function XYChart({
const layersById = filteredLayers.reduce((memo, layer) => {
memo[layer.layerId] = layer;
return memo;
}, {} as Record<string, LayerArgs>);
}, {} as Record<string, DataLayerArgs>);
const handleCursorUpdate = useActiveCursor(chartsActiveCursorService, chartRef, {
datatables: Object.values(data.tables),
@ -349,7 +349,7 @@ export function XYChart({
);
};
const referenceLineLayers = layers.filter((layer) => isReferenceLayer(layer));
const referenceLineLayers = getReferenceLayers(layers);
const referenceLinePaddings = getReferenceLineRequiredPaddings(referenceLineLayers, yAxesMap);
const getYAxesStyle = (groupId: 'left' | 'right') => {
@ -985,7 +985,7 @@ export function XYChart({
);
}
function getFilteredLayers(layers: LayerArgs[], data: LensMultiTable) {
function getFilteredLayers(layers: DataLayerArgs[], data: LensMultiTable) {
return layers.filter((layer) => {
const { layerId, xAccessor, accessors, splitAccessor } = layer;
return (

View file

@ -8,11 +8,10 @@
import { LineAnnotation, RectAnnotation } from '@elastic/charts';
import { shallow } from 'enzyme';
import React from 'react';
import { PaletteOutput } from 'src/plugins/charts/common';
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
import { FieldFormat } from 'src/plugins/field_formats/common';
import { layerTypes, LensMultiTable } from '../../common';
import { LayerArgs, YConfig } from '../../common/expressions';
import { LensMultiTable } from '../../common';
import { ReferenceLineLayerArgs, YConfig } from '../../common/expressions';
import {
ReferenceLineAnnotations,
ReferenceLineAnnotationsProps,
@ -20,12 +19,6 @@ import {
const paletteService = chartPluginMock.createPaletteRegistry();
const mockPaletteOutput: PaletteOutput = {
type: 'palette',
name: 'mock',
params: {},
};
const row: Record<string, number> = {
xAccessorFirstId: 1,
xAccessorSecondId: 2,
@ -57,18 +50,12 @@ const histogramData: LensMultiTable = {
},
};
function createLayers(yConfigs: LayerArgs['yConfig']): LayerArgs[] {
function createLayers(yConfigs: ReferenceLineLayerArgs['yConfig']): ReferenceLineLayerArgs[] {
return [
{
layerId: 'firstLayer',
layerType: layerTypes.REFERENCE_LINE,
hide: false,
yScaleType: 'linear',
xScaleType: 'linear',
isHistogram: false,
seriesType: 'bar_stacked',
layerType: 'referenceLine',
accessors: (yConfigs || []).map(({ forAccessor }) => forAccessor),
palette: mockPaletteOutput,
yConfig: yConfigs,
},
];

View file

@ -13,7 +13,7 @@ import { RectAnnotation, AnnotationDomainType, LineAnnotation, Position } from '
import type { PaletteRegistry } from 'src/plugins/charts/public';
import type { FieldFormat } from 'src/plugins/field_formats/common';
import { euiLightVars } from '@kbn/ui-theme';
import type { LayerArgs, YConfig } from '../../common/expressions';
import type { ReferenceLineLayerArgs, YConfig } from '../../common/expressions';
import type { LensMultiTable } from '../../common/types';
import { hasIcon } from './xy_config_panel/shared/icon_select';
@ -55,7 +55,7 @@ export const computeChartMargins = (
// Note: it does not take into consideration whether the reference line is in view or not
export const getReferenceLineRequiredPaddings = (
referenceLineLayers: LayerArgs[],
referenceLineLayers: ReferenceLineLayerArgs[],
axesMap: Record<'left' | 'right', unknown>
) => {
// collect all paddings for the 4 axis: if any text is detected double it.
@ -181,7 +181,7 @@ function getMarkerToShow(
}
export interface ReferenceLineAnnotationsProps {
layers: LayerArgs[];
layers: ReferenceLineLayerArgs[];
data: LensMultiTable;
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
paletteService: PaletteRegistry;

View file

@ -12,7 +12,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers';
import { ComponentType, ReactWrapper } from 'enzyme';
import type { LensMultiTable } from '../../common';
import { layerTypes } from '../../common';
import type { LayerArgs } from '../../common/expressions';
import type { DataLayerArgs } from '../../common/expressions';
import { getLegendAction } from './get_legend_action';
import { LegendActionPopover } from '../shared_components';
@ -27,7 +27,7 @@ const sampleLayer = {
xScaleType: 'ordinal',
yScaleType: 'linear',
isHistogram: false,
} as LayerArgs;
} as DataLayerArgs;
const tables = {
first: {

View file

@ -9,11 +9,11 @@ import React from 'react';
import type { LegendAction, XYChartSeriesIdentifier } from '@elastic/charts';
import type { LensFilterEvent } from '../types';
import type { LensMultiTable, FormatFactory } from '../../common';
import type { LayerArgs } from '../../common/expressions';
import type { DataLayerArgs } from '../../common/expressions';
import { LegendActionPopover } from '../shared_components';
export const getLegendAction = (
filteredLayers: LayerArgs[],
filteredLayers: DataLayerArgs[],
tables: LensMultiTable['tables'],
onFilter: (data: LensFilterEvent['data']) => void,
formatFactory: FormatFactory,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { XYLayerConfig } from '../../common/expressions';
import { XYDataLayerConfig } from '../../common/expressions';
import { FramePublicAPI } from '../types';
import { computeOverallDataDomain, getStaticValue } from './reference_line_helpers';
@ -51,7 +51,7 @@ describe('reference_line helpers', () => {
// accessor id has no hit in data
expect(
getStaticValue(
[{ layerId: 'id-a', seriesType: 'area' } as XYLayerConfig], // missing xAccessor for groupId == x
[{ layerId: 'id-a', seriesType: 'area' } as XYDataLayerConfig], // missing xAccessor for groupId == x
'x',
{
activeData: getActiveData([
@ -69,7 +69,7 @@ describe('reference_line helpers', () => {
seriesType: 'area',
layerType: 'data',
accessors: ['d'],
} as XYLayerConfig,
} as XYDataLayerConfig,
], // missing hit of accessor "d" in data
'yLeft',
{
@ -88,7 +88,7 @@ describe('reference_line helpers', () => {
seriesType: 'area',
layerType: 'data',
accessors: ['a'],
} as XYLayerConfig,
} as XYDataLayerConfig,
], // missing yConfig fallbacks to left axis, but the requested group is yRight
'yRight',
{
@ -107,7 +107,7 @@ describe('reference_line helpers', () => {
seriesType: 'area',
layerType: 'data',
accessors: ['a'],
} as XYLayerConfig,
} as XYDataLayerConfig,
], // same as above with x groupId
'x',
{
@ -130,7 +130,7 @@ describe('reference_line helpers', () => {
layerType: 'data',
accessors: ['a'],
yConfig: [{ forAccessor: 'a', axisMode: 'right' }],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'yRight',
{
@ -155,7 +155,7 @@ describe('reference_line helpers', () => {
seriesType: 'area',
layerType: 'data',
accessors: ['a'],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'yLeft',
{
@ -178,7 +178,7 @@ describe('reference_line helpers', () => {
layerType: 'data',
accessors: ['a'],
yConfig: [{ forAccessor: 'a', axisMode: 'right' }],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'yRight',
{
@ -205,7 +205,7 @@ describe('reference_line helpers', () => {
seriesType: 'area',
layerType: 'data',
accessors: ['a', 'b'],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'yLeft',
{ activeData: tables },
@ -220,7 +220,7 @@ describe('reference_line helpers', () => {
seriesType: 'area',
layerType: 'data',
accessors: ['a', 'b'],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'yRight',
{ activeData: tables },
@ -243,7 +243,7 @@ describe('reference_line helpers', () => {
seriesType: 'area',
layerType: 'data',
accessors: ['a', 'b'],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'yLeft',
{ activeData: tables },
@ -258,7 +258,7 @@ describe('reference_line helpers', () => {
seriesType: 'area',
layerType: 'data',
accessors: ['a', 'b'],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'yRight',
{ activeData: tables },
@ -277,7 +277,7 @@ describe('reference_line helpers', () => {
layerType: 'data',
xAccessor: 'a',
accessors: [],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'x', // this is influenced by the callback
{
@ -300,7 +300,7 @@ describe('reference_line helpers', () => {
layerType: 'data',
xAccessor: 'a',
accessors: [],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
'x',
{
@ -324,7 +324,7 @@ describe('reference_line helpers', () => {
for (const seriesType of ['bar_stacked', 'bar_horizontal_stacked', 'area_stacked'])
expect(
computeOverallDataDomain(
[{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] } as XYLayerConfig],
[{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] } as XYDataLayerConfig],
['a', 'b', 'c'],
getActiveData([
{
@ -350,7 +350,7 @@ describe('reference_line helpers', () => {
])
expect(
computeOverallDataDomain(
[{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] } as XYLayerConfig],
[{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] } as XYDataLayerConfig],
['a', 'b', 'c'],
getActiveData([
{
@ -375,7 +375,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] },
{ layerId: 'id-b', seriesType, accessors: ['d', 'e', 'f'] },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['a', 'b', 'c', 'd', 'e', 'f'],
getActiveData([
{ id: 'id-a', rows: [{ a: 25, b: 100, c: 100 }] },
@ -389,7 +389,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] },
{ layerId: 'id-b', seriesType, accessors: ['d', 'e', 'f'] },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['a', 'b', 'c', 'd', 'e', 'f'],
getActiveData([
{
@ -425,7 +425,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType, accessors: ['a', 'b', 'c'] },
{ layerId: 'id-b', seriesType, accessors: ['d', 'e', 'f'] },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['a', 'b', 'c', 'd', 'e', 'f'],
getActiveData([
{ id: 'id-a', rows: Array(3).fill({ a: 100, b: 100, c: 100 }) },
@ -443,7 +443,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType: nonStackedSeries, accessors: ['a', 'b', 'c'] },
{ layerId: 'id-b', seriesType: stackedSeries, accessors: ['d', 'e', 'f'] },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['a', 'b', 'c', 'd', 'e', 'f'],
getActiveData([
{ id: 'id-a', rows: [{ a: 100, b: 100, c: 100 }] },
@ -465,7 +465,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType, xAccessor: 'c', accessors: ['a', 'b'] },
{ layerId: 'id-b', seriesType, xAccessor: 'f', accessors: ['d', 'e'] },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['a', 'b', 'd', 'e'],
getActiveData([
{
@ -492,7 +492,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType, accessors: ['c'] },
{ layerId: 'id-b', seriesType, accessors: ['f'] },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['c', 'f'],
getActiveData([
{
@ -520,7 +520,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType, xAccessor: 'c', accessors: ['a', 'b'] },
{ layerId: 'id-b', seriesType, xAccessor: 'f', accessors: ['d', 'e'] },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['a', 'b', 'd', 'e'],
getActiveData([
{
@ -549,7 +549,7 @@ describe('reference_line helpers', () => {
layerId: 'id-a',
seriesType: 'area_stacked',
accessors: ['a', 'b', 'c'],
} as XYLayerConfig,
} as XYDataLayerConfig,
],
['a', 'b', 'c'],
getActiveData([
@ -568,7 +568,13 @@ describe('reference_line helpers', () => {
).toEqual({ min: 0, max: 200 }); // it is stacked, so max is the sum and 0 is the fallback
expect(
computeOverallDataDomain(
[{ layerId: 'id-a', seriesType: 'area', accessors: ['a', 'b', 'c'] } as XYLayerConfig],
[
{
layerId: 'id-a',
seriesType: 'area',
accessors: ['a', 'b', 'c'],
} as XYDataLayerConfig,
],
['a', 'b', 'c'],
getActiveData([
{
@ -602,7 +608,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType: 'area', accessors: ['a', 'b', 'c'] },
{ layerId: 'id-b', seriesType: 'line', accessors: ['d', 'e', 'f'] },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['a', 'b'],
getActiveData([{ id: 'id-c', rows: [{ a: 100, b: 100 }] }]) // mind the layer id here
)
@ -613,7 +619,7 @@ describe('reference_line helpers', () => {
[
{ layerId: 'id-a', seriesType: 'bar', accessors: ['a', 'b', 'c'] },
{ layerId: 'id-b', seriesType: 'bar_stacked' },
] as XYLayerConfig[],
] as XYDataLayerConfig[],
['a', 'b'],
getActiveData([])
)

View file

@ -8,7 +8,7 @@
import { groupBy, partition } from 'lodash';
import { i18n } from '@kbn/i18n';
import { layerTypes } from '../../common';
import type { XYLayerConfig, YConfig } from '../../common/expressions';
import type { XYDataLayerConfig, XYLayerConfig, YConfig } from '../../common/expressions';
import { Datatable } from '../../../../../src/plugins/expressions/public';
import type { AccessorConfig, DatasourcePublicAPI, FramePublicAPI, Visualization } from '../types';
import { groupAxesByType } from './axes_configuration';
@ -17,7 +17,7 @@ import type { XYState } from './types';
import {
checkScaleOperation,
getAxisName,
isDataLayer,
getDataLayers,
isNumericMetric,
} from './visualization_helpers';
import { generateId } from '../id_generator';
@ -41,9 +41,7 @@ export function getGroupsToShow<T extends ReferenceLineBase & { config?: YConfig
if (!state) {
return [];
}
const dataLayers = state.layers.filter(({ layerType = layerTypes.DATA }) =>
isDataLayer({ layerType })
);
const dataLayers = getDataLayers(state.layers);
const groupsAvailable = getGroupsAvailableInData(dataLayers, datasourceLayers, tables);
return referenceLayers
.filter(({ label, config }: T) => groupsAvailable[label] || config?.length)
@ -62,9 +60,7 @@ export function getGroupsRelatedToData<T extends ReferenceLineBase>(
if (!state) {
return [];
}
const dataLayers = state.layers.filter(({ layerType = layerTypes.DATA }) =>
isDataLayer({ layerType })
);
const dataLayers = getDataLayers(state.layers);
const groupsAvailable = getGroupsAvailableInData(dataLayers, datasourceLayers, tables);
return referenceLayers.filter(({ label }: T) => groupsAvailable[label]);
}
@ -72,7 +68,7 @@ export function getGroupsRelatedToData<T extends ReferenceLineBase>(
* Returns a dictionary with the groups filled in all the data layers
*/
export function getGroupsAvailableInData(
dataLayers: XYState['layers'],
dataLayers: XYDataLayerConfig[],
datasourceLayers: Record<string, DatasourcePublicAPI>,
tables: Record<string, Datatable> | undefined
) {
@ -88,10 +84,10 @@ export function getGroupsAvailableInData(
}
export function getStaticValue(
dataLayers: XYState['layers'],
dataLayers: XYDataLayerConfig[],
groupId: 'x' | 'yLeft' | 'yRight',
{ activeData }: Pick<FramePublicAPI, 'activeData'>,
layerHasNumberHistogram: (layer: XYLayerConfig) => boolean
layerHasNumberHistogram: (layer: XYDataLayerConfig) => boolean
) {
const fallbackValue = 100;
if (!activeData) {
@ -124,7 +120,7 @@ export function getStaticValue(
function getAccessorCriteriaForGroup(
groupId: 'x' | 'yLeft' | 'yRight',
dataLayers: XYState['layers'],
dataLayers: XYDataLayerConfig[],
activeData: FramePublicAPI['activeData']
) {
switch (groupId) {
@ -158,7 +154,7 @@ function getAccessorCriteriaForGroup(
}
export function computeOverallDataDomain(
dataLayers: Array<Pick<XYLayerConfig, 'seriesType' | 'accessors' | 'xAccessor' | 'layerId'>>,
dataLayers: XYDataLayerConfig[],
accessorIds: string[],
activeData: NonNullable<FramePublicAPI['activeData']>,
allowStacking: boolean = true
@ -222,7 +218,7 @@ export function computeOverallDataDomain(
}
function computeStaticValueForGroup(
dataLayers: Array<Pick<XYLayerConfig, 'seriesType' | 'accessors' | 'xAccessor' | 'layerId'>>,
dataLayers: XYDataLayerConfig[],
accessorIds: string[],
activeData: NonNullable<FramePublicAPI['activeData']>,
minZeroOrNegativeBase: boolean = true,
@ -275,8 +271,7 @@ export const getReferenceSupportedLayer = (
frame?.datasourceLayers || {},
frame?.activeData
);
const dataLayers =
state?.layers.filter(({ layerType = layerTypes.DATA }) => isDataLayer({ layerType })) || [];
const dataLayers = getDataLayers(state?.layers || []);
const filledDataLayers = dataLayers.filter(
({ accessors, xAccessor }) => accessors.length || xAccessor
);

View file

@ -9,6 +9,7 @@ import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import type { FramePublicAPI, DatasourcePublicAPI } from '../types';
import type { SeriesType, XYLayerConfig, YConfig, ValidLayer } from '../../common/expressions';
import { visualizationTypes } from './types';
import { getDataLayers, isDataLayer } from './visualization_helpers';
export function isHorizontalSeries(seriesType: SeriesType) {
return (
@ -30,8 +31,8 @@ export function isStackedChart(seriesType: SeriesType) {
return seriesType.includes('stacked');
}
export function isHorizontalChart(layers: Array<{ seriesType: SeriesType }>) {
return layers.every((l) => isHorizontalSeries(l.seriesType));
export function isHorizontalChart(layers: XYLayerConfig[]) {
return getDataLayers(layers).every((l) => isHorizontalSeries(l.seriesType));
}
export function getIconForSeries(type: SeriesType): EuiIconType {
@ -45,7 +46,7 @@ export function getIconForSeries(type: SeriesType): EuiIconType {
}
export const getSeriesColor = (layer: XYLayerConfig, accessor: string) => {
if (layer.splitAccessor) {
if (isDataLayer(layer) && layer.splitAccessor) {
return null;
}
return (
@ -55,13 +56,14 @@ export const getSeriesColor = (layer: XYLayerConfig, accessor: string) => {
export const getColumnToLabelMap = (layer: XYLayerConfig, datasource: DatasourcePublicAPI) => {
const columnToLabel: Record<string, string> = {};
layer.accessors.concat(layer.splitAccessor ? [layer.splitAccessor] : []).forEach((accessor) => {
const operation = datasource.getOperationForColumnId(accessor);
if (operation?.label) {
columnToLabel[accessor] = operation.label;
}
});
layer.accessors
.concat(isDataLayer(layer) && layer.splitAccessor ? [layer.splitAccessor] : [])
.forEach((accessor) => {
const operation = datasource.getOperationForColumnId(accessor);
if (operation?.label) {
columnToLabel[accessor] = operation.label;
}
});
return columnToLabel;
};

View file

@ -343,9 +343,6 @@ describe('#toExpression', () => {
{
layerId: 'referenceLine',
layerType: layerTypes.REFERENCELINE,
seriesType: 'area',
splitAccessor: 'd',
xAccessor: 'a',
accessors: ['b', 'c'],
yConfig: [{ forAccessor: 'a' }],
},

View file

@ -11,12 +11,17 @@ import { PaletteRegistry } from 'src/plugins/charts/public';
import { State } from './types';
import { OperationMetadata, DatasourcePublicAPI } from '../types';
import { getColumnToLabelMap } from './state_helpers';
import type { ValidLayer, XYLayerConfig } from '../../common/expressions';
import type {
ValidLayer,
XYLayerConfig,
XYReferenceLineLayerConfig,
YConfig,
} from '../../common/expressions';
import { layerTypes } from '../../common';
import { hasIcon } from './xy_config_panel/shared/icon_select';
import { defaultReferenceLineColor } from './color_assignment';
import { getDefaultVisualValuesForLayer } from '../shared_components/datasource_default_values';
import { isDataLayer, isReferenceLayer } from './visualization_helpers';
import { isDataLayer } from './visualization_helpers';
export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: XYLayerConfig) => {
const originalOrder = datasource
@ -299,102 +304,142 @@ export const buildExpression = (
hideEndzones: [state?.hideEndzones || false],
valuesInLegend: [state?.valuesInLegend || false],
layers: validLayers.map((layer) => {
const columnToLabel = getColumnToLabelMap(layer, datasourceLayers[layer.layerId]);
const xAxisOperation =
datasourceLayers &&
datasourceLayers[layer.layerId].getOperationForColumnId(layer.xAccessor);
const isHistogramDimension = Boolean(
xAxisOperation &&
xAxisOperation.isBucketed &&
xAxisOperation.scale &&
xAxisOperation.scale !== 'ordinal'
if (isDataLayer(layer)) {
return dataLayerToExpression(
layer,
datasourceLayers[layer.layerId],
metadata,
paletteService
);
}
return referenceLineLayerToExpression(
layer,
datasourceLayers[(layer as XYReferenceLineLayerConfig).layerId]
);
return {
type: 'expression',
chain: [
{
type: 'function',
function: 'lens_xy_layer',
arguments: {
layerId: [layer.layerId],
hide: [Boolean(layer.hide)],
xAccessor: layer.xAccessor ? [layer.xAccessor] : [],
yScaleType: [
getScaleType(metadata[layer.layerId][layer.accessors[0]], ScaleType.Ordinal),
],
xScaleType: [
getScaleType(metadata[layer.layerId][layer.xAccessor], ScaleType.Linear),
],
isHistogram: [isHistogramDimension],
splitAccessor: layer.splitAccessor ? [layer.splitAccessor] : [],
yConfig: layer.yConfig
? layer.yConfig.map((yConfig) => ({
type: 'expression',
chain: [
{
type: 'function',
function: 'lens_xy_yConfig',
arguments: {
forAccessor: [yConfig.forAccessor],
axisMode: yConfig.axisMode ? [yConfig.axisMode] : [],
color: isReferenceLayer(layer)
? [yConfig.color || defaultReferenceLineColor]
: yConfig.color
? [yConfig.color]
: [],
lineStyle: [yConfig.lineStyle || 'solid'],
lineWidth: [yConfig.lineWidth || 1],
fill: [yConfig.fill || 'none'],
icon: hasIcon(yConfig.icon) ? [yConfig.icon] : [],
iconPosition:
hasIcon(yConfig.icon) || yConfig.textVisibility
? [yConfig.iconPosition || 'auto']
: ['auto'],
textVisibility: [yConfig.textVisibility || false],
},
},
],
}))
: [],
seriesType: [layer.seriesType],
layerType: [layer.layerType || layerTypes.DATA],
accessors: layer.accessors,
columnToLabel: [JSON.stringify(columnToLabel)],
...(layer.palette
? {
palette: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'theme',
arguments: {
variable: ['palette'],
default: [
paletteService
.get(layer.palette.name)
.toExpression(layer.palette.params),
],
},
},
],
},
],
}
: {}),
},
},
],
};
}),
},
},
],
};
};
const referenceLineLayerToExpression = (
layer: XYReferenceLineLayerConfig,
datasourceLayer: DatasourcePublicAPI
): Ast => {
return {
type: 'expression',
chain: [
{
type: 'function',
function: 'lens_xy_referenceLine_layer',
arguments: {
layerId: [layer.layerId],
yConfig: layer.yConfig
? layer.yConfig.map((yConfig) =>
yConfigToExpression(yConfig, defaultReferenceLineColor)
)
: [],
layerType: [layerTypes.REFERENCELINE],
accessors: layer.accessors,
columnToLabel: [JSON.stringify(getColumnToLabelMap(layer, datasourceLayer))],
},
},
],
};
};
const dataLayerToExpression = (
layer: ValidLayer,
datasourceLayer: DatasourcePublicAPI,
metadata: Record<string, Record<string, OperationMetadata | null>>,
paletteService: PaletteRegistry
): Ast => {
const columnToLabel = getColumnToLabelMap(layer, datasourceLayer);
const xAxisOperation = datasourceLayer?.getOperationForColumnId(layer.xAccessor);
const isHistogramDimension = Boolean(
xAxisOperation &&
xAxisOperation.isBucketed &&
xAxisOperation.scale &&
xAxisOperation.scale !== 'ordinal'
);
return {
type: 'expression',
chain: [
{
type: 'function',
function: 'lens_xy_data_layer',
arguments: {
layerId: [layer.layerId],
hide: [Boolean(layer.hide)],
xAccessor: layer.xAccessor ? [layer.xAccessor] : [],
yScaleType: [
getScaleType(metadata[layer.layerId][layer.accessors[0]], ScaleType.Ordinal),
],
xScaleType: [getScaleType(metadata[layer.layerId][layer.xAccessor], ScaleType.Linear)],
isHistogram: [isHistogramDimension],
splitAccessor: layer.splitAccessor ? [layer.splitAccessor] : [],
yConfig: layer.yConfig
? layer.yConfig.map((yConfig) => yConfigToExpression(yConfig))
: [],
seriesType: [layer.seriesType],
layerType: [layerTypes.DATA],
accessors: layer.accessors,
columnToLabel: [JSON.stringify(columnToLabel)],
...(layer.palette
? {
palette: [
{
type: 'expression',
chain: [
{
type: 'function',
function: 'theme',
arguments: {
variable: ['palette'],
default: [
paletteService
.get(layer.palette.name)
.toExpression(layer.palette.params),
],
},
},
],
},
],
}
: {}),
},
},
],
};
};
const yConfigToExpression = (yConfig: YConfig, defaultColor?: string): Ast => {
return {
type: 'expression',
chain: [
{
type: 'function',
function: 'lens_xy_yConfig',
arguments: {
forAccessor: [yConfig.forAccessor],
axisMode: yConfig.axisMode ? [yConfig.axisMode] : [],
color: yConfig.color ? [yConfig.color] : defaultColor ? [defaultColor] : [],
lineStyle: [yConfig.lineStyle || 'solid'],
lineWidth: [yConfig.lineWidth || 1],
fill: [yConfig.fill || 'none'],
icon: hasIcon(yConfig.icon) ? [yConfig.icon] : [],
iconPosition:
hasIcon(yConfig.icon) || yConfig.textVisibility
? [yConfig.iconPosition || 'auto']
: ['auto'],
textVisibility: [yConfig.textVisibility || false],
},
},
],
};
};

View file

@ -9,7 +9,7 @@ import { getXyVisualization } from './visualization';
import { Position } from '@elastic/charts';
import { Operation, VisualizeEditorContext, Suggestion } from '../types';
import type { State, XYSuggestion } from './types';
import type { SeriesType, XYLayerConfig } from '../../common/expressions';
import type { SeriesType, XYDataLayerConfig, XYLayerConfig } from '../../common/expressions';
import { layerTypes } from '../../common';
import { createMockDatasource, createMockFramePublicAPI } from '../mocks';
import { LensIconChartBar } from '../assets/chart_bar';
@ -32,7 +32,7 @@ function exampleState(): State {
splitAccessor: 'd',
xAccessor: 'a',
accessors: ['b', 'c'],
},
} as XYDataLayerConfig,
],
};
}
@ -105,7 +105,7 @@ describe('xy_visualization', () => {
return {
...state,
layers: types.map((t, i) => ({
...state.layers[0],
...(state.layers[0] as XYDataLayerConfig),
layerId: `layer_${i}`,
seriesType: t,
})),
@ -143,7 +143,7 @@ describe('xy_visualization', () => {
const initialState = xyVisualization.initialize(() => 'l1');
expect(initialState.layers).toHaveLength(1);
expect(initialState.layers[0].xAccessor).not.toBeDefined();
expect((initialState.layers[0] as XYDataLayerConfig).xAccessor).not.toBeDefined();
expect(initialState.layers[0].accessors).toHaveLength(0);
expect(initialState).toMatchInlineSnapshot(`
@ -333,7 +333,6 @@ describe('xy_visualization', () => {
{
layerId: 'referenceLine',
layerType: layerTypes.REFERENCELINE,
seriesType: 'line',
accessors: [],
},
],
@ -345,7 +344,6 @@ describe('xy_visualization', () => {
).toEqual({
layerId: 'referenceLine',
layerType: layerTypes.REFERENCELINE,
seriesType: 'line',
accessors: ['newCol'],
yConfig: [
{
@ -432,7 +430,7 @@ describe('xy_visualization', () => {
},
]);
expect(state?.layers[0].palette).toStrictEqual({
expect((state?.layers[0] as XYDataLayerConfig).palette).toStrictEqual({
name: 'temperature',
type: 'palette',
});
@ -787,7 +785,11 @@ describe('xy_visualization', () => {
state: {
...baseState,
layers: [
{ ...baseState.layers[0], accessors: ['a'], seriesType: 'bar_percentage_stacked' },
{
...baseState.layers[0],
accessors: ['a'],
seriesType: 'bar_percentage_stacked',
} as XYDataLayerConfig,
],
},
frame,
@ -1010,7 +1012,6 @@ describe('xy_visualization', () => {
{
layerId: 'referenceLine',
layerType: layerTypes.REFERENCELINE,
seriesType: 'line',
accessors: [],
yConfig: [{ axisMode: 'left', forAccessor: 'a' }],
},
@ -1073,8 +1074,8 @@ describe('xy_visualization', () => {
it('should compute no groups for referenceLines when the only data accessor available is a date histogram', () => {
const state = getStateWithBaseReferenceLine();
state.layers[0].xAccessor = 'b';
state.layers[0].accessors = [];
(state.layers[0] as XYDataLayerConfig).xAccessor = 'b';
(state.layers[0] as XYDataLayerConfig).accessors = [];
state.layers[1].yConfig = []; // empty the configuration
// set the xAccessor as date_histogram
frame.datasourceLayers.referenceLine.getOperationForColumnId = jest.fn((accessor) => {
@ -1100,8 +1101,8 @@ describe('xy_visualization', () => {
it('should mark horizontal group is invalid when xAccessor is changed to a date histogram', () => {
const state = getStateWithBaseReferenceLine();
state.layers[0].xAccessor = 'b';
state.layers[0].accessors = [];
(state.layers[0] as XYDataLayerConfig).xAccessor = 'b';
(state.layers[0] as XYDataLayerConfig).accessors = [];
state.layers[1].yConfig![0].axisMode = 'bottom';
// set the xAccessor as date_histogram
frame.datasourceLayers.referenceLine.getOperationForColumnId = jest.fn((accessor) => {
@ -1132,10 +1133,10 @@ describe('xy_visualization', () => {
it('should return groups in a specific order (left, right, bottom)', () => {
const state = getStateWithBaseReferenceLine();
state.layers[0].xAccessor = 'c';
state.layers[0].accessors = ['a', 'b'];
(state.layers[0] as XYDataLayerConfig).xAccessor = 'c';
(state.layers[0] as XYDataLayerConfig).accessors = ['a', 'b'];
// invert them on purpose
state.layers[0].yConfig = [
(state.layers[0] as XYDataLayerConfig).yConfig = [
{ axisMode: 'right', forAccessor: 'b' },
{ axisMode: 'left', forAccessor: 'a' },
];
@ -1170,8 +1171,8 @@ describe('xy_visualization', () => {
it('should ignore terms operation for xAccessor', () => {
const state = getStateWithBaseReferenceLine();
state.layers[0].xAccessor = 'b';
state.layers[0].accessors = [];
(state.layers[0] as XYDataLayerConfig).xAccessor = 'b';
(state.layers[0] as XYDataLayerConfig).accessors = [];
state.layers[1].yConfig = []; // empty the configuration
// set the xAccessor as top values
frame.datasourceLayers.referenceLine.getOperationForColumnId = jest.fn((accessor) => {
@ -1197,8 +1198,8 @@ describe('xy_visualization', () => {
it('should mark horizontal group is invalid when accessor is changed to a terms operation', () => {
const state = getStateWithBaseReferenceLine();
state.layers[0].xAccessor = 'b';
state.layers[0].accessors = [];
(state.layers[0] as XYDataLayerConfig).xAccessor = 'b';
(state.layers[0] as XYDataLayerConfig).accessors = [];
state.layers[1].yConfig![0].axisMode = 'bottom';
// set the xAccessor as date_histogram
frame.datasourceLayers.referenceLine.getOperationForColumnId = jest.fn((accessor) => {
@ -1262,7 +1263,7 @@ describe('xy_visualization', () => {
};
const state = getStateWithBaseReferenceLine();
state.layers[0].accessors = ['yAccessorId', 'yAccessorId2'];
(state.layers[0] as XYDataLayerConfig).accessors = ['yAccessorId', 'yAccessorId2'];
state.layers[1].yConfig = []; // empty the configuration
const options = xyVisualization.getConfiguration({
@ -1282,8 +1283,12 @@ describe('xy_visualization', () => {
it('should be excluded and not crash when a custom palette is used for data layer', () => {
const state = getStateWithBaseReferenceLine();
// now add a breakdown on the data layer with a custom palette
state.layers[0].palette = { type: 'palette', name: 'custom', params: {} };
state.layers[0].splitAccessor = 'd';
(state.layers[0] as XYDataLayerConfig).palette = {
type: 'palette',
name: 'custom',
params: {},
};
(state.layers[0] as XYDataLayerConfig).splitAccessor = 'd';
const options = xyVisualization.getConfiguration({
state,
@ -1306,7 +1311,7 @@ describe('xy_visualization', () => {
...baseState.layers[0],
splitAccessor: undefined,
...layerConfigOverride,
},
} as XYDataLayerConfig,
],
},
frame,

View file

@ -6,7 +6,6 @@
*/
import React from 'react';
import { groupBy, uniq } from 'lodash';
import { render } from 'react-dom';
import { Position } from '@elastic/charts';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
@ -22,7 +21,7 @@ import { DimensionEditor } from './xy_config_panel/dimension_editor';
import { LayerHeader } from './xy_config_panel/layer_header';
import type { Visualization, AccessorConfig, FramePublicAPI } from '../types';
import { State, visualizationTypes, XYSuggestion } from './types';
import { SeriesType, XYLayerConfig, YAxisMode } from '../../common/expressions';
import { SeriesType, XYDataLayerConfig, XYLayerConfig, YAxisMode } from '../../common/expressions';
import { layerTypes } from '../../common';
import { isHorizontalChart } from './state_helpers';
import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression';
@ -38,7 +37,9 @@ import {
checkXAccessorCompatibility,
defaultSeriesType,
getAxisName,
getDataLayers,
getDescription,
getFirstDataLayer,
getLayersByType,
getVisualizationType,
isBucketed,
@ -87,16 +88,12 @@ export const getXyVisualization = ({
},
appendLayer(state, layerId, layerType) {
const usedSeriesTypes = uniq(state.layers.map((layer) => layer.seriesType));
const firstUsedSeriesType = getDataLayers(state.layers)?.[0]?.seriesType;
return {
...state,
layers: [
...state.layers,
newLayerState(
usedSeriesTypes.length === 1 ? usedSeriesTypes[0] : state.preferredSeriesType,
layerId,
layerType
),
newLayerState(firstUsedSeriesType || state.preferredSeriesType, layerId, layerType),
],
};
},
@ -175,6 +172,7 @@ export const getXyVisualization = ({
if (isReferenceLayer(layer)) {
return getReferenceConfiguration({ state, frame, layer, sortedAccessors, mappedAccessors });
}
const dataLayers = getDataLayers(state.layers);
const isHorizontal = isHorizontalChart(state.layers);
const { left, right } = groupAxesByType([layer], frame.activeData);
@ -185,24 +183,21 @@ export const getXyVisualization = ({
(right.length && right.length < 2)
);
// Check also for multiple layers that can stack for percentage charts
// Make sure that if multiple dimensions are defined for a single layer, they should belong to the same axis
// Make sure that if multiple dimensions are defined for a single dataLayer, they should belong to the same axis
const hasOnlyOneAccessor =
layerHasOnlyOneAccessor &&
getLayersByType(state, layerTypes.DATA).filter(
dataLayers.filter(
// check that the other layers are compatible with this one
(dataLayer) => {
(l) => {
if (
dataLayer.seriesType === layer.seriesType &&
Boolean(dataLayer.xAccessor) === Boolean(layer.xAccessor) &&
Boolean(dataLayer.splitAccessor) === Boolean(layer.splitAccessor)
l.seriesType === layer.seriesType &&
Boolean(l.xAccessor) === Boolean(layer.xAccessor) &&
Boolean(l.splitAccessor) === Boolean(layer.splitAccessor)
) {
const { left: localLeft, right: localRight } = groupAxesByType(
[dataLayer],
frame.activeData
);
const { left: localLeft, right: localRight } = groupAxesByType([l], frame.activeData);
// return true only if matching axis are found
return (
dataLayer.accessors.length &&
l.accessors.length &&
(Boolean(localLeft.length) === Boolean(left.length) ||
Boolean(localRight.length) === Boolean(right.length))
);
@ -259,7 +254,7 @@ export const getXyVisualization = ({
getMainPalette: (state) => {
if (!state || state.layers.length === 0) return;
return state.layers[0].palette;
return getFirstDataLayer(state.layers)?.palette;
},
setDimension(props) {
@ -371,14 +366,18 @@ export const getXyVisualization = ({
if (!foundLayer) {
return prevState;
}
const dataLayers = getDataLayers(prevState.layers);
const newLayer = { ...foundLayer };
if (newLayer.xAccessor === columnId) {
delete newLayer.xAccessor;
} else if (newLayer.splitAccessor === columnId) {
delete newLayer.splitAccessor;
// as the palette is associated with the break down by dimension, remove it together with the dimension
delete newLayer.palette;
} else if (newLayer.accessors.includes(columnId)) {
if (isDataLayer(newLayer)) {
if (newLayer.xAccessor === columnId) {
delete newLayer.xAccessor;
} else if (newLayer.splitAccessor === columnId) {
delete newLayer.splitAccessor;
// as the palette is associated with the break down by dimension, remove it together with the dimension
delete newLayer.palette;
}
}
if (newLayer.accessors.includes(columnId)) {
newLayer.accessors = newLayer.accessors.filter((a) => a !== columnId);
}
@ -388,10 +387,9 @@ export const getXyVisualization = ({
let newLayers = prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l));
// check if there's any reference layer and pull it off if all data layers have no dimensions set
const layersByType = groupBy(newLayers, ({ layerType }) => layerType);
// check for data layers if they all still have xAccessors
const groupsAvailable = getGroupsAvailableInData(
layersByType[layerTypes.DATA],
dataLayers,
frame.datasourceLayers,
frame?.activeData
);
@ -453,9 +451,11 @@ export const getXyVisualization = ({
getErrorMessages(state, datasourceLayers) {
// Data error handling below here
const hasNoAccessors = ({ accessors }: XYLayerConfig) =>
const hasNoAccessors = ({ accessors }: XYDataLayerConfig) =>
accessors == null || accessors.length === 0;
const hasNoSplitAccessor = ({ splitAccessor, seriesType }: XYLayerConfig) =>
const dataLayers = getDataLayers(state.layers);
const hasNoSplitAccessor = ({ splitAccessor, seriesType }: XYDataLayerConfig) =>
seriesType.includes('percentage') && splitAccessor == null;
const errors: Array<{
@ -466,16 +466,15 @@ export const getXyVisualization = ({
// check if the layers in the state are compatible with this type of chart
if (state && state.layers.length > 1) {
// Order is important here: Y Axis is fundamental to exist to make it valid
const checks: Array<[string, (layer: XYLayerConfig) => boolean]> = [
const checks: Array<[string, (layer: XYDataLayerConfig) => boolean]> = [
['Y', hasNoAccessors],
['Break down', hasNoSplitAccessor],
];
// filter out those layers with no accessors at all
const filteredLayers = state.layers.filter(
({ accessors, xAccessor, splitAccessor, layerType }: XYLayerConfig) =>
isDataLayer({ layerType }) &&
(accessors.length > 0 || xAccessor != null || splitAccessor != null)
const filteredLayers = dataLayers.filter(
({ accessors, xAccessor, splitAccessor, layerType }) =>
accessors.length > 0 || xAccessor != null || splitAccessor != null
);
for (const [dimension, criteria] of checks) {
const result = validateLayersForDimension(dimension, filteredLayers, criteria);

View file

@ -10,7 +10,12 @@ import { uniq } from 'lodash';
import { DatasourcePublicAPI, OperationMetadata, VisualizationType } from '../types';
import { State, visualizationTypes, XYState } from './types';
import { isHorizontalChart } from './state_helpers';
import { SeriesType, XYLayerConfig } from '../../common/expressions';
import {
SeriesType,
XYDataLayerConfig,
XYLayerConfig,
XYReferenceLineLayerConfig,
} from '../../common/expressions';
import { layerTypes } from '..';
import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal';
import { LensIconChartMixedXy } from '../assets/chart_mixed_xy';
@ -59,14 +64,15 @@ export function checkXAccessorCompatibility(
state: XYState,
datasourceLayers: Record<string, DatasourcePublicAPI>
) {
const dataLayers = getDataLayers(state.layers);
const errors = [];
const hasDateHistogramSet = state.layers.some(
const hasDateHistogramSet = dataLayers.some(
checkScaleOperation('interval', 'date', datasourceLayers)
);
const hasNumberHistogram = state.layers.some(
const hasNumberHistogram = dataLayers.some(
checkScaleOperation('interval', 'number', datasourceLayers)
);
const hasOrdinalAxis = state.layers.some(
const hasOrdinalAxis = dataLayers.some(
checkScaleOperation('ordinal', undefined, datasourceLayers)
);
if (state.layers.length > 1 && hasDateHistogramSet && hasNumberHistogram) {
@ -109,7 +115,7 @@ export function checkScaleOperation(
dataType: 'date' | 'number' | 'string' | undefined,
datasourceLayers: Record<string, DatasourcePublicAPI>
) {
return (layer: XYLayerConfig) => {
return (layer: XYDataLayerConfig) => {
const datasourceAPI = datasourceLayers[layer.layerId];
if (!layer.xAccessor) {
return false;
@ -121,11 +127,20 @@ export function checkScaleOperation(
};
}
export const isDataLayer = (layer: Pick<XYLayerConfig, 'layerType'>) =>
layer.layerType === layerTypes.DATA;
export const isDataLayer = (layer: Pick<XYLayerConfig, 'layerType'>): layer is XYDataLayerConfig =>
layer.layerType === layerTypes.DATA || !layer.layerType;
export const isReferenceLayer = (layer: Pick<XYLayerConfig, 'layerType'>) =>
layer?.layerType === layerTypes.REFERENCELINE;
export const getDataLayers = (layers: XYLayerConfig[]) =>
(layers || []).filter((layer): layer is XYDataLayerConfig => isDataLayer(layer));
export const getFirstDataLayer = (layers: XYLayerConfig[]) =>
(layers || []).find((layer): layer is XYDataLayerConfig => isDataLayer(layer));
export const isReferenceLayer = (layer: XYLayerConfig): layer is XYReferenceLineLayerConfig =>
layer.layerType === layerTypes.REFERENCELINE;
export const getReferenceLayers = (layers: XYLayerConfig[]) =>
(layers || []).filter((layer): layer is XYReferenceLineLayerConfig => isReferenceLayer(layer));
export function getVisualizationType(state: State): VisualizationType | 'mixed' {
if (!state.layers.length) {
@ -133,8 +148,9 @@ export function getVisualizationType(state: State): VisualizationType | 'mixed'
visualizationTypes.find((t) => t.id === state.preferredSeriesType) ?? visualizationTypes[0]
);
}
const visualizationType = visualizationTypes.find((t) => t.id === state.layers[0].seriesType);
const seriesTypes = uniq(state.layers.map((l) => l.seriesType));
const dataLayers = getDataLayers(state?.layers);
const visualizationType = visualizationTypes.find((t) => t.id === dataLayers?.[0].seriesType);
const seriesTypes = uniq(dataLayers.map((l) => l.seriesType));
return visualizationType && seriesTypes.length === 1 ? visualizationType : 'mixed';
}
@ -241,8 +257,8 @@ export function getLayersByType(state: State, byType?: string) {
export function validateLayersForDimension(
dimension: string,
layers: XYLayerConfig[],
missingCriteria: (layer: XYLayerConfig) => boolean
layers: XYDataLayerConfig[],
missingCriteria: (layer: XYDataLayerConfig) => boolean
):
| { valid: true }
| {

View file

@ -10,7 +10,7 @@ import React from 'react';
import moment from 'moment';
import { Endzones } from '../../../../../src/plugins/charts/public';
import type { LensMultiTable } from '../../common';
import type { LayerArgs } from '../../common/expressions';
import type { DataLayerArgs } from '../../common/expressions';
import { search } from '../../../../../src/plugins/data/public';
export interface XDomain {
@ -19,7 +19,7 @@ export interface XDomain {
minInterval?: number;
}
export const getAppliedTimeRange = (layers: LayerArgs[], data: LensMultiTable) => {
export const getAppliedTimeRange = (layers: DataLayerArgs[], data: LensMultiTable) => {
return Object.entries(data.tables)
.map(([tableId, table]) => {
const layer = layers.find((l) => l.layerId === tableId);
@ -37,7 +37,7 @@ export const getAppliedTimeRange = (layers: LayerArgs[], data: LensMultiTable) =
};
export const getXDomain = (
layers: LayerArgs[],
layers: DataLayerArgs[],
data: LensMultiTable,
minInterval: number | undefined,
isTimeViz: boolean,

View file

@ -22,7 +22,7 @@ import {
import { getSortedAccessors } from '../to_expression';
import { updateLayer } from '.';
import { TooltipWrapper } from '../../shared_components';
import { isReferenceLayer } from '../visualization_helpers';
import { isDataLayer, isReferenceLayer } from '../visualization_helpers';
const tooltipContent = {
auto: i18n.translate('xpack.lens.configPanel.color.tooltip.auto', {
@ -55,7 +55,6 @@ export const ColorPicker = ({
}) => {
const index = state.layers.findIndex((l) => l.layerId === layerId);
const layer = state.layers[index];
const disabled = Boolean(layer.splitAccessor);
const overwriteColor = getSeriesColor(layer, accessor);
const currentColor = useMemo(() => {
@ -87,6 +86,7 @@ export const ColorPicker = ({
const [color, setColor] = useState(currentColor);
const disabled = Boolean(isDataLayer(layer) && layer.splitAccessor);
const handleColor: EuiColorPickerProps['onChange'] = (text, output) => {
setColor(text);
if (output.isValid || text === '') {

View file

@ -20,6 +20,7 @@ import { VisualOptionsPopover } from './visual_options_popover';
import { getScaleType } from '../to_expression';
import { TooltipWrapper } from '../../shared_components';
import { getDefaultVisualValuesForLayer } from '../../shared_components/datasource_default_values';
import { getDataLayers } from '../visualization_helpers';
type UnwrapArray<T> = T extends Array<infer P> ? P : T;
type AxesSettingsConfigKeys = keyof AxesSettingsConfig;
@ -103,7 +104,7 @@ function hasPercentageAxis(axisGroups: GroupsConfiguration, groupId: string, sta
axisGroups
.find((group) => group.groupId === groupId)
?.series.some(({ layer: layerId }) =>
state?.layers.find(
getDataLayers(state?.layers).find(
(layer) => layer.layerId === layerId && layer.seriesType.includes('percentage')
)
)
@ -115,8 +116,9 @@ export const XyToolbar = memo(function XyToolbar(
) {
const { state, setState, frame, useLegacyTimeAxis } = props;
const dataLayers = getDataLayers(state?.layers);
const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false;
const axisGroups = getAxesConfiguration(state?.layers, shouldRotate, frame.activeData);
const axisGroups = getAxesConfiguration(dataLayers, shouldRotate, frame.activeData);
const dataBounds = getDataBounds(frame.activeData, axisGroups);
const tickLabelsVisibilitySettings = {
@ -196,7 +198,7 @@ export const XyToolbar = memo(function XyToolbar(
});
};
const nonOrdinalXAxis = state?.layers.every(
const nonOrdinalXAxis = dataLayers.every(
(layer) =>
!layer.xAccessor ||
getScaleType(
@ -206,7 +208,7 @@ export const XyToolbar = memo(function XyToolbar(
);
// only allow changing endzone visibility if it could show up theoretically (if it's a time viz)
const onChangeEndzoneVisiblity = state?.layers.every(
const onChangeEndzoneVisiblity = dataLayers.every(
(layer) =>
layer.xAccessor &&
getScaleType(
@ -232,7 +234,7 @@ export const XyToolbar = memo(function XyToolbar(
axisGroups
.find((group) => group.groupId === 'left')
?.series?.some((series) => {
const seriesType = state.layers.find((l) => l.layerId === series.layer)?.seriesType;
const seriesType = dataLayers.find((l) => l.layerId === series.layer)?.seriesType;
return seriesType?.includes('bar') || seriesType?.includes('area');
})
);
@ -249,7 +251,7 @@ export const XyToolbar = memo(function XyToolbar(
axisGroups
.find((group) => group.groupId === 'right')
?.series?.some((series) => {
const seriesType = state.layers.find((l) => l.layerId === series.layer)?.seriesType;
const seriesType = dataLayers.find((l) => l.layerId === series.layer)?.seriesType;
return seriesType?.includes('bar') || seriesType?.includes('area');
})
);
@ -263,12 +265,12 @@ export const XyToolbar = memo(function XyToolbar(
[setState, state]
);
const filteredBarLayers = state?.layers.filter((layer) => layer.seriesType.includes('bar'));
const filteredBarLayers = dataLayers.filter((layer) => layer.seriesType.includes('bar'));
const chartHasMoreThanOneBarSeries =
filteredBarLayers.length > 1 ||
filteredBarLayers.some((layer) => layer.accessors.length > 1 || layer.splitAccessor);
const isTimeHistogramModeEnabled = state?.layers.some(
const isTimeHistogramModeEnabled = dataLayers.some(
({ xAccessor, layerId, seriesType, splitAccessor }) => {
if (!xAccessor) {
return false;

View file

@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import { EuiIcon, EuiPopover, EuiSelectable, EuiText, EuiPopoverTitle } from '@elastic/eui';
import type { VisualizationLayerWidgetProps, VisualizationType } from '../../types';
import { State, visualizationTypes } from '../types';
import { SeriesType } from '../../../common/expressions';
import { SeriesType, XYDataLayerConfig } from '../../../common/expressions';
import { isHorizontalChart, isHorizontalSeries } from '../state_helpers';
import { trackUiEvent } from '../../lens_ui_telemetry';
import { StaticHeader } from '../../shared_components';
@ -45,7 +45,7 @@ function DataLayerHeader(props: VisualizationLayerWidgetProps<State>) {
const [isPopoverOpen, setPopoverIsOpen] = useState(false);
const { state, layerId } = props;
const index = state.layers.findIndex((l) => l.layerId === layerId);
const layer = state.layers[index];
const layer = state.layers[index] as XYDataLayerConfig;
const currentVisType = visualizationTypes.find(({ id }) => id === layer.seriesType)!;
const horizontalOnly = isHorizontalChart(state.layers);

View file

@ -15,6 +15,7 @@ import { XYState } from '../../types';
import { hasHistogramSeries } from '../../state_helpers';
import { ValidLayer } from '../../../../common/expressions';
import type { FramePublicAPI } from '../../../types';
import { getDataLayers } from '../../visualization_helpers';
function getValueLabelDisableReason({
isAreaPercentage,
@ -49,24 +50,25 @@ export const VisualOptionsPopover: React.FC<VisualOptionsPopoverProps> = ({
setState,
datasourceLayers,
}) => {
const isAreaPercentage = state?.layers.some(
const dataLayers = getDataLayers(state.layers);
const isAreaPercentage = dataLayers.some(
({ seriesType }) => seriesType === 'area_percentage_stacked'
);
const hasNonBarSeries = state?.layers.some(({ seriesType }) =>
const hasNonBarSeries = dataLayers.some(({ seriesType }) =>
['area_stacked', 'area', 'line'].includes(seriesType)
);
const hasBarNotStacked = state?.layers.some(({ seriesType }) =>
const hasBarNotStacked = dataLayers.some(({ seriesType }) =>
['bar', 'bar_horizontal'].includes(seriesType)
);
const hasAreaSeries = state?.layers.some(({ seriesType }) =>
const hasAreaSeries = dataLayers.some(({ seriesType }) =>
['area_stacked', 'area', 'area_percentage_stacked'].includes(seriesType)
);
const isHistogramSeries = Boolean(
hasHistogramSeries(state?.layers as ValidLayer[], datasourceLayers)
hasHistogramSeries(dataLayers as ValidLayer[], datasourceLayers)
);
const isValueLabelsEnabled = !hasNonBarSeries && hasBarNotStacked && !isHistogramSeries;

View file

@ -16,6 +16,7 @@ import { ToolbarPopover, ValueLabelsSettings } from '../../../shared_components'
import { MissingValuesOptions } from './missing_values_option';
import { FillOpacityOption } from './fill_opacity_option';
import { layerTypes } from '../../../../common';
import { XYDataLayerConfig } from '../../../../common/expressions';
describe('Visual options popover', () => {
let frame: FramePublicAPI;
@ -52,7 +53,7 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_stacked' }],
layers: [{ ...state.layers[0], seriesType: 'bar_stacked' } as XYDataLayerConfig],
}}
/>
);
@ -68,7 +69,9 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'area_percentage_stacked' }],
layers: [
{ ...state.layers[0], seriesType: 'area_percentage_stacked' } as XYDataLayerConfig,
],
}}
/>
);
@ -85,7 +88,9 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'area_percentage_stacked' }],
layers: [
{ ...state.layers[0], seriesType: 'area_percentage_stacked' } as XYDataLayerConfig,
],
}}
/>
);
@ -101,7 +106,9 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'area_percentage_stacked' }],
layers: [
{ ...state.layers[0], seriesType: 'area_percentage_stacked' } as XYDataLayerConfig,
],
}}
/>
);
@ -138,7 +145,7 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }],
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' } as XYDataLayerConfig],
fittingFunction: 'Carry',
}}
/>
@ -155,7 +162,7 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }],
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' } as XYDataLayerConfig],
fittingFunction: 'Carry',
}}
/>
@ -172,7 +179,7 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'line' }],
layers: [{ ...state.layers[0], seriesType: 'line' } as XYDataLayerConfig],
fittingFunction: 'Carry',
}}
/>
@ -190,7 +197,7 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }],
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' } as XYDataLayerConfig],
fittingFunction: 'Carry',
}}
/>
@ -207,7 +214,7 @@ describe('Visual options popover', () => {
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'area' }],
layers: [{ ...state.layers[0], seriesType: 'area' } as XYDataLayerConfig],
fittingFunction: 'Carry',
}}
/>
@ -230,7 +237,7 @@ describe('Visual options popover', () => {
state={{
...state,
layers: [
{ ...state.layers[0], seriesType: 'bar' },
{ ...state.layers[0], seriesType: 'bar' } as XYDataLayerConfig,
{
seriesType: 'bar',
layerType: layerTypes.DATA,

View file

@ -18,6 +18,7 @@ import { createMockFramePublicAPI, createMockDatasource } from '../../mocks';
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
import { EuiColorPicker } from '@elastic/eui';
import { layerTypes } from '../../../common';
import { XYDataLayerConfig } from '../../../common/expressions';
describe('XY Config panels', () => {
let frame: FramePublicAPI;
@ -205,7 +206,10 @@ describe('XY Config panels', () => {
setState={jest.fn()}
accessor="bar"
groupId="left"
state={{ ...state, layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }] }}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' } as XYDataLayerConfig],
}}
formatFactory={jest.fn()}
paletteService={chartPluginMock.createPaletteRegistry()}
panelRef={React.createRef()}

View file

@ -15,6 +15,7 @@ import { PaletteOutput } from 'src/plugins/charts/public';
import { layerTypes } from '../../common';
import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks';
import { themeServiceMock } from '../../../../../src/core/public/mocks';
import { XYDataLayerConfig } from '../../common/expressions';
jest.mock('../id_generator');
@ -89,12 +90,14 @@ describe('xy_suggestions', () => {
// Helper that plucks out the important part of a suggestion for
// most test assertions
function suggestionSubset(suggestion: VisualizationSuggestion<State>) {
return suggestion.state.layers.map(({ seriesType, splitAccessor, xAccessor, accessors }) => ({
seriesType,
splitAccessor,
x: xAccessor,
y: accessors,
}));
return (suggestion.state.layers as XYDataLayerConfig[]).map(
({ seriesType, splitAccessor, xAccessor, accessors }) => ({
seriesType,
splitAccessor,
x: xAccessor,
y: accessors,
})
);
}
beforeEach(() => {
@ -543,7 +546,7 @@ describe('xy_suggestions', () => {
mainPalette,
});
expect(suggestion.state.layers[0].palette).toEqual(mainPalette);
expect((suggestion.state.layers as XYDataLayerConfig[])[0].palette).toEqual(mainPalette);
});
test('ignores passed in palette for non splitted charts', () => {
@ -559,7 +562,7 @@ describe('xy_suggestions', () => {
mainPalette,
});
expect(suggestion.state.layers[0].palette).toEqual(undefined);
expect((suggestion.state.layers as XYDataLayerConfig[])[0].palette).toEqual(undefined);
});
test('hides reduced suggestions if there is a current state', () => {
@ -655,7 +658,7 @@ describe('xy_suggestions', () => {
expect(suggestions[0].hide).toEqual(false);
expect(suggestions[0].state.preferredSeriesType).toEqual('line');
expect(suggestions[0].state.layers[0].seriesType).toEqual('line');
expect((suggestions[0].state.layers[0] as XYDataLayerConfig).seriesType).toEqual('line');
});
test('makes a visible seriesType suggestion for unchanged table without split', () => {
@ -779,7 +782,11 @@ describe('xy_suggestions', () => {
expect(rest).toHaveLength(visualizationTypes.length - 1);
expect(suggestion.state.preferredSeriesType).toEqual('bar_horizontal');
expect(suggestion.state.layers.every((l) => l.seriesType === 'bar_horizontal')).toBeTruthy();
expect(
(suggestion.state.layers as XYDataLayerConfig[]).every(
(l) => l.seriesType === 'bar_horizontal'
)
).toBeTruthy();
expect(suggestion.title).toEqual('Flip');
});

View file

@ -17,9 +17,10 @@ import {
TableChangeType,
} from '../types';
import { State, XYState, visualizationTypes } from './types';
import type { SeriesType, XYLayerConfig } from '../../common/expressions';
import type { SeriesType, XYDataLayerConfig } from '../../common/expressions';
import { layerTypes } from '../../common';
import { getIconForSeries } from './state_helpers';
import { getDataLayers, isDataLayer } from './visualization_helpers';
const columnSortOrder = {
document: 0,
@ -158,7 +159,8 @@ function flipSeriesType(seriesType: SeriesType) {
function getBucketMappings(table: TableSuggestion, currentState?: State) {
const currentLayer =
currentState && currentState.layers.find(({ layerId }) => layerId === table.layerId);
currentState &&
getDataLayers(currentState.layers).find(({ layerId }) => layerId === table.layerId);
const buckets = table.columns.filter((col) => col.operation.isBucketed);
// reverse the buckets before prioritization to always use the most inner
@ -416,7 +418,7 @@ function getSeriesType(
const defaultType = 'bar_stacked';
const oldLayer = getExistingLayer(currentState, layerId);
const oldLayerSeriesType = oldLayer ? oldLayer.seriesType : false;
const oldLayerSeriesType = oldLayer && isDataLayer(oldLayer) ? oldLayer.seriesType : false;
const closestSeriesType =
oldLayerSeriesType || (currentState && currentState.preferredSeriesType) || defaultType;
@ -496,7 +498,8 @@ function buildSuggestion({
splitBy = xValue;
xValue = undefined;
}
const existingLayer: XYLayerConfig | {} = getExistingLayer(currentState, layerId) || {};
const existingLayer: XYDataLayerConfig | {} =
getExistingLayer(currentState, layerId) || ({} as XYDataLayerConfig);
const accessors = yValues.map((col) => col.columnId);
const newLayer = {
...existingLayer,

View file

@ -11,7 +11,8 @@ import {
counterRate,
metricChart,
yAxisConfig,
layerConfig,
dataLayerConfig,
referenceLineLayerConfig,
formatColumn,
legendConfig,
renameColumns,
@ -39,7 +40,8 @@ export const setupExpressions = (
counterRate,
metricChart,
yAxisConfig,
layerConfig,
dataLayerConfig,
referenceLineLayerConfig,
formatColumn,
legendConfig,
renameColumns,