Skip to content

Commit 9af652e

Browse files
[7.x] [Lens] New value labels config option for bar charts (#81776) (#82849)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent e411757 commit 9af652e

19 files changed

Lines changed: 502 additions & 57 deletions

x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { EuiIconLegend } from '../assets/legend';
1111

1212
const typeToIconMap: { [type: string]: string | IconType } = {
1313
legend: EuiIconLegend as IconType,
14-
values: 'visText',
14+
values: 'number',
1515
};
1616

1717
export interface ToolbarPopoverProps {

x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/lens/public/xy_visualization/expression.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
256256
isVisible: false,
257257
position: Position.Top,
258258
},
259+
valueLabels: 'hide',
259260
axisTitlesVisibilitySettings: {
260261
type: 'lens_xy_axisTitlesVisibilityConfig',
261262
x: true,
@@ -1867,6 +1868,7 @@ describe('xy_expression', () => {
18671868
yTitle: '',
18681869
yRightTitle: '',
18691870
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
1871+
valueLabels: 'hide',
18701872
tickLabelsVisibilitySettings: {
18711873
type: 'lens_xy_tickLabelsConfig',
18721874
x: true,
@@ -1952,6 +1954,7 @@ describe('xy_expression', () => {
19521954
yTitle: '',
19531955
yRightTitle: '',
19541956
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
1957+
valueLabels: 'hide',
19551958
tickLabelsVisibilitySettings: {
19561959
type: 'lens_xy_tickLabelsConfig',
19571960
x: true,
@@ -2023,6 +2026,7 @@ describe('xy_expression', () => {
20232026
yTitle: '',
20242027
yRightTitle: '',
20252028
legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top },
2029+
valueLabels: 'hide',
20262030
tickLabelsVisibilitySettings: {
20272031
type: 'lens_xy_tickLabelsConfig',
20282032
x: true,

x-pack/plugins/lens/public/xy_visualization/expression.tsx

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import {
2020
GeometryValue,
2121
XYChartSeriesIdentifier,
2222
StackMode,
23+
RecursivePartial,
24+
Theme,
25+
VerticalAlignment,
26+
HorizontalAlignment,
2327
} from '@elastic/charts';
2428
import { I18nProvider } from '@kbn/i18n/react';
2529
import {
@@ -131,6 +135,11 @@ export const xyChart: ExpressionFunctionDefinition<
131135
defaultMessage: 'Define how missing values are treated',
132136
}),
133137
},
138+
valueLabels: {
139+
types: ['string'],
140+
options: ['hide', 'inside'],
141+
help: '',
142+
},
134143
tickLabelsVisibilitySettings: {
135144
types: ['lens_xy_tickLabelsConfig'],
136145
help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', {
@@ -214,6 +223,40 @@ export const getXyChartRenderer = (dependencies: {
214223
},
215224
});
216225

226+
function mergeThemeWithValueLabelsStyling(
227+
theme: RecursivePartial<Theme>,
228+
valuesLabelMode: string = 'hide',
229+
isHorizontal: boolean
230+
) {
231+
const VALUE_LABELS_MAX_FONTSIZE = 15;
232+
const VALUE_LABELS_MIN_FONTSIZE = 10;
233+
const VALUE_LABELS_VERTICAL_OFFSET = -10;
234+
const VALUE_LABELS_HORIZONTAL_OFFSET = 10;
235+
236+
if (valuesLabelMode === 'hide') {
237+
return theme;
238+
}
239+
return {
240+
...theme,
241+
...{
242+
barSeriesStyle: {
243+
...theme.barSeriesStyle,
244+
displayValue: {
245+
fontSize: { min: VALUE_LABELS_MIN_FONTSIZE, max: VALUE_LABELS_MAX_FONTSIZE },
246+
fill: { textInverted: true, textBorder: 2 },
247+
alignment: isHorizontal
248+
? {
249+
vertical: VerticalAlignment.Middle,
250+
}
251+
: { horizontal: HorizontalAlignment.Center },
252+
offsetX: isHorizontal ? VALUE_LABELS_HORIZONTAL_OFFSET : 0,
253+
offsetY: isHorizontal ? 0 : VALUE_LABELS_VERTICAL_OFFSET,
254+
},
255+
},
256+
},
257+
};
258+
}
259+
217260
function getIconForSeriesType(seriesType: SeriesType): IconType {
218261
return visualizationTypes.find((c) => c.id === seriesType)!.icon || 'empty';
219262
}
@@ -254,7 +297,7 @@ export function XYChart({
254297
onClickValue,
255298
onSelectRange,
256299
}: XYChartRenderProps) {
257-
const { legend, layers, fittingFunction, gridlinesVisibilitySettings } = args;
300+
const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args;
258301
const chartTheme = chartsThemeService.useChartsTheme();
259302
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
260303

@@ -396,6 +439,16 @@ export function XYChart({
396439
return style;
397440
};
398441

442+
const shouldShowValueLabels =
443+
// No stacked bar charts
444+
filteredLayers.every((layer) => !layer.seriesType.includes('stacked')) &&
445+
// No histogram charts
446+
!isHistogramViz;
447+
448+
const baseThemeWithMaybeValueLabels = !shouldShowValueLabels
449+
? chartTheme
450+
: mergeThemeWithValueLabelsStyling(chartTheme, valueLabels, shouldRotate);
451+
399452
const colorAssignments = getColorAssignments(args.layers, data, formatFactory);
400453

401454
return (
@@ -408,7 +461,7 @@ export function XYChart({
408461
}
409462
legendPosition={legend.position}
410463
showLegendExtra={false}
411-
theme={chartTheme}
464+
theme={baseThemeWithMaybeValueLabels}
412465
baseTheme={chartBaseTheme}
413466
tooltip={{
414467
headerFormatter: (d) => safeXAccessorLabelRenderer(d.value),
@@ -613,6 +666,10 @@ export function XYChart({
613666
});
614667
}
615668

669+
const yAxis = yAxesConfiguration.find((axisConfiguration) =>
670+
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
671+
);
672+
616673
const seriesProps: SeriesSpec = {
617674
splitSeriesAccessors: splitAccessor ? [splitAccessor] : [],
618675
stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [],
@@ -649,9 +706,7 @@ export function XYChart({
649706
palette.params
650707
);
651708
},
652-
groupId: yAxesConfiguration.find((axisConfiguration) =>
653-
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
654-
)?.groupId,
709+
groupId: yAxis?.groupId,
655710
enableHistogramMode:
656711
isHistogram &&
657712
(seriesType.includes('stacked') || !splitAccessor) &&
@@ -723,7 +778,19 @@ export function XYChart({
723778
case 'bar_horizontal':
724779
case 'bar_horizontal_stacked':
725780
case 'bar_horizontal_percentage_stacked':
726-
return <BarSeries key={index} {...seriesProps} />;
781+
const valueLabelsSettings = {
782+
displayValueSettings: {
783+
// This format double fixes two issues in elastic-chart
784+
// * when rotating the chart, the formatter is not correctly picked
785+
// * in some scenarios value labels are not strings, and this breaks the elastic-chart lib
786+
valueFormatter: (d: unknown) => yAxis?.formatter?.convert(d) || '',
787+
showValueLabel: shouldShowValueLabels && valueLabels !== 'hide',
788+
isAlternatingValueLabel: false,
789+
isValueContainedInElement: true,
790+
hideClippedValue: true,
791+
},
792+
};
793+
return <BarSeries key={index} {...seriesProps} {...valueLabelsSettings} />;
727794
case 'area_stacked':
728795
case 'area_percentage_stacked':
729796
return (

x-pack/plugins/lens/public/xy_visualization/state_helpers.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*/
66

77
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
8-
import { SeriesType, visualizationTypes, LayerConfig, YConfig } from './types';
8+
import { FramePublicAPI } from '../types';
9+
import { SeriesType, visualizationTypes, LayerConfig, YConfig, ValidLayer } from './types';
910

1011
export function isHorizontalSeries(seriesType: SeriesType) {
1112
return (
@@ -37,3 +38,23 @@ export const getSeriesColor = (layer: LayerConfig, accessor: string) => {
3738
layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null
3839
);
3940
};
41+
42+
export function hasHistogramSeries(
43+
layers: ValidLayer[] = [],
44+
datasourceLayers?: FramePublicAPI['datasourceLayers']
45+
) {
46+
if (!datasourceLayers) {
47+
return false;
48+
}
49+
const validLayers = layers.filter(({ accessors }) => accessors.length);
50+
51+
return validLayers.some(({ layerId, xAccessor }: ValidLayer) => {
52+
const xAxisOperation = datasourceLayers[layerId].getOperationForColumnId(xAccessor);
53+
return (
54+
xAxisOperation &&
55+
xAxisOperation.isBucketed &&
56+
xAxisOperation.scale &&
57+
xAxisOperation.scale !== 'ordinal'
58+
);
59+
});
60+
}

0 commit comments

Comments
 (0)