diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts index 053b46e480c7b2..4f2736d739b11f 100644 --- a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts +++ b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts @@ -83,6 +83,10 @@ export const pie: ExpressionFunctionDefinition< types: ['boolean'], help: '', }, + showValuesInLegend: { + types: ['boolean'], + help: '', + }, legendPosition: { types: ['string'], options: [Position.Top, Position.Right, Position.Bottom, Position.Left], diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/types.ts b/x-pack/plugins/lens/common/expressions/pie_chart/types.ts index 8c9ec4e5a54e77..a7aa92369dce24 100644 --- a/x-pack/plugins/lens/common/expressions/pie_chart/types.ts +++ b/x-pack/plugins/lens/common/expressions/pie_chart/types.ts @@ -17,6 +17,7 @@ export interface SharedPieLayerState { categoryDisplay: 'default' | 'inside' | 'hide'; legendDisplay: 'default' | 'show' | 'hide'; legendPosition?: 'left' | 'right' | 'top' | 'bottom'; + showValuesInLegend?: boolean; nestedLegend?: boolean; percentDecimals?: number; legendMaxLines?: number; diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index 3b9fdaf094822e..559a3cfc481648 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -86,6 +86,7 @@ export function PieComponent( truncateLegend, hideLabels, palette, + showValuesInLegend, } = props.args; const chartTheme = chartsThemeService.useChartsTheme(); const chartBaseTheme = chartsThemeService.useChartsBaseTheme(); @@ -315,7 +316,7 @@ export function PieComponent( (legend.getShowLegendDefault?.(bucketColumns) ?? false))) } flatLegend={legend.flat} - showLegendExtra={legend.showValues} + showLegendExtra={showValuesInLegend} legendPosition={legendPosition || Position.Right} legendMaxDepth={nestedLegend ? undefined : 1 /* Color is based only on first layer */} onElementClick={props.interactive ?? true ? onElementClickHandler : undefined} diff --git a/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts b/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts index d86500ff8a4fad..bcd9d79babbab2 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts +++ b/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts @@ -14,8 +14,10 @@ import { byDataColorPaletteMap, extractUniqTermsMap, checkTableForContainsSmallValues, + shouldShowValuesInLegend, } from './render_helpers'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; +import type { PieLayerState } from '../../common/expressions'; describe('render helpers', () => { describe('#getSliceValue', () => { @@ -374,4 +376,28 @@ describe('render helpers', () => { expect(checkTableForContainsSmallValues(datatable, columnId, 1)).toBeFalsy(); }); }); + + describe('#shouldShowValuesInLegend', () => { + it('should firstly read the state value', () => { + expect( + shouldShowValuesInLegend({ showValuesInLegend: true } as PieLayerState, 'waffle') + ).toBeTruthy(); + + expect( + shouldShowValuesInLegend({ showValuesInLegend: false } as PieLayerState, 'waffle') + ).toBeFalsy(); + }); + + it('should read value from meta in case of value in state is undefined', () => { + expect( + shouldShowValuesInLegend({ showValuesInLegend: undefined } as PieLayerState, 'waffle') + ).toBeTruthy(); + + expect(shouldShowValuesInLegend({} as PieLayerState, 'waffle')).toBeTruthy(); + + expect( + shouldShowValuesInLegend({ showValuesInLegend: undefined } as PieLayerState, 'pie') + ).toBeFalsy(); + }); + }); }); diff --git a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts index fa20eb6f20fa8d..a9685e13e17741 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts +++ b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts @@ -8,8 +8,9 @@ import type { Datum, LayerValue } from '@elastic/charts'; import type { Datatable, DatatableColumn } from 'src/plugins/expressions/public'; import type { LensFilterEvent } from '../types'; -import type { PieChartTypes } from '../../common/expressions/pie_chart/types'; +import type { PieChartTypes, PieLayerState } from '../../common/expressions/pie_chart/types'; import type { PaletteDefinition, PaletteOutput } from '../../../../../src/plugins/charts/public'; +import { PartitionChartsMeta } from './partition_charts_meta'; export function getSliceValue(d: Datum, metricColumn: DatatableColumn) { const value = d[metricColumn.id]; @@ -44,6 +45,14 @@ export const isPartitionShape = (shape: PieChartTypes | string) => export const isTreemapOrMosaicShape = (shape: PieChartTypes | string) => ['treemap', 'mosaic'].includes(shape); +export const shouldShowValuesInLegend = (layer: PieLayerState, shape: PieChartTypes) => { + if ('showValues' in PartitionChartsMeta[shape]?.legend) { + return layer.showValuesInLegend ?? PartitionChartsMeta[shape]?.legend?.showValues ?? true; + } + + return false; +}; + export const extractUniqTermsMap = (dataTable: Datatable, columnId: string) => [...new Set(dataTable.rows.map((item) => item[columnId]))].reduce( (acc, item, index) => ({ diff --git a/x-pack/plugins/lens/public/pie_visualization/to_expression.ts b/x-pack/plugins/lens/public/pie_visualization/to_expression.ts index e13fbf62708eeb..57270337e67a4d 100644 --- a/x-pack/plugins/lens/public/pie_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/pie_visualization/to_expression.ts @@ -5,10 +5,12 @@ * 2.0. */ -import { Ast } from '@kbn/interpreter/common'; -import { PaletteRegistry } from 'src/plugins/charts/public'; -import { Operation, DatasourcePublicAPI } from '../types'; +import type { Ast } from '@kbn/interpreter/common'; +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import type { Operation, DatasourcePublicAPI } from '../types'; import { DEFAULT_PERCENT_DECIMALS } from './constants'; +import { shouldShowValuesInLegend } from './render_helpers'; + import type { PieVisualizationState } from '../../common/expressions'; export function toExpression( @@ -34,6 +36,7 @@ function expressionHelper( const operations = layer.groups .map((columnId) => ({ columnId, operation: datasource.getOperationForColumnId(columnId) })) .filter((o): o is { columnId: string; operation: Operation } => !!o.operation); + if (!layer.metric || !operations.length) { return null; } @@ -55,6 +58,7 @@ function expressionHelper( categoryDisplay: [layer.categoryDisplay], legendDisplay: [layer.legendDisplay], legendPosition: [layer.legendPosition || 'right'], + showValuesInLegend: [shouldShowValuesInLegend(layer, state.shape)], percentDecimals: [ state.shape === 'waffle' ? DEFAULT_PERCENT_DECIMALS diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index 195a72cca9fedd..70ad4d8c07daa5 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -6,7 +6,7 @@ */ import './toolbar.scss'; -import React from 'react'; +import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, @@ -23,6 +23,7 @@ import type { PieVisualizationState, SharedPieLayerState } from '../../common/ex import { VisualizationDimensionEditorProps, VisualizationToolbarProps } from '../types'; import { ToolbarPopover, LegendSettingsPopover, useDebouncedValue } from '../shared_components'; import { PalettePicker } from '../shared_components'; +import { shouldShowValuesInLegend } from './render_helpers'; const legendOptions: Array<{ value: SharedPieLayerState['legendDisplay']; @@ -55,6 +56,67 @@ const legendOptions: Array<{ export function PieToolbar(props: VisualizationToolbarProps) { const { state, setState } = props; const layer = state.layers[0]; + + const onStateChange = useCallback( + (part: Record) => { + setState({ + ...state, + layers: [{ ...layer, ...part }], + }); + }, + [layer, state, setState] + ); + + const onCategoryDisplayChange = useCallback( + (option) => onStateChange({ categoryDisplay: option }), + [onStateChange] + ); + + const onNumberDisplayChange = useCallback( + (option) => onStateChange({ numberDisplay: option }), + [onStateChange] + ); + + const onPercentDecimalsChange = useCallback( + (option) => { + onStateChange({ percentDecimals: option }); + }, + [onStateChange] + ); + + const onLegendDisplayChange = useCallback( + (optionId) => { + onStateChange({ legendDisplay: legendOptions.find(({ id }) => id === optionId)!.value }); + }, + [onStateChange] + ); + + const onLegendPositionChange = useCallback( + (id) => onStateChange({ legendPosition: id as Position }), + [onStateChange] + ); + + const onNestedLegendChange = useCallback( + (id) => onStateChange({ nestedLegend: !layer.nestedLegend }), + [layer, onStateChange] + ); + + const onTruncateLegendChange = useCallback(() => { + const current = layer.truncateLegend ?? true; + onStateChange({ truncateLegend: !current }); + }, [layer, onStateChange]); + + const onLegendMaxLinesChange = useCallback( + (val) => onStateChange({ legendMaxLines: val }), + [onStateChange] + ); + + const onValueInLegendChange = useCallback(() => { + onStateChange({ + showValuesInLegend: !shouldShowValuesInLegend(layer, state.shape), + }); + }, [layer, state.shape, onStateChange]); + if (!layer) { return null; } @@ -87,12 +149,7 @@ export function PieToolbar(props: VisualizationToolbarProps { - setState({ - ...state, - layers: [{ ...layer, categoryDisplay: option }], - }); - }} + onChange={onCategoryDisplayChange} /> ) : null} @@ -110,12 +167,7 @@ export function PieToolbar(props: VisualizationToolbarProps { - setState({ - ...state, - layers: [{ ...layer, numberDisplay: option }], - }); - }} + onChange={onNumberDisplayChange} /> ) : null} @@ -131,59 +183,28 @@ export function PieToolbar(props: VisualizationToolbarProps { - setState({ - ...state, - layers: [{ ...layer, percentDecimals: value }], - }); - }} + setValue={onPercentDecimalsChange} /> { - setState({ - ...state, - layers: [ - { - ...layer, - legendDisplay: legendOptions.find(({ id }) => id === optionId)!.value, - }, - ], - }); - }} + onDisplayChange={onLegendDisplayChange} + valueInLegend={shouldShowValuesInLegend(layer, state.shape)} + renderValueInLegendSwitch={ + 'showValues' in PartitionChartsMeta[state.shape]?.legend ?? false + } + onValueInLegendChange={onValueInLegendChange} position={layer.legendPosition} - onPositionChange={(id) => { - setState({ - ...state, - layers: [{ ...layer, legendPosition: id as Position }], - }); - }} + onPositionChange={onLegendPositionChange} renderNestedLegendSwitch nestedLegend={!!layer.nestedLegend} - onNestedLegendChange={() => { - setState({ - ...state, - layers: [{ ...layer, nestedLegend: !layer.nestedLegend }], - }); - }} + onNestedLegendChange={onNestedLegendChange} shouldTruncate={layer.truncateLegend ?? true} - onTruncateLegendChange={() => { - const current = layer.truncateLegend ?? true; - setState({ - ...state, - layers: [{ ...layer, truncateLegend: !current }], - }); - }} + onTruncateLegendChange={onTruncateLegendChange} maxLines={layer?.legendMaxLines} - onMaxLinesChange={(val) => { - setState({ - ...state, - layers: [{ ...layer, legendMaxLines: val }], - }); - }} + onMaxLinesChange={onLegendMaxLinesChange} /> );