[Lens] New value labels config option for bar charts (#81776)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Marco Liberati 2020-11-06 16:34:30 +01:00 committed by GitHub
parent 03ee1a6476
commit d3d3fa7bd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 502 additions and 57 deletions

View file

@ -11,7 +11,7 @@ import { EuiIconLegend } from '../assets/legend';
const typeToIconMap: { [type: string]: string | IconType } = {
legend: EuiIconLegend as IconType,
values: 'visText',
values: 'number',
};
export interface ToolbarPopoverProps {

View file

@ -279,6 +279,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
},
]
}
displayValueSettings={
Object {
"hideClippedValue": true,
"isAlternatingValueLabel": false,
"isValueContainedInElement": true,
"showValueLabel": false,
"valueFormatter": [Function],
}
}
enableHistogramMode={false}
groupId="left"
id="d-a"
@ -334,6 +343,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
},
]
}
displayValueSettings={
Object {
"hideClippedValue": true,
"isAlternatingValueLabel": false,
"isValueContainedInElement": true,
"showValueLabel": false,
"valueFormatter": [Function],
}
}
enableHistogramMode={false}
groupId="left"
id="d-b"
@ -457,6 +475,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
},
]
}
displayValueSettings={
Object {
"hideClippedValue": true,
"isAlternatingValueLabel": false,
"isValueContainedInElement": true,
"showValueLabel": false,
"valueFormatter": [Function],
}
}
enableHistogramMode={false}
groupId="left"
id="d-a"
@ -512,6 +539,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
},
]
}
displayValueSettings={
Object {
"hideClippedValue": true,
"isAlternatingValueLabel": false,
"isValueContainedInElement": true,
"showValueLabel": false,
"valueFormatter": [Function],
}
}
enableHistogramMode={false}
groupId="left"
id="d-b"
@ -1019,6 +1055,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
},
]
}
displayValueSettings={
Object {
"hideClippedValue": true,
"isAlternatingValueLabel": false,
"isValueContainedInElement": true,
"showValueLabel": false,
"valueFormatter": [Function],
}
}
enableHistogramMode={false}
groupId="left"
id="d-a"
@ -1078,6 +1123,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
},
]
}
displayValueSettings={
Object {
"hideClippedValue": true,
"isAlternatingValueLabel": false,
"isValueContainedInElement": true,
"showValueLabel": false,
"valueFormatter": [Function],
}
}
enableHistogramMode={false}
groupId="left"
id="d-b"
@ -1205,6 +1259,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
},
]
}
displayValueSettings={
Object {
"hideClippedValue": true,
"isAlternatingValueLabel": false,
"isValueContainedInElement": true,
"showValueLabel": false,
"valueFormatter": [Function],
}
}
enableHistogramMode={false}
groupId="left"
id="d-a"
@ -1264,6 +1327,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
},
]
}
displayValueSettings={
Object {
"hideClippedValue": true,
"isAlternatingValueLabel": false,
"isValueContainedInElement": true,
"showValueLabel": false,
"valueFormatter": [Function],
}
}
enableHistogramMode={false}
groupId="left"
id="d-b"

View file

@ -145,6 +145,9 @@ Object {
"title": Array [
"",
],
"valueLabels": Array [
"hide",
],
"xTitle": Array [
"",
],

View file

@ -256,6 +256,7 @@ const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
isVisible: false,
position: Position.Top,
},
valueLabels: 'hide',
axisTitlesVisibilitySettings: {
type: 'lens_xy_axisTitlesVisibilityConfig',
x: true,
@ -1867,6 +1868,7 @@ describe('xy_expression', () => {
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
@ -1952,6 +1954,7 @@ describe('xy_expression', () => {
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
@ -2023,6 +2026,7 @@ describe('xy_expression', () => {
yTitle: '',
yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top },
valueLabels: 'hide',
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,

View file

@ -20,6 +20,10 @@ import {
GeometryValue,
XYChartSeriesIdentifier,
StackMode,
RecursivePartial,
Theme,
VerticalAlignment,
HorizontalAlignment,
} from '@elastic/charts';
import { I18nProvider } from '@kbn/i18n/react';
import {
@ -131,6 +135,11 @@ export const xyChart: ExpressionFunctionDefinition<
defaultMessage: 'Define how missing values are treated',
}),
},
valueLabels: {
types: ['string'],
options: ['hide', 'inside'],
help: '',
},
tickLabelsVisibilitySettings: {
types: ['lens_xy_tickLabelsConfig'],
help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', {
@ -214,6 +223,40 @@ export const getXyChartRenderer = (dependencies: {
},
});
function mergeThemeWithValueLabelsStyling(
theme: RecursivePartial<Theme>,
valuesLabelMode: string = 'hide',
isHorizontal: boolean
) {
const VALUE_LABELS_MAX_FONTSIZE = 15;
const VALUE_LABELS_MIN_FONTSIZE = 10;
const VALUE_LABELS_VERTICAL_OFFSET = -10;
const VALUE_LABELS_HORIZONTAL_OFFSET = 10;
if (valuesLabelMode === 'hide') {
return theme;
}
return {
...theme,
...{
barSeriesStyle: {
...theme.barSeriesStyle,
displayValue: {
fontSize: { min: VALUE_LABELS_MIN_FONTSIZE, max: VALUE_LABELS_MAX_FONTSIZE },
fill: { textInverted: true, textBorder: 2 },
alignment: isHorizontal
? {
vertical: VerticalAlignment.Middle,
}
: { horizontal: HorizontalAlignment.Center },
offsetX: isHorizontal ? VALUE_LABELS_HORIZONTAL_OFFSET : 0,
offsetY: isHorizontal ? 0 : VALUE_LABELS_VERTICAL_OFFSET,
},
},
},
};
}
function getIconForSeriesType(seriesType: SeriesType): IconType {
return visualizationTypes.find((c) => c.id === seriesType)!.icon || 'empty';
}
@ -254,7 +297,7 @@ export function XYChart({
onClickValue,
onSelectRange,
}: XYChartRenderProps) {
const { legend, layers, fittingFunction, gridlinesVisibilitySettings } = args;
const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args;
const chartTheme = chartsThemeService.useChartsTheme();
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
@ -396,6 +439,16 @@ export function XYChart({
return style;
};
const shouldShowValueLabels =
// No stacked bar charts
filteredLayers.every((layer) => !layer.seriesType.includes('stacked')) &&
// No histogram charts
!isHistogramViz;
const baseThemeWithMaybeValueLabels = !shouldShowValueLabels
? chartTheme
: mergeThemeWithValueLabelsStyling(chartTheme, valueLabels, shouldRotate);
const colorAssignments = getColorAssignments(args.layers, data, formatFactory);
return (
@ -408,7 +461,7 @@ export function XYChart({
}
legendPosition={legend.position}
showLegendExtra={false}
theme={chartTheme}
theme={baseThemeWithMaybeValueLabels}
baseTheme={chartBaseTheme}
tooltip={{
headerFormatter: (d) => safeXAccessorLabelRenderer(d.value),
@ -613,6 +666,10 @@ export function XYChart({
});
}
const yAxis = yAxesConfiguration.find((axisConfiguration) =>
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
);
const seriesProps: SeriesSpec = {
splitSeriesAccessors: splitAccessor ? [splitAccessor] : [],
stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [],
@ -649,9 +706,7 @@ export function XYChart({
palette.params
);
},
groupId: yAxesConfiguration.find((axisConfiguration) =>
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
)?.groupId,
groupId: yAxis?.groupId,
enableHistogramMode:
isHistogram &&
(seriesType.includes('stacked') || !splitAccessor) &&
@ -723,7 +778,19 @@ export function XYChart({
case 'bar_horizontal':
case 'bar_horizontal_stacked':
case 'bar_horizontal_percentage_stacked':
return <BarSeries key={index} {...seriesProps} />;
const valueLabelsSettings = {
displayValueSettings: {
// This format double fixes two issues in elastic-chart
// * when rotating the chart, the formatter is not correctly picked
// * in some scenarios value labels are not strings, and this breaks the elastic-chart lib
valueFormatter: (d: unknown) => yAxis?.formatter?.convert(d) || '',
showValueLabel: shouldShowValueLabels && valueLabels !== 'hide',
isAlternatingValueLabel: false,
isValueContainedInElement: true,
hideClippedValue: true,
},
};
return <BarSeries key={index} {...seriesProps} {...valueLabelsSettings} />;
case 'area_stacked':
case 'area_percentage_stacked':
return (

View file

@ -5,7 +5,8 @@
*/
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import { SeriesType, visualizationTypes, LayerConfig, YConfig } from './types';
import { FramePublicAPI } from '../types';
import { SeriesType, visualizationTypes, LayerConfig, YConfig, ValidLayer } from './types';
export function isHorizontalSeries(seriesType: SeriesType) {
return (
@ -37,3 +38,23 @@ export const getSeriesColor = (layer: LayerConfig, accessor: string) => {
layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null
);
};
export function hasHistogramSeries(
layers: ValidLayer[] = [],
datasourceLayers?: FramePublicAPI['datasourceLayers']
) {
if (!datasourceLayers) {
return false;
}
const validLayers = layers.filter(({ accessors }) => accessors.length);
return validLayers.some(({ layerId, xAccessor }: ValidLayer) => {
const xAxisOperation = datasourceLayers[layerId].getOperationForColumnId(xAccessor);
return (
xAxisOperation &&
xAxisOperation.isBucketed &&
xAxisOperation.scale &&
xAxisOperation.scale !== 'ordinal'
);
});
}

View file

@ -43,6 +43,7 @@ describe('#toExpression', () => {
xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'Carry',
tickLabelsVisibilitySettings: { x: false, yLeft: true, yRight: true },
@ -67,6 +68,7 @@ describe('#toExpression', () => {
(xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -87,6 +89,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -113,6 +116,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -136,6 +140,7 @@ describe('#toExpression', () => {
xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -156,6 +161,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -191,6 +197,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -217,6 +224,7 @@ describe('#toExpression', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -238,4 +246,25 @@ describe('#toExpression', () => {
yRight: [true],
});
});
it('should correctly report the valueLabels visibility settings', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'inside',
preferredSeriesType: 'bar',
layers: [
{
layerId: 'first',
seriesType: 'area',
splitAccessor: 'd',
xAccessor: 'a',
accessors: ['b', 'c'],
},
],
},
frame.datasourceLayers
) as Ast;
expect(expression.chain[0].arguments.valueLabels[0] as Ast).toEqual('inside');
});
});

View file

@ -7,13 +7,9 @@
import { Ast } from '@kbn/interpreter/common';
import { ScaleType } from '@elastic/charts';
import { PaletteRegistry } from 'src/plugins/charts/public';
import { State, LayerConfig } from './types';
import { State, ValidLayer, LayerConfig } from './types';
import { OperationMetadata, DatasourcePublicAPI } from '../types';
interface ValidLayer extends LayerConfig {
xAccessor: NonNullable<LayerConfig['xAccessor']>;
}
export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: LayerConfig) => {
const originalOrder = datasource
.getTableSpec()
@ -60,6 +56,7 @@ export function toPreviewExpression(
...state.legend,
isVisible: false,
},
valueLabels: 'hide',
},
datasourceLayers,
paletteService,
@ -197,6 +194,7 @@ export const buildExpression = (
],
},
],
valueLabels: [state?.valueLabels || 'hide'],
layers: validLayers.map((layer) => {
const columnToLabel: Record<string, string> = {};

View file

@ -364,6 +364,8 @@ export type SeriesType =
export type YAxisMode = 'auto' | 'left' | 'right';
export type ValueLabelConfig = 'hide' | 'inside' | 'outside';
export interface YConfig {
forAccessor: string;
axisMode?: YAxisMode;
@ -381,6 +383,10 @@ export interface LayerConfig {
palette?: PaletteOutput;
}
export interface ValidLayer extends LayerConfig {
xAccessor: NonNullable<LayerConfig['xAccessor']>;
}
export type LayerArgs = LayerConfig & {
columnToLabel?: string; // Actually a JSON key-value pair
yScaleType: 'time' | 'linear' | 'log' | 'sqrt';
@ -398,6 +404,7 @@ export interface XYArgs {
yTitle: string;
yRightTitle: string;
legend: LegendConfig & { type: 'lens_xy_legendConfig' };
valueLabels: ValueLabelConfig;
layers: LayerArgs[];
fittingFunction?: FittingFunction;
axisTitlesVisibilitySettings?: AxesSettingsConfig & {
@ -411,6 +418,7 @@ export interface XYArgs {
export interface XYState {
preferredSeriesType: SeriesType;
legend: LegendConfig;
valueLabels?: ValueLabelConfig;
fittingFunction?: FittingFunction;
layers: LayerConfig[];
xTitle?: string;

View file

@ -15,6 +15,7 @@ import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'
function exampleState(): State {
return {
legend: { position: Position.Bottom, isVisible: true },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -150,6 +151,7 @@ describe('xy_visualization', () => {
},
"preferredSeriesType": "bar_stacked",
"title": "Empty XY chart",
"valueLabels": "hide",
}
`);
});

View file

@ -145,6 +145,7 @@ export const getXyVisualization = ({
state || {
title: 'Empty XY chart',
legend: { isVisible: true, position: Position.Right },
valueLabels: 'hide',
preferredSeriesType: defaultSeriesType,
layers: [
{

View file

@ -1,3 +1,3 @@
.lnsXyToolbar__popover {
width: 320px;
}
}

View file

@ -21,6 +21,7 @@ describe('XY Config panels', () => {
function testState(): State {
return {
legend: { isVisible: true, position: Position.Right },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -115,8 +116,9 @@ describe('XY Config panels', () => {
expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry');
});
it('should disable the popover if there is no area or line series', () => {
it('should show currently selected value labels display setting', () => {
const state = testState();
const component = shallow(
<XyToolbar
frame={frame}
@ -125,11 +127,148 @@ describe('XY Config panels', () => {
...state,
layers: [{ ...state.layers[0], seriesType: 'bar' }],
fittingFunction: 'Carry',
valueLabels: 'inside',
}}
/>
);
expect(component.find(ToolbarPopover).at(0).prop('isDisabled')).toEqual(true);
expect(component.find(EuiButtonGroup).prop('idSelected')).toEqual('value_labels_inside');
});
it('should disable the popover for stacked bar charts', () => {
const state = testState();
const component = shallow(
<XyToolbar
frame={frame}
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_stacked' }],
}}
/>
);
expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
});
it('should disable the popover for percentage area charts', () => {
const state = testState();
const component = shallow(
<XyToolbar
frame={frame}
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'area_percentage_stacked' }],
}}
/>
);
expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
});
it('should disabled the popover if there is histogram series', () => {
// make it detect an histogram series
frame.datasourceLayers.first.getOperationForColumnId = jest.fn().mockReturnValueOnce({
isBucketed: true,
scale: 'interval',
});
const state = testState();
const component = shallow(
<XyToolbar
frame={frame}
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0] }],
}}
/>
);
expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
});
it('should show the popover and display field enabled for bar and horizontal_bar series', () => {
const state = testState();
const component = shallow(
<XyToolbar
frame={frame}
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }],
fittingFunction: 'Carry',
}}
/>
);
expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true);
});
it('should hide the fitting option for bar series', () => {
const state = testState();
const component = shallow(
<XyToolbar
frame={frame}
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }],
fittingFunction: 'Carry',
}}
/>
);
expect(component.exists('[data-test-subj="lnsMissingValuesSelect"]')).toEqual(false);
});
it('should hide in the popover the display option for area and line series', () => {
const state = testState();
const component = shallow(
<XyToolbar
frame={frame}
setState={jest.fn()}
state={{
...state,
layers: [{ ...state.layers[0], seriesType: 'area' }],
fittingFunction: 'Carry',
}}
/>
);
expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(false);
});
it('should keep the display option for bar series with multiple layers', () => {
frame.datasourceLayers = {
...frame.datasourceLayers,
second: createMockDatasource('test').publicAPIMock,
};
const state = testState();
const component = shallow(
<XyToolbar
frame={frame}
setState={jest.fn()}
state={{
...state,
layers: [
{ ...state.layers[0], seriesType: 'bar' },
{
seriesType: 'bar',
layerId: 'second',
splitAccessor: 'baz',
xAccessor: 'foo',
accessors: ['bar'],
},
],
fittingFunction: 'Carry',
}}
/>
);
expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true);
});
it('should disable the popover if there is no right axis', () => {

View file

@ -27,8 +27,20 @@ import {
VisualizationToolbarProps,
VisualizationDimensionEditorProps,
} from '../types';
import { State, SeriesType, visualizationTypes, YAxisMode, AxesSettingsConfig } from './types';
import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers';
import {
State,
SeriesType,
visualizationTypes,
YAxisMode,
AxesSettingsConfig,
ValidLayer,
} from './types';
import {
isHorizontalChart,
isHorizontalSeries,
getSeriesColor,
hasHistogramSeries,
} from './state_helpers';
import { trackUiEvent } from '../lens_ui_telemetry';
import { fittingFunctionDefinitions } from './fitting_functions';
import { ToolbarPopover, LegendSettingsPopover } from '../shared_components';
@ -74,6 +86,27 @@ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label:
},
];
const valueLabelsOptions: Array<{
id: string;
value: 'hide' | 'inside' | 'outside';
label: string;
}> = [
{
id: `value_labels_hide`,
value: 'hide',
label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.auto', {
defaultMessage: 'Hide',
}),
},
{
id: `value_labels_inside`,
value: 'inside',
label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.inside', {
defaultMessage: 'Show',
}),
},
];
export function LayerContextMenu(props: VisualizationLayerWidgetProps<State>) {
const { state, layerId } = props;
const horizontalOnly = isHorizontalChart(state.layers);
@ -118,12 +151,24 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps<State>) {
}
export function XyToolbar(props: VisualizationToolbarProps<State>) {
const { state, setState } = props;
const { state, setState, frame } = props;
const hasNonBarSeries = state?.layers.some(({ seriesType }) =>
['area_stacked', 'area', 'line'].includes(seriesType)
);
const hasBarNotStacked = state?.layers.some(({ seriesType }) =>
['bar', 'bar_horizontal'].includes(seriesType)
);
const isAreaPercentage = state?.layers.some(
({ seriesType }) => seriesType === 'area_percentage_stacked'
);
const isHistogramSeries = Boolean(
hasHistogramSeries(state?.layers as ValidLayer[], frame.datasourceLayers)
);
const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false;
const axisGroups = getAxesConfiguration(state?.layers, shouldRotate);
@ -191,54 +236,99 @@ export function XyToolbar(props: VisualizationToolbarProps<State>) {
: !state?.legend.isVisible
? 'hide'
: 'show';
const valueLabelsVisibilityMode = state?.valueLabels || 'hide';
const isValueLabelsEnabled = !hasNonBarSeries && hasBarNotStacked && !isHistogramSeries;
const isFittingEnabled = hasNonBarSeries;
return (
<EuiFlexGroup gutterSize="m" justifyContent="spaceBetween">
<EuiFlexItem>
<EuiFlexGroup gutterSize="none" responsive={false}>
<TooltipWrapper
tooltipContent={i18n.translate('xpack.lens.xyChart.fittingDisabledHelpText', {
defaultMessage: 'This setting only applies to line and area charts.',
})}
condition={!hasNonBarSeries}
tooltipContent={
isAreaPercentage
? i18n.translate('xpack.lens.xyChart.valuesPercentageDisabledHelpText', {
defaultMessage: 'This setting cannot be changed on percentage area charts.',
})
: i18n.translate('xpack.lens.xyChart.valuesStackedDisabledHelpText', {
defaultMessage: 'This setting cannot be changed on histograms.',
})
}
condition={!isValueLabelsEnabled && !isFittingEnabled}
>
<ToolbarPopover
title={i18n.translate('xpack.lens.xyChart.valuesLabel', {
defaultMessage: 'Values',
})}
isDisabled={!hasNonBarSeries}
type="values"
groupPosition="left"
buttonDataTestSubj="lnsMissingValuesButton"
buttonDataTestSubj="lnsValuesButton"
isDisabled={!isValueLabelsEnabled && !isFittingEnabled}
>
<EuiFormRow
display="columnCompressed"
label={i18n.translate('xpack.lens.xyChart.missingValuesLabel', {
defaultMessage: 'Missing values',
})}
>
<EuiSuperSelect
data-test-subj="lnsMissingValuesSelect"
compressed
options={fittingFunctionDefinitions.map(({ id, title, description }) => {
return {
value: id,
dropdownDisplay: (
<>
<strong>{title}</strong>
<EuiText size="xs" color="subdued">
<p>{description}</p>
</EuiText>
</>
),
inputDisplay: title,
};
{isValueLabelsEnabled ? (
<EuiFormRow
display="columnCompressed"
label={
<span>
{i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', {
defaultMessage: 'Labels',
})}
</span>
}
>
<EuiButtonGroup
isFullWidth
legend={i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', {
defaultMessage: 'Labels',
})}
data-test-subj="lnsValueLabelsDisplay"
name="valueLabelsDisplay"
buttonSize="compressed"
options={valueLabelsOptions}
idSelected={
valueLabelsOptions.find(({ value }) => value === valueLabelsVisibilityMode)!
.id
}
onChange={(modeId) => {
const newMode = valueLabelsOptions.find(({ id }) => id === modeId)!.value;
setState({ ...state, valueLabels: newMode });
}}
/>
</EuiFormRow>
) : null}
{isFittingEnabled ? (
<EuiFormRow
display="columnCompressed"
label={i18n.translate('xpack.lens.xyChart.missingValuesLabel', {
defaultMessage: 'Missing values',
})}
valueOfSelected={state?.fittingFunction || 'None'}
onChange={(value) => setState({ ...state, fittingFunction: value })}
itemLayoutAlign="top"
hasDividers
/>
</EuiFormRow>
>
<EuiSuperSelect
data-test-subj="lnsMissingValuesSelect"
compressed
options={fittingFunctionDefinitions.map(({ id, title, description }) => {
return {
value: id,
dropdownDisplay: (
<>
<strong>{title}</strong>
<EuiText size="xs" color="subdued">
<p>{description}</p>
</EuiText>
</>
),
inputDisplay: title,
};
})}
valueOfSelected={state?.fittingFunction || 'None'}
onChange={(value) => setState({ ...state, fittingFunction: value })}
itemLayoutAlign="top"
hasDividers
/>
</EuiFormRow>
) : null}
</ToolbarPopover>
</TooltipWrapper>
<LegendSettingsPopover

View file

@ -137,6 +137,7 @@ describe('xy_suggestions', () => {
keptLayerIds: [],
state: {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -249,6 +250,7 @@ describe('xy_suggestions', () => {
keptLayerIds: ['first'],
state: {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -289,6 +291,7 @@ describe('xy_suggestions', () => {
keptLayerIds: ['first', 'second'],
state: {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -523,6 +526,7 @@ describe('xy_suggestions', () => {
},
state: {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
layers: [
{
@ -575,6 +579,7 @@ describe('xy_suggestions', () => {
test('keeps existing seriesType for initial tables', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
fittingFunction: 'None',
preferredSeriesType: 'line',
layers: [
@ -608,6 +613,7 @@ describe('xy_suggestions', () => {
test('makes a visible seriesType suggestion for unchanged table without split', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
@ -648,6 +654,7 @@ describe('xy_suggestions', () => {
test('suggests seriesType and stacking when there is a split', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
@ -693,6 +700,7 @@ describe('xy_suggestions', () => {
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
fittingFunction: 'None',
preferredSeriesType: 'bar',
layers: [
@ -725,6 +733,7 @@ describe('xy_suggestions', () => {
test('suggests stacking for unchanged table that has a split', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
layers: [
@ -760,6 +769,7 @@ describe('xy_suggestions', () => {
test('keeps column to dimension mappings on extended tables', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
@ -802,6 +812,7 @@ describe('xy_suggestions', () => {
test('changes column mappings when suggestion is reorder', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
@ -845,6 +856,7 @@ describe('xy_suggestions', () => {
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
preferredSeriesType: 'bar',
fittingFunction: 'None',
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },

View file

@ -509,6 +509,7 @@ function buildSuggestion({
const state: State = {
legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right },
valueLabels: currentState?.valueLabels || 'hide',
fittingFunction: currentState?.fittingFunction || 'None',
xTitle: currentState?.xTitle,
yTitle: currentState?.yTitle,

View file

@ -10937,7 +10937,6 @@
"xpack.lens.xyChart.chartTypeLabel": "チャートタイプ",
"xpack.lens.xyChart.chartTypeLegend": "チャートタイプ",
"xpack.lens.xyChart.emptyXLabel": "(空)",
"xpack.lens.xyChart.fittingDisabledHelpText": "この設定は折れ線グラフとエリアグラフでのみ適用されます。",
"xpack.lens.xyChart.fittingFunction.help": "欠測値の処理方法を定義",
"xpack.lens.xyChart.Gridlines": "グリッド線",
"xpack.lens.xyChart.gridlinesSettings.help": "xおよびy軸のグリッド線を表示",

View file

@ -10950,7 +10950,6 @@
"xpack.lens.xyChart.chartTypeLabel": "图表类型",
"xpack.lens.xyChart.chartTypeLegend": "图表类型",
"xpack.lens.xyChart.emptyXLabel": "(空)",
"xpack.lens.xyChart.fittingDisabledHelpText": "此设置仅适用于折线图和面积图。",
"xpack.lens.xyChart.fittingFunction.help": "定义处理缺失值的方式",
"xpack.lens.xyChart.Gridlines": "网格线",
"xpack.lens.xyChart.gridlinesSettings.help": "显示 x 和 y 轴网格线",

View file

@ -222,8 +222,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
},
async editMissingValues(option: string) {
await retry.try(async () => {
await testSubjects.click('lnsMissingValuesButton');
await testSubjects.exists('lnsMissingValuesSelect');
await testSubjects.click('lnsValuesButton');
await testSubjects.exists('lnsValuesButton');
});
await testSubjects.click('lnsMissingValuesSelect');
const optionSelector = await find.byCssSelector(`#${option}`);