diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.ts index 2de0d1305df52..392c9a0d5830a 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.ts @@ -40,6 +40,13 @@ export const layeredXyVisFunction: LayeredXyVisFn = { types: ['vis_dimension', 'string'], help: strings.getSplitRowAccessorHelp(), }, + singleTable: { + types: ['boolean'], + help: i18n.translate('expressionXY.layeredXyVis.singleTable.help', { + defaultMessage: 'All layers use the one datatable', + }), + default: false, + }, }, async fn(data, args, handlers) { const { layeredXyVisFn } = await import('./layered_xy_vis_fn'); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts index 42f77121cd114..50549d5f8ac1c 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts @@ -17,22 +17,14 @@ import { errors, validateAxes, } from './validate'; -import { appendLayerIds, getDataLayers, getLayersWithTable } from '../helpers'; +import { appendLayerIds, getDataLayers } from '../helpers'; export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) => { const layers = appendLayerIds(args.layers ?? [], 'layers'); const dataLayers = getDataLayers(layers); - const layersWithTable = getLayersWithTable(layers); - // if layers have the same table should log only one - if (layersWithTable.every((l) => l.table === layersWithTable[0].table)) { - logDatatable( - layersWithTable[0].table, - layers, - handlers, - args.splitColumnAccessor, - args.splitRowAccessor - ); + if (args.singleTable) { + logDatatable(data, layers, handlers, args.splitColumnAccessor, args.splitRowAccessor); } else { logDatatables(layers, handlers, args.splitColumnAccessor, args.splitRowAccessor); } diff --git a/src/plugins/chart_expressions/expression_xy/common/helpers/layers.ts b/src/plugins/chart_expressions/expression_xy/common/helpers/layers.ts index 6b681944d7611..84ef7e41fbe5d 100644 --- a/src/plugins/chart_expressions/expression_xy/common/helpers/layers.ts +++ b/src/plugins/chart_expressions/expression_xy/common/helpers/layers.ts @@ -13,9 +13,8 @@ import { XYExtendedLayerConfigResult, ExtendedDataLayerArgs, DataLayerArgs, - XYExtendedLayerConfigResultWithTable, } from '../types'; -import { LayerTypes, SeriesTypes, REFERENCE_LINE_LAYER } from '../constants'; +import { LayerTypes, SeriesTypes } from '../constants'; function isWithLayerId(layer: T): layer is T & WithLayerId { return (layer as T & WithLayerId).layerId ? true : false; @@ -45,13 +44,6 @@ export function getDataLayers(layers: XYExtendedLayerConfigResult[]) { ); } -export function getLayersWithTable(layers: XYExtendedLayerConfigResult[]) { - return layers.filter( - (layer): layer is XYExtendedLayerConfigResultWithTable => - layer.layerType === LayerTypes.DATA || layer.type === REFERENCE_LINE_LAYER || !layer.layerType - ); -} - export function getAccessors< T, U extends { splitAccessors?: T[]; xAccessor?: T; accessors: T[]; markSizeAccessor?: T } diff --git a/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts index 2d7700fd5240d..0970cec985d30 100644 --- a/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts @@ -254,6 +254,7 @@ export interface LayeredXYArgs { showTooltip: boolean; splitRowAccessor?: ExpressionValueVisDimension | string; splitColumnAccessor?: ExpressionValueVisDimension | string; + singleTable?: boolean; } export interface XYProps { @@ -277,6 +278,7 @@ export interface XYProps { detailedTooltip?: boolean; orderBucketsBySum?: boolean; showTooltip: boolean; + singleTable?: boolean; } export interface AnnotationLayerArgs { @@ -327,10 +329,6 @@ export type XYExtendedLayerConfigResult = | ExtendedAnnotationLayerConfigResult | ReferenceLineConfigResult; -export type XYExtendedLayerConfigResultWithTable = - | ExtendedDataLayerConfigResult - | ReferenceLineLayerConfigResult; - export interface ExtendedReferenceLineDecorationConfig extends ReferenceLineArgs { type: typeof EXTENDED_REFERENCE_LINE_DECORATION_CONFIG; } diff --git a/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap b/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap index 60acbd6fd3daf..1390545591f6c 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap @@ -526,6 +526,7 @@ exports[`XYChart component it renders area 1`] = ` "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -1187,6 +1188,7 @@ exports[`XYChart component it renders area 1`] = ` titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -1505,6 +1507,7 @@ exports[`XYChart component it renders bar 1`] = ` "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -2166,6 +2169,7 @@ exports[`XYChart component it renders bar 1`] = ` titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -2484,6 +2488,7 @@ exports[`XYChart component it renders horizontal bar 1`] = ` "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -3145,6 +3150,7 @@ exports[`XYChart component it renders horizontal bar 1`] = ` titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -3463,6 +3469,7 @@ exports[`XYChart component it renders line 1`] = ` "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -4124,6 +4131,7 @@ exports[`XYChart component it renders line 1`] = ` titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -4442,6 +4450,7 @@ exports[`XYChart component it renders stacked area 1`] = ` "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -5103,6 +5112,7 @@ exports[`XYChart component it renders stacked area 1`] = ` titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -5421,6 +5431,7 @@ exports[`XYChart component it renders stacked bar 1`] = ` "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -6082,6 +6093,7 @@ exports[`XYChart component it renders stacked bar 1`] = ` titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -6400,6 +6412,7 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = ` "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -7061,6 +7074,7 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = ` titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object {}, "splitSeriesTitles": Object { @@ -7405,6 +7419,7 @@ exports[`XYChart component split chart should render split chart if both, splitR "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object { "b": "b", }, @@ -8266,6 +8281,7 @@ exports[`XYChart component split chart should render split chart if both, splitR titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object { "b": "b", }, @@ -8624,6 +8640,7 @@ exports[`XYChart component split chart should render split chart if splitColumnA "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object { "b": "b", }, @@ -9478,6 +9495,7 @@ exports[`XYChart component split chart should render split chart if splitColumnA titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object { "b": "b", }, @@ -9834,6 +9852,7 @@ exports[`XYChart component split chart should render split chart if splitRowAcce "setColor": [Function], "titles": Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object { "b": "b", @@ -10688,6 +10707,7 @@ exports[`XYChart component split chart should render split chart if splitRowAcce titles={ Object { "first": Object { + "markSizeTitles": Object {}, "splitColumnTitles": Object {}, "splitRowTitles": Object { "b": "b", diff --git a/src/plugins/chart_expressions/expression_xy/public/components/data_layers.tsx b/src/plugins/chart_expressions/expression_xy/public/components/data_layers.tsx index 930acbcabf87b..ca7acd25fd785 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/data_layers.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/data_layers.tsx @@ -54,6 +54,7 @@ interface Props { defaultXScaleType: XScaleType; fieldFormats: LayersFieldFormats; uiState?: PersistedState; + singleTable?: boolean; } export const DataLayers: FC = ({ @@ -75,8 +76,38 @@ export const DataLayers: FC = ({ defaultXScaleType, fieldFormats, uiState, + singleTable, }) => { - const colorAssignments = getColorAssignments(layers, titles, fieldFormats, formattedDatatables); + // for singleTable mode we should use y accessors from all layers for creating correct series name and getting color + const allYAccessors = layers.flatMap((layer) => layer.accessors); + const allColumnsToLabel = layers.reduce((acc, layer) => { + if (layer.columnToLabel) { + return { ...acc, ...JSON.parse(layer.columnToLabel) }; + } + + return acc; + }, {}); + const allYTitles = Object.keys(titles).reduce((acc, key) => { + if (titles[key].yTitles) { + return { ...acc, ...titles[key].yTitles }; + } + return acc; + }, {}); + const colorAssignments = singleTable + ? getColorAssignments( + [ + { + ...layers[0], + layerId: 'commonLayerId', + accessors: allYAccessors, + columnToLabel: JSON.stringify(allColumnsToLabel), + }, + ], + { commonLayerId: { ...titles, yTitles: allYTitles } }, + { commonLayerId: fieldFormats[layers[0].layerId] }, + { commonLayerId: formattedDatatables[layers[0].layerId] } + ) + : getColorAssignments(layers, titles, fieldFormats, formattedDatatables); return ( <> {layers.flatMap((layer) => @@ -118,6 +149,8 @@ export const DataLayers: FC = ({ defaultXScaleType, fieldFormats, uiState, + allYAccessors, + singleTable, }); const index = `${layer.layerId}-${accessorIndex}`; diff --git a/src/plugins/chart_expressions/expression_xy/public/components/legend_color_picker.tsx b/src/plugins/chart_expressions/expression_xy/public/components/legend_color_picker.tsx index 9fb81bc951338..fa4e02a5a9d74 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/legend_color_picker.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/legend_color_picker.tsx @@ -126,10 +126,10 @@ export const LegendColorPickerWrapper: LegendColorPicker = ({ panelPaddingSize="s" > diff --git a/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.test.tsx b/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.test.tsx index da6d5032158cb..fa8638afc68d1 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.test.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.test.tsx @@ -113,6 +113,7 @@ describe('Tooltip', () => { formatFactory={formatFactory} formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor, splitRowAccessor }} + layers={[sampleLayer]} /> ); @@ -133,6 +134,7 @@ describe('Tooltip', () => { formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor, splitRowAccessor }} xDomain={xDomain} + layers={[sampleLayer]} /> ); @@ -153,6 +155,7 @@ describe('Tooltip', () => { formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor, splitRowAccessor }} xDomain={xDomain} + layers={[sampleLayer]} /> ); @@ -170,6 +173,7 @@ describe('Tooltip', () => { formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor, splitRowAccessor }} xDomain={xDomain2} + layers={[sampleLayer]} /> ); @@ -188,6 +192,7 @@ describe('Tooltip', () => { formatFactory={formatFactory} formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor, splitRowAccessor }} + layers={[sampleLayer]} /> ); @@ -213,6 +218,7 @@ describe('Tooltip', () => { formatFactory={formatFactory} formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor, splitRowAccessor }} + layers={[sampleLayer]} /> ); @@ -240,6 +246,7 @@ describe('Tooltip', () => { formatFactory={formatFactory} formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor, splitRowAccessor }} + layers={[sampleLayer]} /> ); @@ -268,6 +275,7 @@ describe('Tooltip', () => { formatFactory={formatFactory} formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor, splitRowAccessor }} + layers={[sampleLayer]} /> ); @@ -295,6 +303,7 @@ describe('Tooltip', () => { formatFactory={formatFactory} formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitColumnAccessor }} + layers={[sampleLayer]} /> ); @@ -322,6 +331,7 @@ describe('Tooltip', () => { formatFactory={formatFactory} formattedDatatables={{ [layerId]: { table: data, formattedColumns: {} } }} splitAccessors={{ splitRowAccessor }} + layers={[sampleLayer]} /> ); diff --git a/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.tsx b/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.tsx index e41fd00a09920..ca7152e485c9f 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/tooltip/tooltip.tsx @@ -8,7 +8,9 @@ import { TooltipInfo, XYChartSeriesIdentifier } from '@elastic/charts'; import { FormatFactory } from '@kbn/field-formats-plugin/common'; +import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils'; import React, { FC } from 'react'; +import { CommonXYDataLayerConfig } from '../../../common'; import { DatatablesWithFormatInfo, getMetaFromSeriesId, @@ -32,6 +34,7 @@ type Props = TooltipInfo & { splitRowAccessor?: string; splitColumnAccessor?: string; }; + layers: CommonXYDataLayerConfig[]; }; export const Tooltip: FC = ({ @@ -43,6 +46,7 @@ export const Tooltip: FC = ({ formattedDatatables, splitAccessors, xDomain, + layers, }) => { const pickedValue = values.find(({ isHighlighted }) => isHighlighted); @@ -53,9 +57,13 @@ export const Tooltip: FC = ({ const data: TooltipData[] = []; const seriesIdentifier = pickedValue.seriesIdentifier as XYChartSeriesIdentifier; const { layerId, xAccessor, yAccessor } = getMetaFromSeriesId(seriesIdentifier.specId); - const { formattedColumns } = formattedDatatables[layerId]; + const { formattedColumns, table } = formattedDatatables[layerId]; const layerTitles = titles[layerId]; const layerFormats = fieldFormats[layerId]; + const markSizeAccessor = layers.find((layer) => layer.layerId === layerId)?.markSizeAccessor; + const markSizeColumnId = markSizeAccessor + ? getAccessorByDimension(markSizeAccessor, table.columns) + : undefined; let headerFormatter; if (header && xAccessor) { headerFormatter = formattedColumns[xAccessor] @@ -75,6 +83,12 @@ export const Tooltip: FC = ({ value: yFormatter ? yFormatter.convert(pickedValue.value) : `${pickedValue.value}`, }); } + if (markSizeColumnId && pickedValue.formattedMarkValue) { + data.push({ + label: layerTitles?.markSizeTitles?.[markSizeColumnId], + value: pickedValue.formattedMarkValue, + }); + } seriesIdentifier.splitAccessors.forEach((splitValue, key) => { const splitSeriesFormatter = formattedColumns[key] ? null diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx index 1c3b19d044b81..16db7775a6d86 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx @@ -214,6 +214,7 @@ export function XYChart({ xAxisConfig, splitColumnAccessor, splitRowAccessor, + singleTable, } = args; const chartRef = useRef(null); const chartTheme = chartsThemeService.useChartsTheme(); @@ -234,7 +235,7 @@ export function XYChart({ const getShowLegendDefault = useCallback(() => { const legendStateDefault = legend.isVisible && !legend.showSingleSeries ? chartHasMoreThanOneSeries : legend.isVisible; - return uiState?.get('vis.legendOpen', legendStateDefault) || legendStateDefault; + return uiState?.get('vis.legendOpen', legendStateDefault) ?? legendStateDefault; }, [chartHasMoreThanOneSeries, legend.isVisible, legend.showSingleSeries, uiState]); const [showLegend, setShowLegend] = useState(() => getShowLegendDefault()); @@ -476,7 +477,9 @@ export function XYChart({ }) ); - const fit = !hasBarOrArea && extent.mode === AxisExtentModes.DATA_BOUNDS; + const fit = Boolean( + (!hasBarOrArea || axis.extent?.enforce) && extent.mode === AxisExtentModes.DATA_BOUNDS + ); const padding = axis.boundsMargin || undefined; let min: number = NaN; @@ -805,6 +808,7 @@ export function XYChart({ splitColumnAccessor: splitColumnId, splitRowAccessor: splitRowId, }} + layers={dataLayers} xDomain={isTimeViz ? rawXDomain : undefined} /> ) @@ -880,7 +884,7 @@ export function XYChart({ id={axis.groupId} groupId={axis.groupId} position={axis.position} - title={getYAxesTitles(axis.series)} + title={axis.title || getYAxesTitles(axis.series)} gridLine={{ visible: axis.showGridLines, }} @@ -936,6 +940,7 @@ export function XYChart({ defaultXScaleType={defaultXScaleType} fieldFormats={fieldFormats} uiState={uiState} + singleTable={singleTable} /> )} {referenceLineLayers.length ? ( diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.test.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.test.ts index f1a11bbe06185..6442845b0ecf3 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.test.ts +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.test.ts @@ -118,21 +118,28 @@ describe('color_assignment', () => { }, }; + const titles = { + [layers[0].layerId]: { + yTitles: { + y1: 'test1', + y2: 'test2', + y3: 'test3', + y4: 'test4', + }, + }, + [layers[1].layerId]: { + yTitles: { + y1: 'test1', + y2: 'test2', + y3: 'test3', + y4: 'test4', + }, + }, + }; + describe('totalSeriesCount', () => { it('should calculate total number of series per palette', () => { - const assignments = getColorAssignments( - layers, - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, - fieldFormats, - formattedDatatables - ); + const assignments = getColorAssignments(layers, titles, fieldFormats, formattedDatatables); // two y accessors, with 3 splitted series expect(assignments.palette1.totalSeriesCount).toEqual(2 * 3); expect(assignments.palette2.totalSeriesCount).toEqual(2 * 3); @@ -141,14 +148,7 @@ describe('color_assignment', () => { it('should calculate total number of series spanning multible layers', () => { const assignments = getColorAssignments( [layers[0], { ...layers[1], palette: layers[0].palette }], - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, + titles, fieldFormats, formattedDatatables ); @@ -160,14 +160,7 @@ describe('color_assignment', () => { it('should calculate total number of series for non split series', () => { const assignments = getColorAssignments( [layers[0], { ...layers[1], palette: layers[0].palette, splitAccessors: undefined }], - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, + titles, fieldFormats, formattedDatatables ); @@ -200,14 +193,7 @@ describe('color_assignment', () => { const assignments = getColorAssignments( newLayers, - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, + titles, fieldFormats, newFormattedDatatables ); @@ -230,14 +216,7 @@ describe('color_assignment', () => { }; const assignments = getColorAssignments( newLayers, - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, + titles, fieldFormats, newFormattedDatatables ); @@ -249,44 +228,20 @@ describe('color_assignment', () => { describe('getRank', () => { it('should return the correct rank for a series key', () => { - const assignments = getColorAssignments( - layers, - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, - fieldFormats, - formattedDatatables - ); + const assignments = getColorAssignments(layers, titles, fieldFormats, formattedDatatables); // 3 series in front of 2/y2 - 1/y1, 1/y2 and 2/y1 - expect(assignments.palette1.getRank(layers[0], '2 - test2')).toEqual(3); + expect(assignments.palette1.getRank(layers[0].layerId, '2 - test2')).toEqual(3); // 1 series in front of 1/y4 - 1/y3 - expect(assignments.palette2.getRank(layers[1], '1 - test4')).toEqual(1); + expect(assignments.palette2.getRank(layers[1].layerId, '1 - test4')).toEqual(1); }); it('should return the correct rank for a series key spanning multiple layers', () => { const newLayers = [layers[0], { ...layers[1], palette: layers[0].palette }]; - const assignments = getColorAssignments( - newLayers, - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, - fieldFormats, - formattedDatatables - ); + const assignments = getColorAssignments(newLayers, titles, fieldFormats, formattedDatatables); // 3 series in front of 2/y2 - 1/y1, 1/y2 and 2/y1 - expect(assignments.palette1.getRank(newLayers[0], '2 - test2')).toEqual(3); + expect(assignments.palette1.getRank(newLayers[0].layerId, '2 - test2')).toEqual(3); // 2 series in front for the current layer (1/y3, 1/y4), plus all 6 series from the first layer - expect(assignments.palette1.getRank(newLayers[1], '2 - test3')).toEqual(8); + expect(assignments.palette1.getRank(newLayers[1].layerId, '2 - test3')).toEqual(8); }); it('should return the correct rank for a series without a split', () => { @@ -294,23 +249,11 @@ describe('color_assignment', () => { layers[0], { ...layers[1], palette: layers[0].palette, splitAccessors: undefined }, ]; - const assignments = getColorAssignments( - newLayers, - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, - fieldFormats, - formattedDatatables - ); + const assignments = getColorAssignments(newLayers, titles, fieldFormats, formattedDatatables); // 3 series in front of 2/y2 - 1/y1, 1/y2 and 2/y1 - expect(assignments.palette1.getRank(newLayers[0], '2 - test2')).toEqual(3); + expect(assignments.palette1.getRank(newLayers[0].layerId, '2 - test2')).toEqual(3); // 1 series in front for the current layer (y3), plus all 6 series from the first layer - expect(assignments.palette1.getRank(newLayers[1], 'test4')).toEqual(7); + expect(assignments.palette1.getRank(newLayers[1].layerId, 'test4')).toEqual(7); }); it('should return the correct rank for a series with a non-primitive value', () => { @@ -336,21 +279,14 @@ describe('color_assignment', () => { const assignments = getColorAssignments( newLayers, - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, + titles, fieldFormats, newFormattedDatatables ); fieldFormats.first.splitSeriesAccessors.split1.formatter.convert = (x) => x as string; // 3 series in front of (complex object)/y1 - abc/y1, abc/y2 - expect(assignments.palette1.getRank(layers[0], 'formatted - test1')).toEqual(2); + expect(assignments.palette1.getRank(layers[0].layerId, 'formatted - test1')).toEqual(2); }); it('should handle missing columns', () => { @@ -365,20 +301,13 @@ describe('color_assignment', () => { const assignments = getColorAssignments( newLayers, - { - yTitles: { - y1: 'test1', - y2: 'test2', - y3: 'test3', - y4: 'test4', - }, - }, + titles, fieldFormats, newFormattedDatatables ); // if the split column is missing, assume it is the first splitted series. One series in front - 0/y1 - expect(assignments.palette1.getRank(layers[0], 'test2')).toEqual(1); + expect(assignments.palette1.getRank(layers[0].layerId, 'test2')).toEqual(1); }); }); }); diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts index c7139cf036fa2..2cce918d4b798 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts @@ -13,7 +13,12 @@ import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils' import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common'; import { isDataLayer } from './visualization'; import { CommonXYDataLayerConfig, CommonXYLayerConfig } from '../../common'; -import { LayerAccessorsTitles, LayerFieldFormats, LayersFieldFormats } from './layers'; +import { + LayerAccessorsTitles, + LayerFieldFormats, + LayersAccessorsTitles, + LayersFieldFormats, +} from './layers'; import { DatatablesWithFormatInfo, DatatableWithFormatInfo } from './data_layers'; export const defaultReferenceLineColor = euiLightVars.euiColorDarkShade; @@ -22,7 +27,7 @@ export type ColorAssignments = Record< string, { totalSeriesCount: number; - getRank(sortedLayer: CommonXYDataLayerConfig, seriesName: string): number; + getRank(layerId: string, seriesName: string): number; } >; @@ -53,7 +58,8 @@ export const getAllSeries = ( accessors: Array, columnToLabel: CommonXYDataLayerConfig['columnToLabel'], titles: LayerAccessorsTitles, - fieldFormats: LayerFieldFormats + fieldFormats: LayerFieldFormats, + accessorsCount: number ) => { if (!formattedDatatable.table) { return []; @@ -71,7 +77,7 @@ export const getAllSeries = ( const yTitle = columnToLabelMap[yAccessor] ?? titles?.yTitles?.[yAccessor] ?? null; let name = yTitle; if (splitName) { - name = accessors.length > 1 ? `${splitName} - ${yTitle}` : splitName; + name = accessorsCount > 1 ? `${splitName} - ${yTitle}` : splitName; } if (!allSeries.includes(name)) { @@ -85,7 +91,7 @@ export const getAllSeries = ( export function getColorAssignments( layers: CommonXYLayerConfig[], - titles: LayerAccessorsTitles, + titles: LayersAccessorsTitles, fieldFormats: LayersFieldFormats, formattedDatatables: DatatablesWithFormatInfo ): ColorAssignments { @@ -111,22 +117,22 @@ export function getColorAssignments( layer.splitAccessors, layer.accessors, layer.columnToLabel, - titles, - fieldFormats[layer.layerId] + titles[layer.layerId], + fieldFormats[layer.layerId], + layer.accessors.length ) || []; return { numberOfSeries: allSeries.length, allSeries }; }); + const totalSeriesCount = seriesPerLayer.reduce( (sum, perLayer) => sum + perLayer.numberOfSeries, 0 ); return { totalSeriesCount, - getRank(sortedLayer: CommonXYDataLayerConfig, seriesName: string) { - const layerIndex = paletteLayers.findIndex( - (layer) => sortedLayer.layerId === layer.layerId - ); + getRank(layerId: string, seriesName: string) { + const layerIndex = paletteLayers.findIndex((layer) => layerId === layer.layerId); const currentSeriesPerLayer = seriesPerLayer[layerIndex]; const rank = currentSeriesPerLayer.allSeries.indexOf(seriesName); return ( diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx b/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx index 96c2fece7301d..bb37e1c2a56fb 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx @@ -53,6 +53,8 @@ type GetSeriesPropsFn = (config: { defaultXScaleType: XScaleType; fieldFormats: LayersFieldFormats; uiState?: PersistedState; + allYAccessors: Array; + singleTable?: boolean; }) => SeriesSpec; type GetSeriesNameFn = ( @@ -78,7 +80,8 @@ type GetColorFn = ( getSeriesNameFn: (d: XYChartSeriesIdentifier) => SeriesName; syncColors?: boolean; }, - uiState?: PersistedState + uiState?: PersistedState, + singleTable?: boolean ) => string | null; type GetPointConfigFn = (config: { @@ -307,7 +310,8 @@ const getLineConfig: GetLineConfigFn = ({ showLines, lineWidth }) => ({ const getColor: GetColorFn = ( series, { layer, accessor, colorAssignments, paletteService, syncColors, getSeriesNameFn }, - uiState + uiState, + singleTable ) => { const overwriteColor = getSeriesColor(layer, accessor); if (overwriteColor !== null) { @@ -327,7 +331,7 @@ const getColor: GetColorFn = ( { name, totalSeriesAtDepth: colorAssignment.totalSeriesCount, - rankAtDepth: colorAssignment.getRank(layer, name), + rankAtDepth: colorAssignment.getRank(singleTable ? 'commonLayerId' : layer.layerId, name), }, ]; return paletteService.get(layer.palette.name).getCategoricalColor( @@ -343,7 +347,7 @@ const getColor: GetColorFn = ( }; const EMPTY_ACCESSOR = '-'; -const SPLIT_CHAR = '.'; +const SPLIT_CHAR = ':'; export const generateSeriesId = ( { layerId }: Pick, @@ -383,6 +387,8 @@ export const getSeriesProps: GetSeriesPropsFn = ({ defaultXScaleType, fieldFormats, uiState, + allYAccessors, + singleTable, }): SeriesSpec => { const { table, isStacked, markSizeAccessor } = layer; const isPercentage = layer.isPercentage; @@ -449,7 +455,7 @@ export const getSeriesProps: GetSeriesPropsFn = ({ d, { splitAccessors: layer.splitAccessors || [], - accessorsCount: layer.accessors.length, + accessorsCount: singleTable ? allYAccessors.length : layer.accessors.length, alreadyFormattedColumns: formattedColumns, columns: formattedTable.columns, splitAccessorsFormats: fieldFormats[layer.layerId].splitSeriesAccessors, @@ -489,7 +495,8 @@ export const getSeriesProps: GetSeriesPropsFn = ({ getSeriesNameFn, syncColors, }, - uiState + uiState, + singleTable ), groupId: yAxis?.groupId, enableHistogramMode, diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/layers.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/layers.ts index 8ff87e381cf48..7dc26e4c76cf1 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/layers.ts +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/layers.ts @@ -61,6 +61,7 @@ export interface LayerAccessorsTitles { splitSeriesTitles?: AccessorsTitles; splitColumnTitles?: AccessorsTitles; splitRowTitles?: AccessorsTitles; + markSizeTitles?: AccessorsTitles; } export type LayersAccessorsTitles = Record; @@ -192,11 +193,18 @@ const getTitleForYAccessor = ( group.series.some(({ accessor, layer }) => accessor === yAccessor && layer === layerId) ); - return axisGroup?.title || column!.name; + return column?.name ?? axisGroup?.title; }; export const getLayerTitles = ( - { xAccessor, accessors, splitAccessors = [], table, layerId }: CommonXYDataLayerConfig, + { + xAccessor, + accessors, + splitAccessors = [], + table, + layerId, + markSizeAccessor, + }: CommonXYDataLayerConfig, { splitColumnAccessor, splitRowAccessor }: SplitAccessors, { xTitle }: CustomTitles, groups: GroupsConfiguration @@ -231,6 +239,7 @@ export const getLayerTitles = ( }), {} ), + markSizeTitles: mapTitle(markSizeAccessor), splitColumnTitles: mapTitle(splitColumnAccessor), splitRowTitles: mapTitle(splitRowAccessor), }; diff --git a/src/plugins/vis_types/xy/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_types/xy/public/__snapshots__/to_ast.test.ts.snap index 8f11e3521cdd7..37b8daab6a7a4 100644 --- a/src/plugins/vis_types/xy/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_types/xy/public/__snapshots__/to_ast.test.ts.snap @@ -20,6 +20,9 @@ Object { "showTooltip": Array [ true, ], + "singleTable": Array [ + true, + ], "valueLabels": Array [ "hide", ], diff --git a/src/plugins/vis_types/xy/public/to_ast.ts b/src/plugins/vis_types/xy/public/to_ast.ts index 07a58a258abd6..7f8212eef7625 100644 --- a/src/plugins/vis_types/xy/public/to_ast.ts +++ b/src/plugins/vis_types/xy/public/to_ast.ts @@ -42,6 +42,8 @@ interface Bounds { max?: string | number; } +type YDimension = Omit & { accessor: string }; + const getCurveType = (type?: InterpolationMode) => { switch (type) { case 'cardinal': @@ -67,13 +69,13 @@ const prepareLengend = (params: VisParams, legendSize?: LegendSize) => { return buildExpression([legend]); }; -const getCorrectAccessor = (yAccessor: Dimension, aggId: string) => { +const getCorrectAccessor = (yAccessor: Dimension | YDimension, aggId: string) => { return typeof yAccessor.accessor === 'number' ? `col-${yAccessor.accessor}-${aggId}` : yAccessor.accessor; }; -const prepareDecoration = (axisId: string, yAccessor: Dimension, aggId: string) => { +const prepareDecoration = (axisId: string, yAccessor: YDimension, aggId: string) => { const dataDecorationConfig = buildExpressionFunction('dataDecorationConfig', { forAccessor: getCorrectAccessor(yAccessor, aggId), axisId, @@ -99,7 +101,7 @@ const prepareLayers = ( seriesParam: SeriesParam, isHistogram: boolean, valueAxes: ValueAxis[], - yAccessors: Dimension[], + yAccessors: YDimension[], xAccessor: Dimension | null, splitAccessors?: Dimension[], markSizeAccessor?: Dimension, @@ -294,7 +296,7 @@ const prepareReferenceLine = (thresholdLine: ThresholdLine, axisId: string) => { return buildExpression([referenceLine]); }; -const prepareVisDimension = (data: Dimension) => { +const prepareVisDimension = (data: Dimension | YDimension) => { const visDimension = buildExpressionFunction('visdimension', { accessor: data.accessor }); if (data.format) { @@ -389,16 +391,23 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params legendSize = LegendSize.AUTO; } - const yAccessors = (dimensions.y || []).reduce>((acc, yDimension) => { - const yAgg = responseAggs[yDimension.accessor]; - const aggId = getSafeId(yAgg.id); - if (acc[aggId]) { - acc[aggId].push(yDimension); - } else { - acc[aggId] = [yDimension]; - } - return acc; - }, {}); + const yAccessors = (dimensions.y || []).reduce>( + (acc, yDimension) => { + const yAgg = responseAggs[yDimension.accessor]; + const aggId = getSafeId(yAgg.id); + const dimension: YDimension = { + ...yDimension, + accessor: getCorrectAccessor(yDimension, yAgg.id), + }; + if (acc[aggId]) { + acc[aggId].push(dimension); + } else { + acc[aggId] = [dimension]; + } + return acc; + }, + {} + ); const xScale = vis.params.categoryAxes[0].scale; @@ -417,7 +426,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params const visTypeXy = buildExpressionFunction('layeredXyVis', { layers: [ ...finalSeriesParams - .filter((seriesParam) => seriesParam.show) + .filter((seriesParam) => seriesParam.show && yAccessors[seriesParam.data.id]) .map((seriesParam) => prepareLayers( seriesParam, @@ -474,6 +483,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params splitColumnAccessor: dimensions.splitColumn?.map(prepareVisDimension), splitRowAccessor: dimensions.splitRow?.map(prepareVisDimension), valueLabels: vis.params.labels.show ? 'show' : 'hide', + singleTable: true, }); const ast = buildExpression(mapColumn ? [mapColumn, visTypeXy] : [visTypeXy]);