- {messages.map(({ shortMessage, longMessage }, index) => (
-
0 ? 0 : euiTheme.size.base} ${euiTheme.size.base}
- ${index > 0 ? euiTheme.size.s : 0};
- `}
- >
- {index ? : null}
-
- {shortMessage}
-
-
-
- ))}
+
+ {messages.map(({ shortMessage, longMessage }, index) => {
+ return (
+ <>
+ {index ?
: null}
+
+ >
+ );
+ })}
);
diff --git a/x-pack/plugins/lens/public/shared_components/info_badges/info_badge.test.tsx b/x-pack/plugins/lens/public/shared_components/info_badges/info_badge.test.tsx
new file mode 100644
index 0000000000000..f1a718b5c2976
--- /dev/null
+++ b/x-pack/plugins/lens/public/shared_components/info_badges/info_badge.test.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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 React from 'react';
+import { render } from '@testing-library/react';
+import { InfoBadge } from './info_badge';
+
+describe('Info badge', () => {
+ it('should render no icon if no palette is passed', () => {
+ const res = render(
+
+ );
+
+ expect(res.queryByTestId('prefix-0-icon')).not.toBeInTheDocument();
+ expect(res.queryByTestId('prefix-0-palette')).not.toBeInTheDocument();
+ });
+
+ it('should render an icon if a single palette color is passed over', () => {
+ const res = render(
+
+ );
+
+ expect(res.queryByTestId('prefix-0-icon')).toBeInTheDocument();
+ expect(res.queryByTestId('prefix-0-palette')).not.toBeInTheDocument();
+ });
+
+ it('should render both an icon an a palette indicator if multiple colors are passed over', () => {
+ const res = render(
+
+ );
+
+ expect(res.queryByTestId('prefix-0-icon')).toBeInTheDocument();
+ expect(res.queryByTestId('prefix-0-palette')).toBeInTheDocument();
+ });
+
+ it('should render children as value when passed', () => {
+ const res = render(
+
+ 100%
+
+ );
+ expect(res.getByText('100%')).toBeInTheDocument();
+ });
+});
diff --git a/x-pack/plugins/lens/public/shared_components/info_badges/info_badge.tsx b/x-pack/plugins/lens/public/shared_components/info_badges/info_badge.tsx
new file mode 100644
index 0000000000000..cc78c59b69eea
--- /dev/null
+++ b/x-pack/plugins/lens/public/shared_components/info_badges/info_badge.tsx
@@ -0,0 +1,82 @@
+/*
+ * 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 {
+ EuiColorPaletteDisplay,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiText,
+ useEuiTheme,
+} from '@elastic/eui';
+import { css } from '@emotion/react';
+import React, { type ReactChildren, type ReactChild } from 'react';
+
+export function InfoBadge({
+ title,
+ dataView,
+ index,
+ palette,
+ children,
+ 'data-test-subj-prefix': dataTestSubjPrefix,
+}: {
+ title: string;
+ dataView: string;
+ index: number;
+ palette?: string[];
+ children?: ReactChild | ReactChildren;
+ 'data-test-subj-prefix': string;
+}) {
+ const { euiTheme } = useEuiTheme();
+ const hasColor = Boolean(palette);
+ const hasSingleColor = palette && palette.length === 1;
+ const hasMultipleColors = palette && palette.length > 1;
+ const iconType = hasSingleColor ? 'stopFilled' : 'color';
+ return (
+
+
+ {hasColor ? (
+
+
+
+ ) : null}
+
+
+ {title}
+
+
+ {children}
+
+ {hasMultipleColors ? (
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts
index a9a98c50c38d6..407aacba9e4a2 100644
--- a/x-pack/plugins/lens/public/state_management/selectors.ts
+++ b/x-pack/plugins/lens/public/state_management/selectors.ts
@@ -11,7 +11,7 @@ import { SavedObjectReference } from '@kbn/core/public';
import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common';
import { LensState } from './types';
import { Datasource, DatasourceMap, VisualizationMap } from '../types';
-import { getDatasourceLayers } from '../editor_frame_service/editor_frame';
+import { getDatasourceLayers } from './utils';
export const selectPersistedDoc = (state: LensState) => state.lens.persistedDoc;
export const selectQuery = (state: LensState) => state.lens.query;
diff --git a/x-pack/plugins/lens/public/state_management/utils.ts b/x-pack/plugins/lens/public/state_management/utils.ts
new file mode 100644
index 0000000000000..3d19326938d35
--- /dev/null
+++ b/x-pack/plugins/lens/public/state_management/utils.ts
@@ -0,0 +1,34 @@
+/*
+ * 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 memoizeOne from 'memoize-one';
+import type { DatasourceMap, DatasourceLayers } from '../types';
+import type { DatasourceStates, DataViewsState } from './types';
+
+export const getDatasourceLayers = memoizeOne(function getDatasourceLayers(
+ datasourceStates: DatasourceStates,
+ datasourceMap: DatasourceMap,
+ indexPatterns: DataViewsState['indexPatterns']
+) {
+ const datasourceLayers: DatasourceLayers = {};
+ Object.keys(datasourceMap)
+ .filter((id) => datasourceStates[id] && !datasourceStates[id].isLoading)
+ .forEach((id) => {
+ const datasourceState = datasourceStates[id].state;
+ const datasource = datasourceMap[id];
+
+ const layers = datasource.getLayers(datasourceState);
+ layers.forEach((layer) => {
+ datasourceLayers[layer] = datasourceMap[id].getPublicAPI({
+ state: datasourceState,
+ layerId: layer,
+ indexPatterns,
+ });
+ });
+ });
+ return datasourceLayers;
+});
diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts
index faa3d8e14e5bd..1904be8c1541c 100644
--- a/x-pack/plugins/lens/public/types.ts
+++ b/x-pack/plugins/lens/public/types.ts
@@ -164,6 +164,7 @@ export interface VisualizationInfo {
icon?: IconType;
label?: string;
dimensions: Array<{ name: string; id: string; dimensionType: string }>;
+ palette?: string[];
}>;
}
@@ -1291,7 +1292,7 @@ export interface Visualization
{
props: VisualizationStateFromContextChangeProps
) => Suggestion | undefined;
- getVisualizationInfo?: (state: T) => VisualizationInfo;
+ getVisualizationInfo?: (state: T, frame?: FramePublicAPI) => VisualizationInfo;
/**
* A visualization can return custom dimensions for the reporting tool
*/
diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts
index 0ac87039c819c..dd08bd8a2c5f2 100644
--- a/x-pack/plugins/lens/public/utils.ts
+++ b/x-pack/plugins/lens/public/utils.ts
@@ -361,3 +361,7 @@ export const getSearchWarningMessages = (
return [...warningsMap.values()].flat();
};
+
+export function nonNullable(v: T): v is NonNullable {
+ return v != null;
+}
diff --git a/x-pack/plugins/lens/public/visualization_container.scss b/x-pack/plugins/lens/public/visualization_container.scss
index cdadb22feb634..028b8aa8a3a14 100644
--- a/x-pack/plugins/lens/public/visualization_container.scss
+++ b/x-pack/plugins/lens/public/visualization_container.scss
@@ -30,5 +30,6 @@
// Make the visualization modifiers icon appear only on panel hover
.embPanel__content:hover .lnsEmbeddablePanelFeatureList_button {
color: $euiTextColor;
- transition: color $euiAnimSpeedSlow;
+ background: $euiColorEmptyShade;
+ transition: color $euiAnimSpeedSlow, background $euiAnimSpeedSlow;
}
\ No newline at end of file
diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx
index 9d7266b711bef..ecc6bc958124f 100644
--- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx
@@ -610,7 +610,11 @@ export const getDatatableVisualization = ({
return suggestion;
},
- getVisualizationInfo(state: DatatableVisualizationState) {
+ getVisualizationInfo(state) {
+ const visibleMetricColumns = state.columns.filter(
+ (c) => !c.hidden && c.colorMode && c.colorMode !== 'none'
+ );
+
return {
layers: [
{
@@ -618,6 +622,11 @@ export const getDatatableVisualization = ({
layerType: state.layerType,
chartType: 'table',
...this.getDescription(state),
+ palette:
+ // if multiple columns have color by value, do not show the palette for now: see #154349
+ visibleMetricColumns.length > 1
+ ? undefined
+ : visibleMetricColumns[0]?.palette?.params?.stops?.map(({ color }) => color),
dimensions: state.columns.map((column) => {
let name = i18n.translate('xpack.lens.datatable.metric', {
defaultMessage: 'Metric',
diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx
index aa8ef574cd7c7..ddab89444419f 100644
--- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx
@@ -27,6 +27,7 @@ import { LayerTypes } from '@kbn/expression-xy-plugin/public';
import type { FormBasedPersistedState } from '../../datasources/form_based/types';
import type {
DatasourceLayers,
+ FramePublicAPI,
OperationMetadata,
Suggestion,
UserMessage,
@@ -234,23 +235,12 @@ export const getGaugeVisualization = ({
getSuggestions,
getConfiguration({ state, frame }) {
- const hasColoring = Boolean(state.colorMode !== 'none' && state.palette?.params?.stops);
-
const row = state?.layerId ? frame?.activeData?.[state?.layerId]?.rows?.[0] : undefined;
- const { metricAccessor } = state ?? {};
-
- const accessors = getAccessorsFromState(state);
-
- let palette;
- if (!(row == null || metricAccessor == null || state?.palette == null || !hasColoring)) {
- const currentMinMax = {
- min: getMinValue(row, accessors),
- max: getMaxValue(row, accessors),
- };
-
- const displayStops = applyPaletteParams(paletteService, state?.palette, currentMinMax);
- palette = displayStops.map(({ color }) => color);
- }
+ const { palette, metricAccessor, accessors } = getConfigurationAccessorsAndPalette(
+ state,
+ paletteService,
+ frame.activeData
+ );
return {
groups: [
@@ -602,11 +592,16 @@ export const getGaugeVisualization = ({
return suggestion;
},
- getVisualizationInfo(state: GaugeVisualizationState) {
+ getVisualizationInfo(state, frame) {
+ const { palette, accessors } = getConfigurationAccessorsAndPalette(
+ state,
+ paletteService,
+ frame?.activeData
+ );
const dimensions = [];
- if (state.metricAccessor) {
+ if (accessors?.metric) {
dimensions.push({
- id: state.metricAccessor,
+ id: accessors.metric,
name: i18n.translate('xpack.lens.gauge.metricLabel', {
defaultMessage: 'Metric',
}),
@@ -614,9 +609,9 @@ export const getGaugeVisualization = ({
});
}
- if (state.maxAccessor) {
+ if (accessors?.max) {
dimensions.push({
- id: state.maxAccessor,
+ id: accessors.max,
name: i18n.translate('xpack.lens.gauge.maxValueLabel', {
defaultMessage: 'Maximum value',
}),
@@ -624,9 +619,9 @@ export const getGaugeVisualization = ({
});
}
- if (state.minAccessor) {
+ if (accessors?.min) {
dimensions.push({
- id: state.minAccessor,
+ id: accessors.min,
name: i18n.translate('xpack.lens.gauge.minValueLabel', {
defaultMessage: 'Minimum value',
}),
@@ -634,9 +629,9 @@ export const getGaugeVisualization = ({
});
}
- if (state.goalAccessor) {
+ if (accessors?.goal) {
dimensions.push({
- id: state.goalAccessor,
+ id: accessors.goal,
name: i18n.translate('xpack.lens.gauge.goalValueLabel', {
defaultMessage: 'Goal value',
}),
@@ -651,8 +646,44 @@ export const getGaugeVisualization = ({
chartType: state.shape,
...this.getDescription(state),
dimensions,
+ palette,
},
],
};
},
});
+
+// When the active data comes from the embeddable side it might not have been indexed by layerId
+// rather using a "default" key
+function getActiveDataForLayer(
+ layerId: string | undefined,
+ activeData: FramePublicAPI['activeData'] | undefined
+) {
+ if (activeData && layerId) {
+ return activeData[layerId] || activeData.default;
+ }
+}
+
+function getConfigurationAccessorsAndPalette(
+ state: GaugeVisualizationState,
+ paletteService: PaletteRegistry,
+ activeData?: FramePublicAPI['activeData']
+) {
+ const hasColoring = Boolean(state.colorMode !== 'none' && state.palette?.params?.stops);
+
+ const row = getActiveDataForLayer(state?.layerId, activeData)?.rows?.[0];
+ const { metricAccessor } = state ?? {};
+
+ const accessors = getAccessorsFromState(state);
+
+ let palette;
+ if (row != null && metricAccessor != null && state?.palette != null && hasColoring) {
+ const currentMinMax = {
+ min: getMinValue(row, accessors),
+ max: getMaxValue(row, accessors),
+ };
+ const displayStops = applyPaletteParams(paletteService, state?.palette, currentMinMax);
+ palette = displayStops.map(({ color }) => color);
+ }
+ return { metricAccessor, accessors, palette };
+}
diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx
index 91c9e60f38d8f..7d448e56e9fee 100644
--- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx
@@ -515,7 +515,7 @@ export const getHeatmapVisualization = ({
return suggestion;
},
- getVisualizationInfo(state: HeatmapVisualizationState) {
+ getVisualizationInfo(state, frame) {
const dimensions = [];
if (state.xAccessor) {
dimensions.push({
@@ -543,6 +543,15 @@ export const getHeatmapVisualization = ({
});
}
+ const { displayStops } = getSafePaletteParams(
+ paletteService,
+ // When the active data comes from the embeddable side it might not have been indexed by layerId
+ // rather using a "default" key
+ frame?.activeData?.[state.layerId] || frame?.activeData?.default,
+ state.valueAccessor,
+ state?.palette && state.palette.accessor === state.valueAccessor ? state.palette : undefined
+ );
+
return {
layers: [
{
@@ -551,6 +560,7 @@ export const getHeatmapVisualization = ({
chartType: state.shape,
...this.getDescription(state),
dimensions,
+ palette: displayStops.length ? displayStops.map(({ color }) => color) : undefined,
},
],
};
diff --git a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx
index 957010b5b131e..85fea42623fc4 100644
--- a/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/legacy_metric/visualization.tsx
@@ -324,6 +324,9 @@ export const getLegacyMetricVisualization = ({
});
}
+ const hasColoring = state.palette != null;
+ const stops = state.palette?.params?.stops || [];
+
return {
layers: [
{
@@ -332,6 +335,7 @@ export const getLegacyMetricVisualization = ({
chartType: 'metric',
...this.getDescription(state),
dimensions,
+ palette: hasColoring ? stops.map(({ color }) => color) : undefined,
},
],
};
diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx
index b44f783cb83ef..b4b0b159a2711 100644
--- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx
@@ -35,6 +35,7 @@ import { Toolbar } from './toolbar';
import { generateId } from '../../id_generator';
import { FormatSelectorOptions } from '../../datasources/form_based/dimension_panel/format_selector';
import { toExpression } from './to_expression';
+import { nonNullable } from '../../utils';
export const DEFAULT_MAX_COLUMNS = 3;
@@ -666,7 +667,7 @@ export const getMetricVisualization = ({
return suggestion;
},
- getVisualizationInfo(state: MetricVisualizationState) {
+ getVisualizationInfo(state) {
const dimensions = [];
if (state.metricAccessor) {
dimensions.push({
@@ -706,6 +707,10 @@ export const getMetricVisualization = ({
});
}
+ const stops = state.palette?.params?.stops || [];
+ const hasStaticColoring = !!state.color;
+ const hasDynamicColoring = !!state.palette;
+
return {
layers: [
{
@@ -714,6 +719,12 @@ export const getMetricVisualization = ({
chartType: 'metric',
...this.getDescription(state),
dimensions,
+ palette: (hasDynamicColoring
+ ? stops.map(({ color }) => color)
+ : hasStaticColoring
+ ? [state.color]
+ : [getDefaultColor(state)]
+ ).filter(nonNullable),
},
],
};
diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
index da52c6efc105b..ca6216f3838b0 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
@@ -46,6 +46,7 @@ import { DimensionDataExtraEditor, DimensionEditor, PieToolbar } from './toolbar
import { LayerSettings } from './layer_settings';
import { checkTableForContainsSmallValues } from './render_helpers';
import { DatasourcePublicAPI } from '../..';
+import { nonNullable } from '../../utils';
const metricLabel = i18n.translate('xpack.lens.pie.groupMetricLabelSingular', {
defaultMessage: 'Metric',
@@ -202,7 +203,7 @@ export const getPieVisualization = ({
// count multiple metrics as a bucket dimension so that the rest of the dimension
// groups UI behaves correctly.
const multiMetricsBucketDimensionCount =
- layer.metrics.length > 1 && state.shape !== 'mosaic' ? 1 : 0;
+ layer.metrics.length > 1 && state.shape !== PieChartTypes.MOSAIC ? 1 : 0;
const totalNonCollapsedAccessors =
accessors.reduce(
@@ -223,8 +224,8 @@ export const getPieVisualization = ({
: undefined;
switch (state.shape) {
- case 'donut':
- case 'pie':
+ case PieChartTypes.DONUT:
+ case PieChartTypes.PIE:
return {
...primaryGroupConfigBaseProps,
groupLabel: i18n.translate('xpack.lens.pie.sliceGroupLabel', {
@@ -239,7 +240,7 @@ export const getPieVisualization = ({
dataTestSubj: 'lnsPie_sliceByDimensionPanel',
hideGrouping: true,
};
- case 'mosaic':
+ case PieChartTypes.MOSAIC:
return {
...primaryGroupConfigBaseProps,
groupLabel: i18n.translate('xpack.lens.pie.verticalAxisLabel', {
@@ -267,7 +268,7 @@ export const getPieVisualization = ({
dimensionsTooMany:
totalNonCollapsedAccessors - PartitionChartsMeta[state.shape].maxBuckets,
dataTestSubj: 'lnsPie_groupByDimensionPanel',
- hideGrouping: state.shape === 'treemap',
+ hideGrouping: state.shape === PieChartTypes.TREEMAP,
};
}
};
@@ -297,7 +298,7 @@ export const getPieVisualization = ({
);
switch (state.shape) {
- case 'mosaic':
+ case PieChartTypes.MOSAIC:
return {
...secondaryGroupConfigBaseProps,
groupLabel: i18n.translate('xpack.lens.pie.horizontalAxisLabel', {
@@ -374,8 +375,8 @@ export const getPieVisualization = ({
return {
groups: [getPrimaryGroupConfig(), getSecondaryGroupConfig(), getMetricGroupConfig()].filter(
- Boolean
- ) as VisualizationDimensionGroupConfig[],
+ nonNullable
+ ),
};
},
@@ -595,7 +596,7 @@ export const getPieVisualization = ({
if (
numericColumn &&
- state.shape === 'waffle' &&
+ state.shape === PieChartTypes.WAFFLE &&
layer.primaryGroups.length &&
checkTableForContainsSmallValues(frame.activeData[layerId], numericColumn.id, 1)
) {
@@ -619,7 +620,7 @@ export const getPieVisualization = ({
return metricColId;
}
})
- .filter(Boolean) as string[];
+ .filter(nonNullable);
if (metricsWithArrayValues.length) {
const labels = metricsWithArrayValues.map(
@@ -650,10 +651,42 @@ export const getPieVisualization = ({
return [...errors, ...warningMessages];
},
- getVisualizationInfo(state: PieVisualizationState) {
+ getVisualizationInfo(state, frame) {
const layer = state.layers[0];
const dimensions: VisualizationInfo['layers'][number]['dimensions'] = [];
+ const datasource = frame?.datasourceLayers[layer.layerId];
+ const hasSliceBy = layer.primaryGroups.length + (layer.secondaryGroups?.length || 0);
+ const hasMultipleMetrics = layer.allowMultipleMetrics;
+ const palette = [];
+
+ if (!hasSliceBy && datasource) {
+ if (hasMultipleMetrics) {
+ palette.push(
+ ...layer.metrics.map(
+ (columnId) =>
+ layer.colorsByDimension?.[columnId] ??
+ getDefaultColorForMultiMetricDimension({
+ layer,
+ columnId,
+ paletteService,
+ datasource,
+ })
+ )
+ );
+ } else if (!layer.primaryGroups?.length) {
+ // This is a logic integrated in the renderer, here simulated
+ // In the particular case of no color assigned (as no sliceBy dimension defined)
+ // the color is generated on the fly from the default palette
+ palette.push(
+ ...paletteService
+ .get(state.palette?.name || 'default')
+ .getCategoricalColors(Math.max(10, layer.metrics.length))
+ .slice(0, layer.metrics.length)
+ );
+ }
+ }
+
layer.metrics.forEach((metric) => {
dimensions.push({
id: metric,
@@ -662,7 +695,7 @@ export const getPieVisualization = ({
});
});
- if (state.shape === 'mosaic' && layer.secondaryGroups && layer.secondaryGroups.length) {
+ if (state.shape === PieChartTypes.MOSAIC && layer.secondaryGroups?.length) {
layer.secondaryGroups.forEach((accessor) => {
dimensions.push({
name: i18n.translate('xpack.lens.pie.horizontalAxisLabel', {
@@ -674,18 +707,19 @@ export const getPieVisualization = ({
});
}
- if (layer.primaryGroups && layer.primaryGroups.length) {
+ if (layer.primaryGroups?.length) {
let name = i18n.translate('xpack.lens.pie.treemapGroupLabel', {
defaultMessage: 'Group by',
});
let dimensionType = 'group_by';
- if (state.shape === 'mosaic') {
+
+ if (state.shape === PieChartTypes.MOSAIC) {
name = i18n.translate('xpack.lens.pie.verticalAxisLabel', {
defaultMessage: 'Vertical axis',
});
dimensionType = 'vertical_axis';
}
- if (state.shape === 'donut' || state.shape === 'pie') {
+ if (state.shape === PieChartTypes.DONUT || state.shape === PieChartTypes.PIE) {
name = i18n.translate('xpack.lens.pie.sliceGroupLabel', {
defaultMessage: 'Slice by',
});
@@ -698,8 +732,18 @@ export const getPieVisualization = ({
id: accessor,
});
});
+
+ if (layer.primaryGroups.some((id) => !isCollapsed(id, layer))) {
+ palette.push(
+ ...paletteService
+ .get(state.palette?.name || 'default')
+ .getCategoricalColors(10, state.palette?.params)
+ );
+ }
}
+ const finalPalette = palette.filter(nonNullable);
+
return {
layers: [
{
@@ -708,6 +752,7 @@ export const getPieVisualization = ({
chartType: state.shape,
...this.getDescription(state),
dimensions,
+ palette: finalPalette.length ? finalPalette : undefined,
},
],
};
diff --git a/x-pack/plugins/lens/public/visualizations/xy/color_assignment.ts b/x-pack/plugins/lens/public/visualizations/xy/color_assignment.ts
index 5c0afb1eb47a7..1416797ad8014 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/color_assignment.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/color_assignment.ts
@@ -44,15 +44,13 @@ export function getColorAssignments(
): ColorAssignments {
const layersPerPalette: Record = {};
- layers
- .filter((layer): layer is XYDataLayerConfig => isDataLayer(layer))
- .forEach((layer) => {
- const palette = layer.palette?.name || 'default';
- if (!layersPerPalette[palette]) {
- layersPerPalette[palette] = [];
- }
- layersPerPalette[palette].push(layer);
- });
+ layers.filter(isDataLayer).forEach((layer) => {
+ const palette = layer.palette?.name || 'default';
+ if (!layersPerPalette[palette]) {
+ layersPerPalette[palette] = [];
+ }
+ layersPerPalette[palette].push(layer);
+ });
return mapValues(layersPerPalette, (paletteLayers) => {
const seriesPerLayer = paletteLayers.map((layer, layerIndex) => {
diff --git a/x-pack/plugins/lens/public/visualizations/xy/info_badges.tsx b/x-pack/plugins/lens/public/visualizations/xy/info_badges.tsx
index d3c0ac1653ad5..fcff325cb9b23 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/info_badges.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/info_badges.tsx
@@ -5,10 +5,9 @@
* 2.0.
*/
-import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui';
-import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import React from 'react';
+import { InfoBadge } from '../../shared_components/info_badges/info_badge';
import { FramePublicAPI, VisualizationInfo } from '../../types';
import { XYAnnotationLayerConfig } from './types';
@@ -21,30 +20,25 @@ export function IgnoredGlobalFiltersEntries({
visualizationInfo: VisualizationInfo;
dataViews: FramePublicAPI['dataViews'];
}) {
- const { euiTheme } = useEuiTheme();
return (
<>
{layers.map((layer, layerIndex) => {
const dataView = dataViews.indexPatterns[layer.indexPatternId];
+ const layerInfo = visualizationInfo.layers.find(({ layerId }) => layerId === layer.layerId);
const layerTitle =
- visualizationInfo.layers.find(({ layerId, label }) => layerId === layer.layerId)?.label ||
+ layerInfo?.label ||
i18n.translate('xpack.lens.xyChart.layerAnnotationsLabel', {
defaultMessage: 'Annotations',
});
+ const layerPalette = layerInfo?.palette;
return (
-
-
-
- {layerTitle}
-
-
-
+
);
})}
>
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts
index 9b82ab8c0d90e..9f1d1625835c1 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts
@@ -2242,7 +2242,8 @@ describe('xy_visualization', () => {
expect(yConfigs?.accessors[1].columnId).toEqual('b');
expect(yConfigs?.accessors[1].color).toEqual('green');
- paletteGetter.mockClear();
+ // This call restores the initial state of the paletteGetter
+ paletteGetter.mockRestore();
});
});
});
diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
index 2f5df56c7b42d..1ecd9adbd8cc5 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx
@@ -26,6 +26,7 @@ import {
isDraggedDataViewField,
isOperationFromCompatibleGroup,
isOperationFromTheSameGroup,
+ nonNullable,
renewIDs,
} from '../../utils';
import { getSuggestions } from './xy_suggestions';
@@ -74,6 +75,7 @@ import {
getUniqueLabels,
onAnnotationDrop,
isDateHistogram,
+ getSingleColorAnnotationConfig,
} from './annotations/helpers';
import {
checkXAccessorCompatibility,
@@ -868,7 +870,7 @@ export const getXyVisualization = ({
);
}
- const info = getNotifiableFeatures(state, frame.dataViews);
+ const info = getNotifiableFeatures(state, frame, paletteService, fieldFormats);
return errors.concat(warnings, info);
},
@@ -913,7 +915,9 @@ export const getXyVisualization = ({
return suggestion;
},
- getVisualizationInfo,
+ getVisualizationInfo(state, frame) {
+ return getVisualizationInfo(state, frame, paletteService, fieldFormats);
+ },
});
const getMappedAccessors = ({
@@ -954,18 +958,26 @@ const getMappedAccessors = ({
return mappedAccessors;
};
-function getVisualizationInfo(state: XYState) {
+function getVisualizationInfo(
+ state: XYState,
+ frame: Partial | undefined,
+ paletteService: PaletteRegistry,
+ fieldFormats: FieldFormatsStart
+) {
const isHorizontal = isHorizontalChart(state.layers);
const visualizationLayersInfo = state.layers.map((layer) => {
+ const palette = [];
const dimensions = [];
let chartType: SeriesType | undefined;
let icon;
let label;
+
if (isDataLayer(layer)) {
chartType = layer.seriesType;
const layerVisType = visualizationTypes.find((visType) => visType.id === chartType);
icon = layerVisType?.icon;
label = layerVisType?.fullLabel || layerVisType?.label;
+
if (layer.xAccessor) {
dimensions.push({
name: getAxisName('x', { isHorizontal }),
@@ -981,6 +993,21 @@ function getVisualizationInfo(state: XYState) {
dimensionType: 'y',
});
});
+ if (frame?.datasourceLayers && frame.activeData) {
+ const sortedAccessors: string[] = getSortedAccessors(
+ frame.datasourceLayers[layer.layerId],
+ layer
+ );
+ const mappedAccessors = getMappedAccessors({
+ state,
+ frame: frame as Pick,
+ layer,
+ fieldFormats,
+ paletteService,
+ accessors: sortedAccessors,
+ });
+ palette.push(...mappedAccessors.flatMap(({ color }) => color));
+ }
}
if (layer.splitAccessor) {
dimensions.push({
@@ -990,6 +1017,13 @@ function getVisualizationInfo(state: XYState) {
dimensionType: 'breakdown',
id: layer.splitAccessor,
});
+ if (!layer.collapseFn) {
+ palette.push(
+ ...paletteService
+ .get(layer.palette?.name || 'default')
+ .getCategoricalColors(10, layer.palette?.params)
+ );
+ }
}
}
if (isReferenceLayer(layer) && layer.accessors && layer.accessors.length) {
@@ -1006,6 +1040,20 @@ function getVisualizationInfo(state: XYState) {
defaultMessage: 'Reference lines',
});
icon = IconChartBarReferenceLine;
+ if (frame?.datasourceLayers && frame.activeData) {
+ const sortedAccessors: string[] = getSortedAccessors(
+ frame.datasourceLayers[layer.layerId],
+ layer
+ );
+ palette.push(
+ ...getReferenceConfiguration({
+ state,
+ frame: frame as Pick,
+ layer,
+ sortedAccessors,
+ }).groups.flatMap(({ accessors }) => accessors.map(({ color }) => color))
+ );
+ }
}
if (isAnnotationsLayer(layer) && layer.annotations && layer.annotations.length) {
layer.annotations.forEach((annotation) => {
@@ -1021,8 +1069,15 @@ function getVisualizationInfo(state: XYState) {
defaultMessage: 'Annotations',
});
icon = IconChartBarAnnotations;
+ palette.push(
+ ...layer.annotations
+ .filter(({ isHidden }) => !isHidden)
+ .map((annotation) => getSingleColorAnnotationConfig(annotation).color)
+ );
}
+ const finalPalette = palette?.filter(nonNullable);
+
return {
layerId: layer.layerId,
layerType: layer.layerType,
@@ -1030,6 +1085,7 @@ function getVisualizationInfo(state: XYState) {
icon,
label,
dimensions,
+ palette: finalPalette.length ? finalPalette : undefined,
};
});
return {
@@ -1039,7 +1095,9 @@ function getVisualizationInfo(state: XYState) {
function getNotifiableFeatures(
state: XYState,
- dataViews: FramePublicAPI['dataViews']
+ frame: Pick & Partial,
+ paletteService: PaletteRegistry,
+ fieldFormats: FieldFormatsStart
): UserMessage[] {
const annotationsWithIgnoreFlag = getAnnotationsLayers(state.layers).filter(
(layer) => layer.ignoreGlobalFilters
@@ -1047,7 +1105,7 @@ function getNotifiableFeatures(
if (!annotationsWithIgnoreFlag.length) {
return [];
}
- const visualizationInfo = getVisualizationInfo(state);
+ const visualizationInfo = getVisualizationInfo(state, frame, paletteService, fieldFormats);
return [
{
@@ -1061,7 +1119,7 @@ function getNotifiableFeatures(
),
displayLocations: [{ id: 'embeddableBadge' }],