diff --git a/x-pack/plugins/lens/public/shared_components/legend_location_settings.test.tsx b/x-pack/plugins/lens/public/shared_components/legend_location_settings.test.tsx
new file mode 100644
index 0000000000000..0c494f4d0090d
--- /dev/null
+++ b/x-pack/plugins/lens/public/shared_components/legend_location_settings.test.tsx
@@ -0,0 +1,132 @@
+/*
+ * 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 { Position } from '@elastic/charts';
+import { shallowWithIntl as shallow, mountWithIntl as mount } from '@kbn/test/jest';
+import { LegendLocationSettings, LegendLocationSettingsProps } from './legend_location_settings';
+
+describe('Legend Location Settings', () => {
+ let props: LegendLocationSettingsProps;
+ beforeEach(() => {
+ props = {
+ onLocationChange: jest.fn(),
+ onPositionChange: jest.fn(),
+ };
+ });
+
+ it('should have default the Position to right when no position is given', () => {
+ const component = shallow();
+ expect(
+ component.find('[data-test-subj="lens-legend-position-btn"]').prop('idSelected')
+ ).toEqual(Position.Right);
+ });
+
+ it('should have called the onPositionChange function on ButtonGroup change', () => {
+ const component = shallow();
+ component.find('[data-test-subj="lens-legend-position-btn"]').simulate('change');
+ expect(props.onPositionChange).toHaveBeenCalled();
+ });
+
+ it('should disable the position group if isDisabled prop is true', () => {
+ const component = shallow();
+ expect(
+ component.find('[data-test-subj="lens-legend-position-btn"]').prop('isDisabled')
+ ).toEqual(true);
+ });
+
+ it('should hide the position button group if location inside is given', () => {
+ const newProps = {
+ ...props,
+ location: 'inside',
+ } as LegendLocationSettingsProps;
+ const component = shallow();
+ expect(component.find('[data-test-subj="lens-legend-position-btn"]').length).toEqual(0);
+ });
+
+ it('should render the location settings if location inside is given', () => {
+ const newProps = {
+ ...props,
+ location: 'inside',
+ } as LegendLocationSettingsProps;
+ const component = shallow();
+ expect(component.find('[data-test-subj="lens-legend-location-btn"]').length).toEqual(1);
+ });
+
+ it('should have selected the given location', () => {
+ const newProps = {
+ ...props,
+ location: 'inside',
+ } as LegendLocationSettingsProps;
+ const component = shallow();
+ expect(
+ component.find('[data-test-subj="lens-legend-location-btn"]').prop('idSelected')
+ ).toEqual('xy_location_inside');
+ });
+
+ it('should have called the onLocationChange function on ButtonGroup change', () => {
+ const newProps = {
+ ...props,
+ location: 'inside',
+ } as LegendLocationSettingsProps;
+ const component = shallow();
+ component
+ .find('[data-test-subj="lens-legend-location-btn"]')
+ .simulate('change', 'xy_location_outside');
+ expect(props.onLocationChange).toHaveBeenCalled();
+ });
+
+ it('should default the alignment to top right when no value is given', () => {
+ const newProps = {
+ ...props,
+ location: 'inside',
+ } as LegendLocationSettingsProps;
+ const component = shallow();
+ expect(
+ component.find('[data-test-subj="lens-legend-inside-alignment-btn"]').prop('idSelected')
+ ).toEqual('xy_location_alignment_top_right');
+ });
+
+ it('should have called the onAlignmentChange function on ButtonGroup change', () => {
+ const newProps = {
+ ...props,
+ onAlignmentChange: jest.fn(),
+ location: 'inside',
+ } as LegendLocationSettingsProps;
+ const component = shallow();
+ component
+ .find('[data-test-subj="lens-legend-inside-alignment-btn"]')
+ .simulate('change', 'xy_location_alignment_top_left');
+ expect(newProps.onAlignmentChange).toHaveBeenCalled();
+ });
+
+ it('should have default the columns input to 1 when no value is given', () => {
+ const newProps = {
+ ...props,
+ location: 'inside',
+ } as LegendLocationSettingsProps;
+ const component = mount();
+ expect(
+ component.find('[data-test-subj="lens-legend-location-columns-input"]').at(0).prop('value')
+ ).toEqual(1);
+ });
+
+ it('should disable the components when is Disabled is true', () => {
+ const newProps = {
+ ...props,
+ location: 'inside',
+ isDisabled: true,
+ } as LegendLocationSettingsProps;
+ const component = shallow();
+ expect(
+ component.find('[data-test-subj="lens-legend-location-btn"]').prop('isDisabled')
+ ).toEqual(true);
+ expect(
+ component.find('[data-test-subj="lens-legend-inside-alignment-btn"]').prop('isDisabled')
+ ).toEqual(true);
+ });
+});
diff --git a/x-pack/plugins/lens/public/shared_components/legend_location_settings.tsx b/x-pack/plugins/lens/public/shared_components/legend_location_settings.tsx
new file mode 100644
index 0000000000000..4265dee2251b5
--- /dev/null
+++ b/x-pack/plugins/lens/public/shared_components/legend_location_settings.tsx
@@ -0,0 +1,329 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { EuiFormRow, EuiButtonGroup, EuiFieldNumber } from '@elastic/eui';
+import { VerticalAlignment, HorizontalAlignment, Position } from '@elastic/charts';
+import { useDebouncedValue } from './debounced_value';
+import { TooltipWrapper } from './tooltip_wrapper';
+
+export interface LegendLocationSettingsProps {
+ /**
+ * Sets the legend position
+ */
+ position?: Position;
+ /**
+ * Callback on position option change
+ */
+ onPositionChange: (id: string) => void;
+ /**
+ * Determines the legend location
+ */
+ location?: 'inside' | 'outside';
+ /**
+ * Callback on location option change
+ */
+ onLocationChange?: (id: string) => void;
+ /**
+ * Sets the vertical alignment for legend inside chart
+ */
+ verticalAlignment?: VerticalAlignment;
+ /**
+ * Sets the vertical alignment for legend inside chart
+ */
+ horizontalAlignment?: HorizontalAlignment;
+ /**
+ * Callback on horizontal alignment option change
+ */
+ onAlignmentChange?: (id: string) => void;
+ /**
+ * Sets the number of columns for legend inside chart
+ */
+ floatingColumns?: number;
+ /**
+ * Callback on horizontal alignment option change
+ */
+ onFloatingColumnsChange?: (value: number) => void;
+ /**
+ * Flag to disable the location settings
+ */
+ isDisabled?: boolean;
+}
+
+const DEFAULT_FLOATING_COLUMNS = 1;
+
+const toggleButtonsIcons = [
+ {
+ id: Position.Top,
+ label: i18n.translate('xpack.lens.shared.legendPositionTop', {
+ defaultMessage: 'Top',
+ }),
+ iconType: 'arrowUp',
+ },
+ {
+ id: Position.Right,
+ label: i18n.translate('xpack.lens.shared.legendPositionRight', {
+ defaultMessage: 'Right',
+ }),
+ iconType: 'arrowRight',
+ },
+ {
+ id: Position.Bottom,
+ label: i18n.translate('xpack.lens.shared.legendPositionBottom', {
+ defaultMessage: 'Bottom',
+ }),
+ iconType: 'arrowDown',
+ },
+ {
+ id: Position.Left,
+ label: i18n.translate('xpack.lens.shared.legendPositionLeft', {
+ defaultMessage: 'Left',
+ }),
+ iconType: 'arrowLeft',
+ },
+];
+
+const locationOptions: Array<{
+ id: string;
+ value: 'outside' | 'inside';
+ label: string;
+}> = [
+ {
+ id: `xy_location_outside`,
+ value: 'outside',
+ label: i18n.translate('xpack.lens.xyChart.legendLocation.outside', {
+ defaultMessage: 'Outside',
+ }),
+ },
+ {
+ id: `xy_location_inside`,
+ value: 'inside',
+ label: i18n.translate('xpack.lens.xyChart.legendLocation.inside', {
+ defaultMessage: 'Inside',
+ }),
+ },
+];
+
+const locationAlignmentButtonsIcons: Array<{
+ id: string;
+ value: 'bottom_left' | 'bottom_right' | 'top_left' | 'top_right';
+ label: string;
+ iconType: string;
+}> = [
+ {
+ id: 'xy_location_alignment_top_right',
+ value: 'top_right',
+ label: i18n.translate('xpack.lens.shared.legendLocationTopRight', {
+ defaultMessage: 'Top right',
+ }),
+ iconType: 'editorPositionTopRight',
+ },
+ {
+ id: 'xy_location_alignment_top_left',
+ value: 'top_left',
+ label: i18n.translate('xpack.lens.shared.legendLocationTopLeft', {
+ defaultMessage: 'Top left',
+ }),
+ iconType: 'editorPositionTopLeft',
+ },
+ {
+ id: 'xy_location_alignment_bottom_right',
+ value: 'bottom_right',
+ label: i18n.translate('xpack.lens.shared.legendLocationBottomRight', {
+ defaultMessage: 'Bottom right',
+ }),
+ iconType: 'editorPositionBottomRight',
+ },
+ {
+ id: 'xy_location_alignment_bottom_left',
+ value: 'bottom_left',
+ label: i18n.translate('xpack.lens.shared.legendLocationBottomLeft', {
+ defaultMessage: 'Bottom left',
+ }),
+ iconType: 'editorPositionBottomLeft',
+ },
+];
+
+const FloatingColumnsInput = ({
+ value,
+ setValue,
+ isDisabled,
+}: {
+ value: number;
+ setValue: (value: number) => void;
+ isDisabled: boolean;
+}) => {
+ const { inputValue, handleInputChange } = useDebouncedValue({ value, onChange: setValue });
+ return (
+ {
+ handleInputChange(Number(e.target.value));
+ }}
+ />
+ );
+};
+
+export const LegendLocationSettings: React.FunctionComponent = ({
+ location,
+ onLocationChange = () => {},
+ position,
+ onPositionChange,
+ verticalAlignment,
+ horizontalAlignment,
+ onAlignmentChange = () => {},
+ floatingColumns,
+ onFloatingColumnsChange = () => {},
+ isDisabled = false,
+}) => {
+ const alignment = `${verticalAlignment || VerticalAlignment.Top}_${
+ horizontalAlignment || HorizontalAlignment.Right
+ }`;
+ return (
+ <>
+ {location && (
+
+
+ value === location)!.id}
+ onChange={(optionId) => {
+ const newLocation = locationOptions.find(({ id }) => id === optionId)!.value;
+ onLocationChange(newLocation);
+ }}
+ />
+
+
+ )}
+
+ <>
+ {(!location || location === 'outside') && (
+
+
+
+ )}
+ {location === 'inside' && (
+
+ value === alignment)!.id
+ }
+ onChange={(optionId) => {
+ const newAlignment = locationAlignmentButtonsIcons.find(
+ ({ id }) => id === optionId
+ )!.value;
+ onAlignmentChange(newAlignment);
+ }}
+ isIconOnly
+ />
+
+ )}
+ >
+
+ {location && (
+
+
+
+
+
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx
index 5a6f1b91234e8..e2fd630702b6b 100644
--- a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx
+++ b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx
@@ -6,7 +6,6 @@
*/
import React from 'react';
-import { Position } from '@elastic/charts';
import { shallowWithIntl as shallow } from '@kbn/test/jest';
import { LegendSettingsPopover, LegendSettingsPopoverProps } from './legend_settings_popover';
@@ -51,26 +50,6 @@ describe('Legend Settings', () => {
expect(props.onDisplayChange).toHaveBeenCalled();
});
- it('should have default the Position to right when no position is given', () => {
- const component = shallow();
- expect(
- component.find('[data-test-subj="lens-legend-position-btn"]').prop('idSelected')
- ).toEqual(Position.Right);
- });
-
- it('should have called the onPositionChange function on ButtonGroup change', () => {
- const component = shallow();
- component.find('[data-test-subj="lens-legend-position-btn"]').simulate('change');
- expect(props.onPositionChange).toHaveBeenCalled();
- });
-
- it('should disable the position button group on hide mode', () => {
- const component = shallow();
- expect(
- component.find('[data-test-subj="lens-legend-position-btn"]').prop('isDisabled')
- ).toEqual(true);
- });
-
it('should enable the Nested Legend Switch when renderNestedLegendSwitch prop is true', () => {
const component = shallow();
expect(component.find('[data-test-subj="lens-legend-nested-switch"]')).toHaveLength(1);
diff --git a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx
index e86a81ba66203..0ec7c11f6fdc1 100644
--- a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx
+++ b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx
@@ -8,15 +8,21 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiButtonGroup, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
-import { Position } from '@elastic/charts';
+import { Position, VerticalAlignment, HorizontalAlignment } from '@elastic/charts';
import { ToolbarPopover } from '../shared_components';
+import { LegendLocationSettings } from './legend_location_settings';
import { ToolbarButtonProps } from '../../../../../src/plugins/kibana_react/public';
+import { TooltipWrapper } from './tooltip_wrapper';
export interface LegendSettingsPopoverProps {
/**
* Determines the legend display options
*/
- legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide' | 'default'; label: string }>;
+ legendOptions: Array<{
+ id: string;
+ value: 'auto' | 'show' | 'hide' | 'default';
+ label: string;
+ }>;
/**
* Determines the legend mode
*/
@@ -33,6 +39,34 @@ export interface LegendSettingsPopoverProps {
* Callback on position option change
*/
onPositionChange: (id: string) => void;
+ /**
+ * Determines the legend location
+ */
+ location?: 'inside' | 'outside';
+ /**
+ * Callback on location option change
+ */
+ onLocationChange?: (id: string) => void;
+ /**
+ * Sets the vertical alignment for legend inside chart
+ */
+ verticalAlignment?: VerticalAlignment;
+ /**
+ * Sets the vertical alignment for legend inside chart
+ */
+ horizontalAlignment?: HorizontalAlignment;
+ /**
+ * Callback on horizontal alignment option change
+ */
+ onAlignmentChange?: (id: string) => void;
+ /**
+ * Sets the number of columns for legend inside chart
+ */
+ floatingColumns?: number;
+ /**
+ * Callback on horizontal alignment option change
+ */
+ onFloatingColumnsChange?: (value: number) => void;
/**
* If true, nested legend switch is rendered
*/
@@ -63,42 +97,18 @@ export interface LegendSettingsPopoverProps {
groupPosition?: ToolbarButtonProps['groupPosition'];
}
-const toggleButtonsIcons = [
- {
- id: Position.Bottom,
- label: i18n.translate('xpack.lens.shared.legendPositionBottom', {
- defaultMessage: 'Bottom',
- }),
- iconType: 'arrowDown',
- },
- {
- id: Position.Left,
- label: i18n.translate('xpack.lens.shared.legendPositionLeft', {
- defaultMessage: 'Left',
- }),
- iconType: 'arrowLeft',
- },
- {
- id: Position.Right,
- label: i18n.translate('xpack.lens.shared.legendPositionRight', {
- defaultMessage: 'Right',
- }),
- iconType: 'arrowRight',
- },
- {
- id: Position.Top,
- label: i18n.translate('xpack.lens.shared.legendPositionTop', {
- defaultMessage: 'Top',
- }),
- iconType: 'arrowUp',
- },
-];
-
export const LegendSettingsPopover: React.FunctionComponent = ({
legendOptions,
mode,
onDisplayChange,
position,
+ location,
+ onLocationChange = () => {},
+ verticalAlignment,
+ horizontalAlignment,
+ floatingColumns,
+ onAlignmentChange = () => {},
+ onFloatingColumnsChange = () => {},
onPositionChange,
renderNestedLegendSwitch,
nestedLegend,
@@ -136,26 +146,18 @@ export const LegendSettingsPopover: React.FunctionComponent
-
-
-
+
{renderNestedLegendSwitch && (
-
+ condition={mode === 'hide'}
+ position="top"
+ delay="regular"
+ display="block"
+ >
+
+
)}
{renderValueInLegendSwitch && (
@@ -183,17 +195,27 @@ export const LegendSettingsPopover: React.FunctionComponent
-
+ condition={mode === 'hide'}
+ position="top"
+ delay="regular"
+ display="block"
+ >
+
+
)}
diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap
index ac8f089d46487..bcf54c6696ee0 100644
--- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap
+++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap
@@ -114,6 +114,9 @@ Object {
"chain": Array [
Object {
"arguments": Object {
+ "floatingColumns": Array [],
+ "horizontalAlignment": Array [],
+ "isInside": Array [],
"isVisible": Array [
true,
],
@@ -121,6 +124,7 @@ Object {
"bottom",
],
"showSingleSeries": Array [],
+ "verticalAlignment": Array [],
},
"function": "lens_xy_legendConfig",
"type": "function",
diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
index 930f6888ce532..b018e62f1fd8f 100644
--- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
@@ -17,6 +17,9 @@ import {
XYChartSeriesIdentifier,
SeriesNameFn,
Fit,
+ HorizontalAlignment,
+ VerticalAlignment,
+ LayoutDirection,
} from '@elastic/charts';
import { PaletteOutput } from 'src/plugins/charts/public';
import {
@@ -2251,6 +2254,30 @@ describe('xy_expression', () => {
expect(component.find(Settings).prop('showLegend')).toEqual(true);
});
+ test('it should populate the correct legendPosition if isInside is set', () => {
+ const { data, args } = sampleArgs();
+
+ const component = shallow(
+
+ );
+
+ expect(component.find(Settings).prop('legendPosition')).toEqual({
+ vAlign: VerticalAlignment.Top,
+ hAlign: HorizontalAlignment.Right,
+ direction: LayoutDirection.Vertical,
+ floating: true,
+ floatingColumns: 1,
+ });
+ });
+
test('it not show legend if isVisible is set to false', () => {
const { data, args } = sampleArgs();
diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx
index 404608f9da43a..7c767cd1d1b04 100644
--- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx
@@ -22,9 +22,11 @@ import {
StackMode,
VerticalAlignment,
HorizontalAlignment,
+ LayoutDirection,
ElementClickListener,
BrushEndListener,
CurveType,
+ LegendPositionConfig,
LabelOverflowConstraint,
} from '@elastic/charts';
import { I18nProvider } from '@kbn/i18n/react';
@@ -602,6 +604,14 @@ export function XYChart({
onSelectRange(context);
};
+ const legendInsideParams = {
+ vAlign: legend.verticalAlignment ?? VerticalAlignment.Top,
+ hAlign: legend?.horizontalAlignment ?? HorizontalAlignment.Right,
+ direction: LayoutDirection.Vertical,
+ floating: true,
+ floatingColumns: legend?.floatingColumns ?? 1,
+ } as LegendPositionConfig;
+
return (
, index: n
};
}
-const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [
+const legendOptions: Array<{
+ id: string;
+ value: 'auto' | 'show' | 'hide';
+ label: string;
+}> = [
{
id: `xy_legend_auto`,
value: 'auto',
@@ -319,32 +323,72 @@ export const XyToolbar = memo(function XyToolbar(props: VisualizationToolbarProp
{
+ setState({
+ ...state,
+ legend: {
+ ...state.legend,
+ isInside: location === 'inside',
+ },
+ });
+ }}
onDisplayChange={(optionId) => {
const newMode = legendOptions.find(({ id }) => id === optionId)!.value;
if (newMode === 'auto') {
setState({
...state,
- legend: { ...state.legend, isVisible: true, showSingleSeries: false },
+ legend: {
+ ...state.legend,
+ isVisible: true,
+ showSingleSeries: false,
+ },
});
} else if (newMode === 'show') {
setState({
...state,
- legend: { ...state.legend, isVisible: true, showSingleSeries: true },
+ legend: {
+ ...state.legend,
+ isVisible: true,
+ showSingleSeries: true,
+ },
});
} else if (newMode === 'hide') {
setState({
...state,
- legend: { ...state.legend, isVisible: false, showSingleSeries: false },
+ legend: {
+ ...state.legend,
+ isVisible: false,
+ showSingleSeries: false,
+ },
});
}
}}
position={state?.legend.position}
+ horizontalAlignment={state?.legend.horizontalAlignment}
+ verticalAlignment={state?.legend.verticalAlignment}
+ floatingColumns={state?.legend.floatingColumns}
+ onFloatingColumnsChange={(val) => {
+ setState({
+ ...state,
+ legend: { ...state.legend, floatingColumns: val },
+ });
+ }}
onPositionChange={(id) => {
setState({
...state,
legend: { ...state.legend, position: id as Position },
});
}}
+ onAlignmentChange={(value) => {
+ const [vertical, horizontal] = value.split('_');
+ const verticalAlignment = vertical as VerticalAlignment;
+ const horizontalAlignment = horizontal as HorizontalAlignment;
+ setState({
+ ...state,
+ legend: { ...state.legend, verticalAlignment, horizontalAlignment },
+ });
+ }}
renderValueInLegendSwitch={nonOrdinalXAxis}
valueInLegend={state?.valuesInLegend}
onValueInLegendChange={() => {
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 0cf63b94f1758..ad3a58e0e9584 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -12712,7 +12712,6 @@
"xpack.lens.shared.curveLabel": "",
"xpack.lens.shared.legendLabel": "凡例",
"xpack.lens.shared.legendPositionBottom": "一番下",
- "xpack.lens.shared.legendPositionLabel": "位置",
"xpack.lens.shared.legendPositionLeft": "左",
"xpack.lens.shared.legendPositionRight": "右",
"xpack.lens.shared.legendPositionTop": "トップ",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 6659c51867fa6..f7b8e216f3a4f 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -12884,7 +12884,6 @@
"xpack.lens.shared.curveLabel": "视觉选项",
"xpack.lens.shared.legendLabel": "图例",
"xpack.lens.shared.legendPositionBottom": "底部",
- "xpack.lens.shared.legendPositionLabel": "位置",
"xpack.lens.shared.legendPositionLeft": "左",
"xpack.lens.shared.legendPositionRight": "右",
"xpack.lens.shared.legendPositionTop": "顶部",