diff --git a/x-pack/plugins/lens/public/assets/axis_bottom.tsx b/x-pack/plugins/lens/public/assets/axis_bottom.tsx
new file mode 100644
index 0000000000000..9529a93e4c1cc
--- /dev/null
+++ b/x-pack/plugins/lens/public/assets/axis_bottom.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as React from 'react';
+
+export const EuiIconAxisBottom = ({
+ title,
+ titleId,
+ ...props
+}: {
+ title: string;
+ titleId: string;
+}) => (
+
+);
diff --git a/x-pack/plugins/lens/public/assets/axis_left.tsx b/x-pack/plugins/lens/public/assets/axis_left.tsx
new file mode 100644
index 0000000000000..d1ec0b76a1bd5
--- /dev/null
+++ b/x-pack/plugins/lens/public/assets/axis_left.tsx
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as React from 'react';
+
+export const EuiIconAxisLeft = ({
+ title,
+ titleId,
+ ...props
+}: {
+ title: string;
+ titleId: string;
+}) => (
+
+);
diff --git a/x-pack/plugins/lens/public/assets/axis_right.tsx b/x-pack/plugins/lens/public/assets/axis_right.tsx
new file mode 100644
index 0000000000000..e61f87b963a05
--- /dev/null
+++ b/x-pack/plugins/lens/public/assets/axis_right.tsx
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as React from 'react';
+
+export const EuiIconAxisRight = ({
+ title,
+ titleId,
+ ...props
+}: {
+ title: string;
+ titleId: string;
+}) => (
+
+);
diff --git a/x-pack/plugins/lens/public/assets/axis_top.tsx b/x-pack/plugins/lens/public/assets/axis_top.tsx
new file mode 100644
index 0000000000000..90fbdc4a21552
--- /dev/null
+++ b/x-pack/plugins/lens/public/assets/axis_top.tsx
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as React from 'react';
+
+export const EuiIconAxisTop = ({
+ title,
+ titleId,
+ ...props
+}: {
+ title: string;
+ titleId: string;
+}) => (
+
+);
diff --git a/x-pack/plugins/lens/public/assets/legend.tsx b/x-pack/plugins/lens/public/assets/legend.tsx
new file mode 100644
index 0000000000000..d73e68839d9fb
--- /dev/null
+++ b/x-pack/plugins/lens/public/assets/legend.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as React from 'react';
+
+export const EuiIconLegend = ({ title, titleId, ...props }: { title: string; titleId: string }) => (
+
+);
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss
index 90cc049db96eb..a4d8288d5e600 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_workspace_panel_wrapper.scss
@@ -38,5 +38,5 @@
}
.lnsWorkspacePanelWrapper__toolbar {
- margin-bottom: $euiSizeS;
+ margin-bottom: 0;
}
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx
index 682316a586626..abbd7e0838bed 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx
@@ -9,7 +9,7 @@ import { EuiPopover, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { NativeRenderer } from '../../../native_renderer';
import { Visualization, VisualizationLayerWidgetProps } from '../../../types';
-import { ToolbarButton } from '../../../toolbar_button';
+import { ToolbarButton } from '../../../shared_components';
export function LayerSettings({
layerId,
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
index 82983862e7c03..f4526cac39c8a 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx
@@ -19,7 +19,7 @@ import { Visualization, FramePublicAPI, Datasource } from '../../../types';
import { Action } from '../state_management';
import { getSuggestions, switchToSuggestion, Suggestion } from '../suggestion_helpers';
import { trackUiEvent } from '../../../lens_ui_telemetry';
-import { ToolbarButton } from '../../../toolbar_button';
+import { ToolbarButton } from '../../../shared_components';
interface VisualizationSelection {
visualizationId: string;
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx
index 901a86bb56e1d..8e7d504ff7677 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx
@@ -67,8 +67,14 @@ export function WorkspacePanelWrapper({
);
return (
<>
-
-
+
+
{activeVisualization && activeVisualization.renderToolbar && (
-
+
= [
{
@@ -99,179 +93,128 @@ const legendOptions: Array<{
id: 'pieLegendDisplay-default',
value: 'default',
label: i18n.translate('xpack.lens.pieChart.legendVisibility.auto', {
- defaultMessage: 'auto',
+ defaultMessage: 'Auto',
}),
},
{
id: 'pieLegendDisplay-show',
value: 'show',
label: i18n.translate('xpack.lens.pieChart.legendVisibility.show', {
- defaultMessage: 'show',
+ defaultMessage: 'Show',
}),
},
{
id: 'pieLegendDisplay-hide',
value: 'hide',
label: i18n.translate('xpack.lens.pieChart.legendVisibility.hide', {
- defaultMessage: 'hide',
+ defaultMessage: 'Hide',
}),
},
];
export function PieToolbar(props: VisualizationToolbarProps) {
- const [open, setOpen] = useState(false);
const { state, setState } = props;
const layer = state.layers[0];
if (!layer) {
return null;
}
return (
-
-
- {
- setOpen(!open);
- }}
- >
- {i18n.translate('xpack.lens.pieChart.settingsLabel', { defaultMessage: 'Settings' })}
-
- }
- isOpen={open}
- closePopover={() => {
- setOpen(false);
- }}
- anchorPosition="downRight"
+
+
+
-
- {
- setState({
- ...state,
- layers: [{ ...layer, categoryDisplay: option }],
- });
- }}
- />
-
-
- {
- setState({
- ...state,
- layers: [{ ...layer, numberDisplay: option }],
- });
- }}
- />
-
-
-
-
- setState({
- ...state,
- layers: [{ ...layer, percentDecimals: value }],
- })
- }
- />
-
-
-
-
- value === layer.legendDisplay)!.id}
- onChange={(optionId) => {
- setState({
- ...state,
- layers: [
- {
- ...layer,
- legendDisplay: legendOptions.find(({ id }) => id === optionId)!.value,
- },
- ],
- });
- }}
- buttonSize="compressed"
- isFullWidth
- />
-
-
- {
- setState({ ...state, layers: [{ ...layer, nestedLegend: !layer.nestedLegend }] });
- }}
- />
-
-
-
- {
- setState({
- ...state,
- layers: [{ ...layer, legendPosition: e.target.value as Position }],
- });
- }}
- />
-
-
-
+ {
+ setState({
+ ...state,
+ layers: [{ ...layer, categoryDisplay: option }],
+ });
+ }}
+ />
+
+
+ {
+ setState({
+ ...state,
+ layers: [{ ...layer, numberDisplay: option }],
+ });
+ }}
+ />
+
+
+
+
+ setState({
+ ...state,
+ layers: [{ ...layer, percentDecimals: value }],
+ })
+ }
+ />
+
+
+ {
+ setState({
+ ...state,
+ layers: [
+ {
+ ...layer,
+ legendDisplay: legendOptions.find(({ id }) => id === optionId)!.value,
+ },
+ ],
+ });
+ }}
+ position={layer.legendPosition}
+ onPositionChange={(id) => {
+ setState({
+ ...state,
+ layers: [{ ...layer, legendPosition: id as Position }],
+ });
+ }}
+ renderNestedLegendSwitch
+ nestedLegend={!!layer.nestedLegend}
+ onNestedLegendChange={() => {
+ setState({
+ ...state,
+ layers: [{ ...layer, nestedLegend: !layer.nestedLegend }],
+ });
+ }}
+ />
);
}
diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts
index ad662fd7a59d9..c0362a5660adb 100644
--- a/x-pack/plugins/lens/public/shared_components/index.ts
+++ b/x-pack/plugins/lens/public/shared_components/index.ts
@@ -5,3 +5,6 @@
*/
export * from './empty_placeholder';
+export { ToolbarPopoverProps, ToolbarPopover } from './toolbar_popover';
+export { ToolbarButtonProps, ToolbarButton } from './toolbar_button';
+export { LegendSettingsPopover } from './legend_settings_popover';
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
new file mode 100644
index 0000000000000..1e0e6b33b6cd4
--- /dev/null
+++ b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx
@@ -0,0 +1,106 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { Position } from '@elastic/charts';
+import { shallowWithIntl as shallow } from 'test_utils/enzyme_helpers';
+import { LegendSettingsPopover, LegendSettingsPopoverProps } from './legend_settings_popover';
+
+describe('Legend Settings', () => {
+ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [
+ {
+ id: `test_legend_auto`,
+ value: 'auto',
+ label: 'Auto',
+ },
+ {
+ id: `test_legend_show`,
+ value: 'show',
+ label: 'Show',
+ },
+ {
+ id: `test_legend_hide`,
+ value: 'hide',
+ label: 'Hide',
+ },
+ ];
+ let props: LegendSettingsPopoverProps;
+ beforeEach(() => {
+ props = {
+ legendOptions,
+ mode: 'auto',
+ onDisplayChange: jest.fn(),
+ onPositionChange: jest.fn(),
+ };
+ });
+
+ it('should have selected the given mode as Display value', () => {
+ const component = shallow();
+ expect(component.find('[data-test-subj="lens-legend-display-btn"]').prop('idSelected')).toEqual(
+ 'test_legend_auto'
+ );
+ });
+
+ it('should have called the onDisplayChange function on ButtonGroup change', () => {
+ const component = shallow();
+ component.find('[data-test-subj="lens-legend-display-btn"]').simulate('change');
+ 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);
+ });
+
+ it('should set the switch state on nestedLegend prop value', () => {
+ const component = shallow(
+
+ );
+ expect(component.find('[data-test-subj="lens-legend-nested-switch"]').prop('checked')).toEqual(
+ true
+ );
+ });
+
+ it('should have called the onNestedLegendChange function on switch change', () => {
+ const nestedProps = {
+ ...props,
+ renderNestedLegendSwitch: true,
+ onNestedLegendChange: jest.fn(),
+ };
+ const component = shallow();
+ component.find('[data-test-subj="lens-legend-nested-switch"]').simulate('change');
+ expect(nestedProps.onNestedLegendChange).toHaveBeenCalled();
+ });
+
+ it('should disable switch group on hide mode', () => {
+ const component = shallow(
+
+ );
+ expect(component.find('[data-test-subj="lens-legend-nested-switch"]').prop('disabled')).toEqual(
+ true
+ );
+ });
+});
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
new file mode 100644
index 0000000000000..b3df4814b85f8
--- /dev/null
+++ b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx
@@ -0,0 +1,158 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiFormRow, EuiButtonGroup, EuiSwitch, EuiSwitchEvent } from '@elastic/eui';
+import { Position } from '@elastic/charts';
+import { ToolbarPopover } from '../shared_components';
+
+export interface LegendSettingsPopoverProps {
+ /**
+ * Determines the legend display options
+ */
+ legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide' | 'default'; label: string }>;
+ /**
+ * Determines the legend mode
+ */
+ mode: 'default' | 'show' | 'hide' | 'auto';
+ /**
+ * Callback on display option change
+ */
+ onDisplayChange: (id: string) => void;
+ /**
+ * Sets the legend position
+ */
+ position?: Position;
+ /**
+ * Callback on position option change
+ */
+ onPositionChange: (id: string) => void;
+ /**
+ * If true, nested legend switch is rendered
+ */
+ renderNestedLegendSwitch?: boolean;
+ /**
+ * nested legend switch status
+ */
+ nestedLegend?: boolean;
+ /**
+ * Callback on nested switch status change
+ */
+ onNestedLegendChange?: (event: EuiSwitchEvent) => void;
+}
+
+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,
+ onPositionChange,
+ renderNestedLegendSwitch,
+ nestedLegend,
+ onNestedLegendChange = () => {},
+}) => {
+ return (
+
+
+ value === mode)!.id}
+ onChange={onDisplayChange}
+ />
+
+
+
+
+ {renderNestedLegendSwitch && (
+
+
+
+ )}
+
+ );
+};
diff --git a/x-pack/plugins/lens/public/shared_components/toolbar_button.scss b/x-pack/plugins/lens/public/shared_components/toolbar_button.scss
new file mode 100644
index 0000000000000..61b02f47678c3
--- /dev/null
+++ b/x-pack/plugins/lens/public/shared_components/toolbar_button.scss
@@ -0,0 +1,60 @@
+.lnsToolbarButton {
+ line-height: $euiButtonHeight; // Keeps alignment of text and chart icon
+ background-color: $euiColorEmptyShade;
+
+ // Some toolbar buttons are just icons, but EuiButton comes with margin and min-width that need to be removed
+ min-width: 0;
+
+ &[class*='--text'] {
+ // Lighten the border color for all states
+ border-color: $euiBorderColor !important; // sass-lint:disable-line no-important
+ }
+
+ &[class*='isDisabled'] {
+ // There is a popover `pointer-events: none` that messes with the not-allowed cursor
+ pointer-events: initial;
+ }
+
+ .lnsToolbarButton__text > svg {
+ margin-top: -1px; // Just some weird alignment issue when icon is the child not the `iconType`
+ }
+
+ .lnsToolbarButton__text:empty {
+ margin: 0;
+ }
+
+ // Toolbar buttons don't look good with centered text when fullWidth
+ &[class*='fullWidth'] {
+ text-align: left;
+
+ .lnsToolbarButton__content {
+ justify-content: space-between;
+ }
+ }
+
+}
+
+.lnsToolbarButton--groupLeft {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.lnsToolbarButton--groupCenter {
+ border-radius: 0;
+ border-left: none;
+}
+
+.lnsToolbarButton--groupRight {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ border-left: none;
+}
+
+.lnsToolbarButton--bold {
+ font-weight: $euiFontWeightBold;
+}
+
+.lnsToolbarButton--s {
+ box-shadow: none !important; // sass-lint:disable-line no-important
+ font-size: $euiFontSizeS;
+}
diff --git a/x-pack/plugins/lens/public/toolbar_button/toolbar_button.tsx b/x-pack/plugins/lens/public/shared_components/toolbar_button.tsx
similarity index 71%
rename from x-pack/plugins/lens/public/toolbar_button/toolbar_button.tsx
rename to x-pack/plugins/lens/public/shared_components/toolbar_button.tsx
index 0a63781818171..56647352750a1 100644
--- a/x-pack/plugins/lens/public/toolbar_button/toolbar_button.tsx
+++ b/x-pack/plugins/lens/public/shared_components/toolbar_button.tsx
@@ -9,6 +9,13 @@ import React from 'react';
import classNames from 'classnames';
import { EuiButton, PropsOf, EuiButtonProps } from '@elastic/eui';
+const groupPositionToClassMap = {
+ none: null,
+ left: 'lnsToolbarButton--groupLeft',
+ center: 'lnsToolbarButton--groupCenter',
+ right: 'lnsToolbarButton--groupRight',
+};
+
export type ToolbarButtonProps = PropsOf & {
/**
* Determines prominence
@@ -18,6 +25,14 @@ export type ToolbarButtonProps = PropsOf & {
* Smaller buttons also remove extra shadow for less prominence
*/
size?: EuiButtonProps['size'];
+ /**
+ * Determines if the button will have a down arrow or not
+ */
+ hasArrow?: boolean;
+ /**
+ * Adjusts the borders for groupings
+ */
+ groupPosition?: 'none' | 'left' | 'center' | 'right';
};
export const ToolbarButton: React.FunctionComponent = ({
@@ -25,10 +40,13 @@ export const ToolbarButton: React.FunctionComponent = ({
className,
fontWeight = 'normal',
size = 'm',
+ hasArrow = true,
+ groupPosition = 'none',
...rest
}) => {
const classes = classNames(
'lnsToolbarButton',
+ groupPositionToClassMap[groupPosition],
[`lnsToolbarButton--${fontWeight}`, `lnsToolbarButton--${size}`],
className
);
@@ -36,7 +54,7 @@ export const ToolbarButton: React.FunctionComponent = ({
= ({
+ children,
+ title,
+ type,
+ isDisabled = false,
+ groupPosition,
+}) => {
+ const [open, setOpen] = useState(false);
+
+ const iconType: string | IconType = typeof type === 'string' ? typeToIconMap[type] : type;
+
+ return (
+
+ {
+ setOpen(!open);
+ }}
+ hasArrow={false}
+ isDisabled={isDisabled}
+ groupPosition={groupPosition}
+ >
+
+
+ }
+ isOpen={open}
+ closePopover={() => {
+ setOpen(false);
+ }}
+ anchorPosition="downRight"
+ >
+ {title}
+ {children}
+
+
+ );
+};
diff --git a/x-pack/plugins/lens/public/toolbar_button/index.tsx b/x-pack/plugins/lens/public/toolbar_button/index.tsx
deleted file mode 100644
index ee6489726a0a7..0000000000000
--- a/x-pack/plugins/lens/public/toolbar_button/index.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export { ToolbarButtonProps, ToolbarButton } from './toolbar_button';
diff --git a/x-pack/plugins/lens/public/toolbar_button/toolbar_button.scss b/x-pack/plugins/lens/public/toolbar_button/toolbar_button.scss
deleted file mode 100644
index f36fdfdf02aba..0000000000000
--- a/x-pack/plugins/lens/public/toolbar_button/toolbar_button.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-.lnsToolbarButton {
- line-height: $euiButtonHeight; // Keeps alignment of text and chart icon
- background-color: $euiColorEmptyShade;
- border-color: $euiBorderColor;
-
- // Some toolbar buttons are just icons, but EuiButton comes with margin and min-width that need to be removed
- min-width: 0;
-
- .lnsToolbarButton__text:empty {
- margin: 0;
- }
-
- // Toolbar buttons don't look good with centered text when fullWidth
- &[class*='fullWidth'] {
- text-align: left;
-
- .lnsToolbarButton__content {
- justify-content: space-between;
- }
- }
-}
-
-.lnsToolbarButton--bold {
- font-weight: $euiFontWeightBold;
-}
-
-.lnsToolbarButton--s {
- box-shadow: none !important; // sass-lint:disable-line no-important
- font-size: $euiFontSizeS;
-}
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 19ea75239ddb2..dd8c6377cacdc 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
@@ -5,6 +5,28 @@ Object {
"chain": Array [
Object {
"arguments": Object {
+ "axisTitlesVisibilitySettings": Array [
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {
+ "x": Array [
+ true,
+ ],
+ "yLeft": Array [
+ true,
+ ],
+ "yRight": Array [
+ true,
+ ],
+ },
+ "function": "lens_xy_axisTitlesVisibilityConfig",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ },
+ ],
"fittingFunction": Array [
"Carry",
],
@@ -16,7 +38,10 @@ Object {
"x": Array [
false,
],
- "y": Array [
+ "yLeft": Array [
+ true,
+ ],
+ "yRight": Array [
true,
],
},
@@ -92,12 +117,6 @@ Object {
"type": "expression",
},
],
- "showXAxisTitle": Array [
- true,
- ],
- "showYAxisTitle": Array [
- true,
- ],
"tickLabelsVisibilitySettings": Array [
Object {
"chain": Array [
@@ -106,7 +125,10 @@ Object {
"x": Array [
false,
],
- "y": Array [
+ "yLeft": Array [
+ true,
+ ],
+ "yRight": Array [
true,
],
},
@@ -120,6 +142,9 @@ Object {
"xTitle": Array [
"",
],
+ "yRightTitle": Array [
+ "",
+ ],
"yTitle": Array [
"",
],
diff --git a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts
index 7b0edf2b367be..15c08d17e49c6 100644
--- a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts
@@ -203,7 +203,7 @@ describe('axes_configuration', () => {
it('should map auto series to left axis', () => {
const formatFactory = jest.fn();
- const groups = getAxesConfiguration([sampleLayer], tables, formatFactory, false);
+ const groups = getAxesConfiguration([sampleLayer], false, tables, formatFactory);
expect(groups.length).toEqual(1);
expect(groups[0].position).toEqual('left');
expect(groups[0].series[0].accessor).toEqual('yAccessorId');
@@ -213,7 +213,7 @@ describe('axes_configuration', () => {
it('should map auto series to right axis if formatters do not match', () => {
const formatFactory = jest.fn();
const twoSeriesLayer = { ...sampleLayer, accessors: ['yAccessorId', 'yAccessorId2'] };
- const groups = getAxesConfiguration([twoSeriesLayer], tables, formatFactory, false);
+ const groups = getAxesConfiguration([twoSeriesLayer], false, tables, formatFactory);
expect(groups.length).toEqual(2);
expect(groups[0].position).toEqual('left');
expect(groups[1].position).toEqual('right');
@@ -227,7 +227,7 @@ describe('axes_configuration', () => {
...sampleLayer,
accessors: ['yAccessorId', 'yAccessorId2', 'yAccessorId3'],
};
- const groups = getAxesConfiguration([threeSeriesLayer], tables, formatFactory, false);
+ const groups = getAxesConfiguration([threeSeriesLayer], false, tables, formatFactory);
expect(groups.length).toEqual(2);
expect(groups[0].position).toEqual('left');
expect(groups[1].position).toEqual('right');
@@ -240,9 +240,9 @@ describe('axes_configuration', () => {
const formatFactory = jest.fn();
const groups = getAxesConfiguration(
[{ ...sampleLayer, yConfig: [{ forAccessor: 'yAccessorId', axisMode: 'right' }] }],
+ false,
tables,
- formatFactory,
- false
+ formatFactory
);
expect(groups.length).toEqual(1);
expect(groups[0].position).toEqual('right');
@@ -260,9 +260,9 @@ describe('axes_configuration', () => {
yConfig: [{ forAccessor: 'yAccessorId', axisMode: 'right' }],
},
],
+ false,
tables,
- formatFactory,
- false
+ formatFactory
);
expect(groups.length).toEqual(2);
expect(groups[0].position).toEqual('left');
@@ -284,9 +284,9 @@ describe('axes_configuration', () => {
yConfig: [{ forAccessor: 'yAccessorId', axisMode: 'right' }],
},
],
+ false,
tables,
- formatFactory,
- false
+ formatFactory
);
expect(formatFactory).toHaveBeenCalledTimes(2);
expect(formatFactory).toHaveBeenCalledWith({ id: 'number' });
diff --git a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts
index 006995d92a926..876baaabb57c5 100644
--- a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts
@@ -20,7 +20,7 @@ interface FormattedMetric {
type GroupsConfiguration = Array<{
groupId: string;
position: 'left' | 'right' | 'bottom' | 'top';
- formatter: IFieldFormat;
+ formatter?: IFieldFormat;
series: Array<{ layer: string; accessor: string }>;
}>;
@@ -33,9 +33,9 @@ export function isFormatterCompatible(
export function getAxesConfiguration(
layers: LayerConfig[],
- tables: Record,
- formatFactory: (mapping: SerializedFieldFormat) => IFieldFormat,
- shouldRotate: boolean
+ shouldRotate: boolean,
+ tables?: Record,
+ formatFactory?: (mapping: SerializedFieldFormat) => IFieldFormat
): GroupsConfiguration {
const series: { auto: FormattedMetric[]; left: FormattedMetric[]; right: FormattedMetric[] } = {
auto: [],
@@ -43,13 +43,13 @@ export function getAxesConfiguration(
right: [],
};
- layers.forEach((layer) => {
- const table = tables[layer.layerId];
+ layers?.forEach((layer) => {
+ const table = tables?.[layer.layerId];
layer.accessors.forEach((accessor) => {
const mode =
layer.yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode ||
'auto';
- let formatter: SerializedFieldFormat = table.columns.find((column) => column.id === accessor)
+ let formatter: SerializedFieldFormat = table?.columns.find((column) => column.id === accessor)
?.formatHint || { id: 'number' };
if (layer.seriesType.includes('percentage') && formatter.id !== 'percent') {
formatter = {
@@ -70,16 +70,18 @@ export function getAxesConfiguration(
series.auto.forEach((currentSeries) => {
if (
series.left.length === 0 ||
- series.left.every((leftSeries) =>
- isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
- )
+ (tables &&
+ series.left.every((leftSeries) =>
+ isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
+ ))
) {
series.left.push(currentSeries);
} else if (
series.right.length === 0 ||
- series.right.every((rightSeries) =>
- isFormatterCompatible(rightSeries.fieldFormat, currentSeries.fieldFormat)
- )
+ (tables &&
+ series.left.every((leftSeries) =>
+ isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
+ ))
) {
series.right.push(currentSeries);
} else if (series.right.length >= series.left.length) {
@@ -95,7 +97,7 @@ export function getAxesConfiguration(
axisGroups.push({
groupId: 'left',
position: shouldRotate ? 'bottom' : 'left',
- formatter: formatFactory(series.left[0].fieldFormat),
+ formatter: formatFactory?.(series.left[0].fieldFormat),
series: series.left.map(({ fieldFormat, ...currentSeries }) => currentSeries),
});
}
@@ -104,7 +106,7 @@ export function getAxesConfiguration(
axisGroups.push({
groupId: 'right',
position: shouldRotate ? 'top' : 'right',
- formatter: formatFactory(series.right[0].fieldFormat),
+ formatter: formatFactory?.(series.right[0].fieldFormat),
series: series.right.map(({ fieldFormat, ...currentSeries }) => currentSeries),
});
}
diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx
new file mode 100644
index 0000000000000..9e71323377c1a
--- /dev/null
+++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx
@@ -0,0 +1,81 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallowWithIntl as shallow } from 'test_utils/enzyme_helpers';
+import { AxisSettingsPopover, AxisSettingsPopoverProps } from './axis_settings_popover';
+import { ToolbarPopover } from '../shared_components';
+
+describe('Axes Settings', () => {
+ let props: AxisSettingsPopoverProps;
+ beforeEach(() => {
+ props = {
+ layers: [
+ {
+ seriesType: 'bar',
+ layerId: 'first',
+ splitAccessor: 'baz',
+ xAccessor: 'foo',
+ accessors: ['bar'],
+ },
+ ],
+ updateTitleState: jest.fn(),
+ axisTitle: 'My custom X axis title',
+ axis: 'x',
+ areTickLabelsVisible: true,
+ areGridlinesVisible: true,
+ isAxisTitleVisible: true,
+ toggleAxisTitleVisibility: jest.fn(),
+ toggleTickLabelsVisibility: jest.fn(),
+ toggleGridlinesVisibility: jest.fn(),
+ };
+ });
+
+ it('should disable the popover if the isDisabled property is true', () => {
+ const component = shallow();
+ expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
+ });
+
+ it('should show the axes title on the corresponding input text', () => {
+ const component = shallow();
+ expect(component.find('[data-test-subj="lnsxAxisTitle"]').prop('value')).toBe(
+ 'My custom X axis title'
+ );
+ });
+
+ it('should disable the input text if the switch is off', () => {
+ const component = shallow();
+ expect(component.find('[data-test-subj="lnsxAxisTitle"]').prop('disabled')).toBe(true);
+ });
+
+ it('has the tickLabels switch on by default', () => {
+ const component = shallow();
+ expect(component.find('[data-test-subj="lnsshowxAxisTickLabels"]').prop('checked')).toBe(true);
+ });
+
+ it('has the tickLabels switch off when tickLabelsVisibilitySettings for this axes are false', () => {
+ const component = shallow(
+
+ );
+ expect(component.find('[data-test-subj="lnsshowyLeftAxisTickLabels"]').prop('checked')).toBe(
+ false
+ );
+ });
+
+ it('has the gridlines switch on by default', () => {
+ const component = shallow();
+ expect(component.find('[data-test-subj="lnsshowxAxisGridlines"]').prop('checked')).toBe(true);
+ });
+
+ it('has the gridlines switch off when gridlinesVisibilitySettings for this axes are false', () => {
+ const component = shallow(
+
+ );
+ expect(component.find('[data-test-subj="lnsshowyRightAxisGridlines"]').prop('checked')).toBe(
+ false
+ );
+ });
+});
diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx
new file mode 100644
index 0000000000000..835f3e2cde769
--- /dev/null
+++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx
@@ -0,0 +1,205 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState } from 'react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiText,
+ EuiSwitch,
+ EuiSpacer,
+ EuiFieldText,
+ IconType,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { LayerConfig, AxesSettingsConfig } from './types';
+import { ToolbarPopover, ToolbarButtonProps } from '../shared_components';
+import { isHorizontalChart } from './state_helpers';
+import { EuiIconAxisBottom } from '../assets/axis_bottom';
+import { EuiIconAxisLeft } from '../assets/axis_left';
+import { EuiIconAxisRight } from '../assets/axis_right';
+import { EuiIconAxisTop } from '../assets/axis_top';
+
+type AxesSettingsConfigKeys = keyof AxesSettingsConfig;
+export interface AxisSettingsPopoverProps {
+ /**
+ * Determines the axis
+ */
+ axis: AxesSettingsConfigKeys;
+ /**
+ * Contains the chart layers
+ */
+ layers?: LayerConfig[];
+ /**
+ * Determines the axis title
+ */
+ axisTitle: string | undefined;
+ /**
+ * Callback to axis title change
+ */
+ updateTitleState: (value: string) => void;
+ /**
+ * Determines if the popover is Disabled
+ */
+ isDisabled?: boolean;
+ /**
+ * Determines if the ticklabels of the axis are visible
+ */
+ areTickLabelsVisible: boolean;
+ /**
+ * Toggles the axis tickLabels visibility
+ */
+ toggleTickLabelsVisibility: (axis: AxesSettingsConfigKeys) => void;
+ /**
+ * Determines if the gridlines of the axis are visible
+ */
+ areGridlinesVisible: boolean;
+ /**
+ * Toggles the gridlines visibility
+ */
+ toggleGridlinesVisibility: (axis: AxesSettingsConfigKeys) => void;
+ /**
+ * Determines if the title visibility switch is on and the input text is disabled
+ */
+ isAxisTitleVisible: boolean;
+ /**
+ * Toggles the axis title visibility
+ */
+ toggleAxisTitleVisibility: (axis: AxesSettingsConfigKeys, checked: boolean) => void;
+}
+const popoverConfig = (
+ axis: AxesSettingsConfigKeys,
+ isHorizontal: boolean
+): { icon: IconType; groupPosition: ToolbarButtonProps['groupPosition']; popoverTitle: string } => {
+ switch (axis) {
+ case 'yLeft':
+ return {
+ icon: (isHorizontal ? EuiIconAxisBottom : EuiIconAxisLeft) as IconType,
+ groupPosition: 'left',
+ popoverTitle: isHorizontal
+ ? i18n.translate('xpack.lens.xyChart.bottomAxisLabel', {
+ defaultMessage: 'Bottom axis',
+ })
+ : i18n.translate('xpack.lens.xyChart.leftAxisLabel', {
+ defaultMessage: 'Left axis',
+ }),
+ };
+ case 'yRight':
+ return {
+ icon: (isHorizontal ? EuiIconAxisTop : EuiIconAxisRight) as IconType,
+ groupPosition: 'right',
+ popoverTitle: isHorizontal
+ ? i18n.translate('xpack.lens.xyChart.topAxisLabel', {
+ defaultMessage: 'Top axis',
+ })
+ : i18n.translate('xpack.lens.xyChart.rightAxisLabel', {
+ defaultMessage: 'Right axis',
+ }),
+ };
+ case 'x':
+ default:
+ return {
+ icon: (isHorizontal ? EuiIconAxisLeft : EuiIconAxisBottom) as IconType,
+ groupPosition: 'center',
+ popoverTitle: isHorizontal
+ ? i18n.translate('xpack.lens.xyChart.leftAxisLabel', {
+ defaultMessage: 'Left axis',
+ })
+ : i18n.translate('xpack.lens.xyChart.bottomAxisLabel', {
+ defaultMessage: 'Bottom axis',
+ }),
+ };
+ }
+};
+
+export const AxisSettingsPopover: React.FunctionComponent = ({
+ layers,
+ axis,
+ axisTitle,
+ updateTitleState,
+ toggleTickLabelsVisibility,
+ toggleGridlinesVisibility,
+ isDisabled,
+ areTickLabelsVisible,
+ areGridlinesVisible,
+ isAxisTitleVisible,
+ toggleAxisTitleVisibility,
+}) => {
+ const [title, setTitle] = useState(axisTitle);
+
+ const isHorizontal = layers?.length ? isHorizontalChart(layers) : false;
+ const config = popoverConfig(axis, isHorizontal);
+
+ const onTitleChange = (value: string): void => {
+ setTitle(value);
+ updateTitleState(value);
+ };
+ return (
+
+
+
+
+
+ {i18n.translate('xpack.lens.xyChart.axisNameLabel', {
+ defaultMessage: 'Axis name',
+ })}
+
+
+
+
+ toggleAxisTitleVisibility(axis, target.checked)}
+ checked={isAxisTitleVisible}
+ />
+
+
+
+ onTitleChange(target.value)}
+ aria-label={i18n.translate('xpack.lens.xyChart.overwriteAxisTitle', {
+ defaultMessage: 'Overwrite axis title',
+ })}
+ />
+
+ toggleTickLabelsVisibility(axis)}
+ checked={areTickLabelsVisible}
+ />
+
+ toggleGridlinesVisibility(axis)}
+ checked={areGridlinesVisible}
+ />
+
+ );
+};
diff --git a/x-pack/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts
index fddcad7989b25..470d197e847eb 100644
--- a/x-pack/plugins/lens/public/xy_visualization/index.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/index.ts
@@ -10,7 +10,14 @@ import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'
import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
import { xyVisualization } from './xy_visualization';
import { xyChart, getXyChartRenderer } from './xy_expression';
-import { legendConfig, layerConfig, yAxisConfig, tickLabelsConfig, gridlinesConfig } from './types';
+import {
+ legendConfig,
+ layerConfig,
+ yAxisConfig,
+ tickLabelsConfig,
+ gridlinesConfig,
+ axisTitlesVisibilityConfig,
+} from './types';
import { EditorFrameSetup, FormatFactory } from '../types';
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
@@ -41,6 +48,7 @@ export class XyVisualization {
expressions.registerFunction(() => yAxisConfig);
expressions.registerFunction(() => tickLabelsConfig);
expressions.registerFunction(() => gridlinesConfig);
+ expressions.registerFunction(() => axisTitlesVisibilityConfig);
expressions.registerFunction(() => layerConfig);
expressions.registerFunction(() => xyChart);
diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts
index 825281d6d88c2..d09ba01b32c66 100644
--- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts
@@ -41,8 +41,8 @@ describe('#toExpression', () => {
legend: { position: Position.Bottom, isVisible: true },
preferredSeriesType: 'bar',
fittingFunction: 'Carry',
- tickLabelsVisibilitySettings: { x: false, y: true },
- gridlinesVisibilitySettings: { x: false, y: true },
+ tickLabelsVisibilitySettings: { x: false, yLeft: true, yRight: true },
+ gridlinesVisibilitySettings: { x: false, yLeft: true, yRight: true },
layers: [
{
layerId: 'first',
@@ -79,7 +79,7 @@ describe('#toExpression', () => {
).toEqual('None');
});
- it('should default the showXAxisTitle and showYAxisTitle to true', () => {
+ it('should default the axisTitles visibility settings to true', () => {
const expression = xyVisualization.toExpression(
{
legend: { position: Position.Bottom, isVisible: true },
@@ -96,8 +96,13 @@ describe('#toExpression', () => {
},
frame.datasourceLayers
) as Ast;
- expect(expression.chain[0].arguments.showXAxisTitle[0]).toBe(true);
- expect(expression.chain[0].arguments.showYAxisTitle[0]).toBe(true);
+ expect(
+ (expression.chain[0].arguments.axisTitlesVisibilitySettings[0] as Ast).chain[0].arguments
+ ).toEqual({
+ x: [true],
+ yLeft: [true],
+ yRight: [true],
+ });
});
it('should generate an expression without x accessor', () => {
@@ -166,6 +171,7 @@ describe('#toExpression', () => {
expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('d');
expect(expression.chain[0].arguments.xTitle).toEqual(['']);
expect(expression.chain[0].arguments.yTitle).toEqual(['']);
+ expect(expression.chain[0].arguments.yRightTitle).toEqual(['']);
expect(
(expression.chain[0].arguments.layers[0] as Ast).chain[0].arguments.columnToLabel
).toEqual([
@@ -198,7 +204,8 @@ describe('#toExpression', () => {
(expression.chain[0].arguments.tickLabelsVisibilitySettings[0] as Ast).chain[0].arguments
).toEqual({
x: [true],
- y: [true],
+ yLeft: [true],
+ yRight: [true],
});
});
@@ -223,7 +230,8 @@ describe('#toExpression', () => {
(expression.chain[0].arguments.gridlinesVisibilitySettings[0] as Ast).chain[0].arguments
).toEqual({
x: [true],
- y: [true],
+ yLeft: [true],
+ yRight: [true],
});
});
});
diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts
index f64624776186d..df8d571a1fdf8 100644
--- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts
@@ -99,6 +99,7 @@ export const buildExpression = (
arguments: {
xTitle: [state.xTitle || ''],
yTitle: [state.yTitle || ''],
+ yRightTitle: [state.yRightTitle || ''],
legend: [
{
type: 'expression',
@@ -118,8 +119,22 @@ export const buildExpression = (
},
],
fittingFunction: [state.fittingFunction || 'None'],
- showXAxisTitle: [state.showXAxisTitle ?? true],
- showYAxisTitle: [state.showYAxisTitle ?? true],
+ axisTitlesVisibilitySettings: [
+ {
+ type: 'expression',
+ chain: [
+ {
+ type: 'function',
+ function: 'lens_xy_axisTitlesVisibilityConfig',
+ arguments: {
+ x: [state?.axisTitlesVisibilitySettings?.x ?? true],
+ yLeft: [state?.axisTitlesVisibilitySettings?.yLeft ?? true],
+ yRight: [state?.axisTitlesVisibilitySettings?.yRight ?? true],
+ },
+ },
+ ],
+ },
+ ],
tickLabelsVisibilitySettings: [
{
type: 'expression',
@@ -129,7 +144,8 @@ export const buildExpression = (
function: 'lens_xy_tickLabelsConfig',
arguments: {
x: [state?.tickLabelsVisibilitySettings?.x ?? true],
- y: [state?.tickLabelsVisibilitySettings?.y ?? true],
+ yLeft: [state?.tickLabelsVisibilitySettings?.yLeft ?? true],
+ yRight: [state?.tickLabelsVisibilitySettings?.yRight ?? true],
},
},
],
@@ -144,7 +160,8 @@ export const buildExpression = (
function: 'lens_xy_gridlinesConfig',
arguments: {
x: [state?.gridlinesVisibilitySettings?.x ?? true],
- y: [state?.gridlinesVisibilitySettings?.y ?? true],
+ yLeft: [state?.gridlinesVisibilitySettings?.yLeft ?? true],
+ yRight: [state?.gridlinesVisibilitySettings?.yRight ?? true],
},
},
],
diff --git a/x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx b/x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx
new file mode 100644
index 0000000000000..cdbec3fd5d6ae
--- /dev/null
+++ b/x-pack/plugins/lens/public/xy_visualization/tooltip_wrapper.tsx
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiToolTip } from '@elastic/eui';
+
+export interface TooltipWrapperProps {
+ tooltipContent: string;
+ condition: boolean;
+}
+
+export const TooltipWrapper: React.FunctionComponent = ({
+ children,
+ condition,
+ tooltipContent,
+}) => {
+ return (
+ <>
+ {condition ? (
+
+ <>{children}>
+
+ ) : (
+ children
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts
index 8438b1f27dd0d..185fa20f169ee 100644
--- a/x-pack/plugins/lens/public/xy_visualization/types.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/types.ts
@@ -80,7 +80,8 @@ export const legendConfig: ExpressionFunctionDefinition<
export interface AxesSettingsConfig {
x: boolean;
- y: boolean;
+ yLeft: boolean;
+ yRight: boolean;
}
type TickLabelsConfigResult = AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' };
@@ -103,10 +104,16 @@ export const tickLabelsConfig: ExpressionFunctionDefinition<
defaultMessage: 'Specifies whether or not the tick labels of the x-axis are visible.',
}),
},
- y: {
+ yLeft: {
types: ['boolean'],
- help: i18n.translate('xpack.lens.xyChart.yAxisTickLabels.help', {
- defaultMessage: 'Specifies whether or not the tick labels of the y-axis are visible.',
+ help: i18n.translate('xpack.lens.xyChart.yLeftAxisTickLabels.help', {
+ defaultMessage: 'Specifies whether or not the tick labels of the left y-axis are visible.',
+ }),
+ },
+ yRight: {
+ types: ['boolean'],
+ help: i18n.translate('xpack.lens.xyChart.yRightAxisTickLabels.help', {
+ defaultMessage: 'Specifies whether or not the tick labels of the right y-axis are visible.',
}),
},
},
@@ -138,10 +145,16 @@ export const gridlinesConfig: ExpressionFunctionDefinition<
defaultMessage: 'Specifies whether or not the gridlines of the x-axis are visible.',
}),
},
- y: {
+ yLeft: {
+ types: ['boolean'],
+ help: i18n.translate('xpack.lens.xyChart.yLeftAxisgridlines.help', {
+ defaultMessage: 'Specifies whether or not the gridlines of the left y-axis are visible.',
+ }),
+ },
+ yRight: {
types: ['boolean'],
- help: i18n.translate('xpack.lens.xyChart.yAxisgridlines.help', {
- defaultMessage: 'Specifies whether or not the gridlines of the y-axis are visible.',
+ help: i18n.translate('xpack.lens.xyChart.yRightAxisgridlines.help', {
+ defaultMessage: 'Specifies whether or not the gridlines of the right y-axis are visible.',
}),
},
},
@@ -153,6 +166,49 @@ export const gridlinesConfig: ExpressionFunctionDefinition<
},
};
+type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & {
+ type: 'lens_xy_axisTitlesVisibilityConfig';
+};
+
+export const axisTitlesVisibilityConfig: ExpressionFunctionDefinition<
+ 'lens_xy_axisTitlesVisibilityConfig',
+ null,
+ AxesSettingsConfig,
+ AxisTitlesVisibilityConfigResult
+> = {
+ name: 'lens_xy_axisTitlesVisibilityConfig',
+ aliases: [],
+ type: 'lens_xy_axisTitlesVisibilityConfig',
+ help: `Configure the xy chart's axis titles appearance`,
+ inputTypes: ['null'],
+ args: {
+ x: {
+ types: ['boolean'],
+ help: i18n.translate('xpack.lens.xyChart.xAxisTitle.help', {
+ defaultMessage: 'Specifies whether or not the title of the x-axis are visible.',
+ }),
+ },
+ yLeft: {
+ types: ['boolean'],
+ help: i18n.translate('xpack.lens.xyChart.yLeftAxisTitle.help', {
+ defaultMessage: 'Specifies whether or not the title of the left y-axis are visible.',
+ }),
+ },
+ yRight: {
+ types: ['boolean'],
+ help: i18n.translate('xpack.lens.xyChart.yRightAxisTitle.help', {
+ defaultMessage: 'Specifies whether or not the title of the right y-axis are visible.',
+ }),
+ },
+ },
+ fn: function fn(input: unknown, args: AxesSettingsConfig) {
+ return {
+ type: 'lens_xy_axisTitlesVisibilityConfig',
+ ...args,
+ };
+ },
+};
+
interface AxisConfig {
title: string;
hide?: boolean;
@@ -329,11 +385,13 @@ export type LayerArgs = LayerConfig & {
export interface XYArgs {
xTitle: string;
yTitle: string;
+ yRightTitle: string;
legend: LegendConfig & { type: 'lens_xy_legendConfig' };
layers: LayerArgs[];
fittingFunction?: FittingFunction;
- showXAxisTitle?: boolean;
- showYAxisTitle?: boolean;
+ axisTitlesVisibilitySettings?: AxesSettingsConfig & {
+ type: 'lens_xy_axisTitlesVisibilityConfig';
+ };
tickLabelsVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' };
gridlinesVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' };
}
@@ -346,8 +404,8 @@ export interface XYState {
layers: LayerConfig[];
xTitle?: string;
yTitle?: string;
- showXAxisTitle?: boolean;
- showYAxisTitle?: boolean;
+ yRightTitle?: string;
+ axisTitlesVisibilitySettings?: AxesSettingsConfig;
tickLabelsVisibilitySettings?: AxesSettingsConfig;
gridlinesVisibilitySettings?: AxesSettingsConfig;
}
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss
index c353f3f370ee5..5b14fca78e65d 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss
@@ -1,3 +1,3 @@
.lnsXyToolbar__popover {
- width: 400px;
-}
+ width: 320px;
+}
\ No newline at end of file
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx
index 89a2574026ced..7e2e8f0453588 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx
@@ -8,6 +8,8 @@ import React from 'react';
import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/enzyme_helpers';
import { EuiButtonGroupProps, EuiSuperSelect, EuiButtonGroup } from '@elastic/eui';
import { LayerContextMenu, XyToolbar } from './xy_config_panel';
+import { ToolbarPopover } from '../shared_components';
+import { AxisSettingsPopover } from './axis_settings_popover';
import { FramePublicAPI } from '../types';
import { State } from './types';
import { Position } from '@elastic/charts';
@@ -113,7 +115,7 @@ describe('XY Config panels', () => {
expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry');
});
- it('should disable the select if there is no area or line series', () => {
+ it('should disable the popover if there is no area or line series', () => {
const state = testState();
const component = shallow(
{
/>
);
- expect(component.find(EuiSuperSelect).prop('disabled')).toEqual(true);
+ expect(component.find(ToolbarPopover).at(0).prop('isDisabled')).toEqual(true);
});
- it('should show the values of the X and Y axes titles on the corresponding input text', () => {
+ it('should disable the popover if there is no right axis', () => {
+ const state = testState();
+ const component = shallow();
+
+ expect(component.find(AxisSettingsPopover).at(2).prop('isDisabled')).toEqual(true);
+ });
+
+ it('should enable the popover if there is right axis', () => {
const state = testState();
const component = shallow(
{
setState={jest.fn()}
state={{
...state,
- xTitle: 'My custom X axis title',
- yTitle: 'My custom Y axis title',
+ layers: [{ ...state.layers[0], yConfig: [{ axisMode: 'right', forAccessor: 'bar' }] }],
}}
/>
);
- expect(component.find('[data-test-subj="lnsXAxisTitle"]').prop('value')).toBe(
- 'My custom X axis title'
- );
- expect(component.find('[data-test-subj="lnsYAxisTitle"]').prop('value')).toBe(
- 'My custom Y axis title'
- );
+ expect(component.find(AxisSettingsPopover).at(2).prop('isDisabled')).toEqual(false);
});
- it('should disable the input texts if the switch is off', () => {
+ it('should render the AxisSettingsPopover 3 times', () => {
const state = testState();
const component = shallow(
{
setState={jest.fn()}
state={{
...state,
- showXAxisTitle: false,
- showYAxisTitle: false,
+ layers: [{ ...state.layers[0], yConfig: [{ axisMode: 'right', forAccessor: 'foo' }] }],
}}
/>
);
- expect(component.find('[data-test-subj="lnsXAxisTitle"]').prop('disabled')).toBe(true);
- expect(component.find('[data-test-subj="lnsYAxisTitle"]').prop('disabled')).toBe(true);
- });
-
- it('has the tick labels buttons enabled', () => {
- const state = testState();
- const component = shallow();
-
- const options = component
- .find('[data-test-subj="lnsTickLabelsSettings"]')
- .prop('options') as EuiButtonGroupProps['options'];
-
- expect(options!.map(({ label }) => label)).toEqual(['X-axis', 'Y-axis']);
-
- const selections = component
- .find('[data-test-subj="lnsTickLabelsSettings"]')
- .prop('idToSelectedMap');
-
- expect(selections!).toEqual({ x: true, y: true });
- });
-
- it('has the gridlines buttons enabled', () => {
- const state = testState();
- const component = shallow();
-
- const selections = component
- .find('[data-test-subj="lnsGridlinesSettings"]')
- .prop('idToSelectedMap');
-
- expect(selections!).toEqual({ x: true, y: true });
+ expect(component.find(AxisSettingsPopover).length).toEqual(3);
});
});
});
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
index 62fd6e013f20d..bc98bf53d9f12 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
@@ -5,7 +5,7 @@
*/
import './xy_config_panel.scss';
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { Position } from '@elastic/charts';
import { debounce } from 'lodash';
@@ -15,19 +15,13 @@ import {
EuiFlexItem,
EuiSuperSelect,
EuiFormRow,
- EuiPopover,
EuiText,
- EuiSelect,
htmlIdGenerator,
EuiForm,
EuiColorPicker,
EuiColorPickerProps,
EuiToolTip,
EuiIcon,
- EuiFieldText,
- EuiSwitch,
- EuiHorizontalRule,
- EuiTitle,
} from '@elastic/eui';
import {
VisualizationLayerWidgetProps,
@@ -38,9 +32,13 @@ import { State, SeriesType, visualizationTypes, YAxisMode, AxesSettingsConfig }
import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers';
import { trackUiEvent } from '../lens_ui_telemetry';
import { fittingFunctionDefinitions } from './fitting_functions';
-import { ToolbarButton } from '../toolbar_button';
+import { ToolbarPopover, LegendSettingsPopover } from '../shared_components';
+import { AxisSettingsPopover } from './axis_settings_popover';
+import { TooltipWrapper } from './tooltip_wrapper';
+import { getAxesConfiguration } from './axes_configuration';
type UnwrapArray = T extends Array ? P : T;
+type AxesSettingsConfigKeys = keyof AxesSettingsConfig;
function updateLayer(state: State, layer: UnwrapArray, index: number): State {
const newLayers = [...state.layers];
@@ -57,21 +55,21 @@ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label:
id: `xy_legend_auto`,
value: 'auto',
label: i18n.translate('xpack.lens.xyChart.legendVisibility.auto', {
- defaultMessage: 'auto',
+ defaultMessage: 'Auto',
}),
},
{
id: `xy_legend_show`,
value: 'show',
label: i18n.translate('xpack.lens.xyChart.legendVisibility.show', {
- defaultMessage: 'show',
+ defaultMessage: 'Show',
}),
},
{
id: `xy_legend_hide`,
value: 'hide',
label: i18n.translate('xpack.lens.xyChart.legendVisibility.hide', {
- defaultMessage: 'hide',
+ defaultMessage: 'Hide',
}),
},
];
@@ -120,86 +118,25 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps) {
}
export function XyToolbar(props: VisualizationToolbarProps) {
- const axes = [
- {
- id: 'x',
- label: 'X-axis',
- },
- {
- id: 'y',
- label: 'Y-axis',
- },
- ];
+ const { state, setState } = props;
- const { frame, state, setState } = props;
-
- const [open, setOpen] = useState(false);
const hasNonBarSeries = state?.layers.some(({ seriesType }) =>
['area_stacked', 'area', 'line'].includes(seriesType)
);
- const [xAxisTitle, setXAxisTitle] = useState(state?.xTitle);
- const [yAxisTitle, setYAxisTitle] = useState(state?.yTitle);
-
- const xyTitles = useCallback(() => {
- const defaults = {
- xTitle: xAxisTitle,
- yTitle: yAxisTitle,
- };
- const layer = state?.layers[0];
- if (!layer || !layer.accessors.length) {
- return defaults;
- }
- const datasource = frame.datasourceLayers[layer.layerId];
- if (!datasource) {
- return defaults;
- }
- const x = layer.xAccessor ? datasource.getOperationForColumnId(layer.xAccessor) : null;
- const y = layer.accessors[0] ? datasource.getOperationForColumnId(layer.accessors[0]) : null;
-
- return {
- xTitle: defaults.xTitle || x?.label,
- yTitle: defaults.yTitle || y?.label,
- };
- /* We want this callback to run only if open changes its state. What we want to accomplish here is to give the user a better UX.
- By default these input fields have the axis legends. If the user changes the input text, the axis legends should also change.
- BUT if the user cleans up the input text, it should remain empty until the user closes and reopens the panel.
- In that case, the default axes legend should appear. */
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [open]);
-
- useEffect(() => {
- const {
- xTitle,
- yTitle,
- }: { xTitle: string | undefined; yTitle: string | undefined } = xyTitles();
- setXAxisTitle(xTitle);
- setYAxisTitle(yTitle);
- }, [xyTitles]);
-
- const onXTitleChange = (value: string): void => {
- setXAxisTitle(value);
- setState({ ...state, xTitle: value });
- };
-
- const onYTitleChange = (value: string): void => {
- setYAxisTitle(value);
- setState({ ...state, yTitle: value });
- };
-
- type AxesSettingsConfigKeys = keyof AxesSettingsConfig;
+ const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false;
+ const axisGroups = getAxesConfiguration(state?.layers, shouldRotate);
const tickLabelsVisibilitySettings = {
x: state?.tickLabelsVisibilitySettings?.x ?? true,
- y: state?.tickLabelsVisibilitySettings?.y ?? true,
+ yLeft: state?.tickLabelsVisibilitySettings?.yLeft ?? true,
+ yRight: state?.tickLabelsVisibilitySettings?.yRight ?? true,
};
-
- const onTickLabelsVisibilitySettingsChange = (optionId: string): void => {
- const id = optionId as AxesSettingsConfigKeys;
+ const onTickLabelsVisibilitySettingsChange = (optionId: AxesSettingsConfigKeys): void => {
const newTickLabelsVisibilitySettings = {
...tickLabelsVisibilitySettings,
...{
- [id]: !tickLabelsVisibilitySettings[id],
+ [optionId]: !tickLabelsVisibilitySettings[optionId],
},
};
setState({
@@ -210,15 +147,15 @@ export function XyToolbar(props: VisualizationToolbarProps) {
const gridlinesVisibilitySettings = {
x: state?.gridlinesVisibilitySettings?.x ?? true,
- y: state?.gridlinesVisibilitySettings?.y ?? true,
+ yLeft: state?.gridlinesVisibilitySettings?.yLeft ?? true,
+ yRight: state?.gridlinesVisibilitySettings?.yRight ?? true,
};
- const onGridlinesVisibilitySettingsChange = (optionId: string): void => {
- const id = optionId as AxesSettingsConfigKeys;
+ const onGridlinesVisibilitySettingsChange = (optionId: AxesSettingsConfigKeys): void => {
const newGridlinesVisibilitySettings = {
...gridlinesVisibilitySettings,
...{
- [id]: !gridlinesVisibilitySettings[id],
+ [optionId]: !gridlinesVisibilitySettings[optionId],
},
};
setState({
@@ -227,6 +164,27 @@ export function XyToolbar(props: VisualizationToolbarProps) {
});
};
+ const axisTitlesVisibilitySettings = {
+ x: state?.axisTitlesVisibilitySettings?.x ?? true,
+ yLeft: state?.axisTitlesVisibilitySettings?.yLeft ?? true,
+ yRight: state?.axisTitlesVisibilitySettings?.yRight ?? true,
+ };
+ const onAxisTitlesVisibilitySettingsChange = (
+ axis: AxesSettingsConfigKeys,
+ checked: boolean
+ ): void => {
+ const newAxisTitlesVisibilitySettings = {
+ ...axisTitlesVisibilitySettings,
+ ...{
+ [axis]: checked,
+ },
+ };
+ setState({
+ ...state,
+ axisTitlesVisibilitySettings: newAxisTitlesVisibilitySettings,
+ });
+ };
+
const legendMode =
state?.legend.isVisible && !state?.legend.showSingleSeries
? 'auto'
@@ -234,243 +192,149 @@ export function XyToolbar(props: VisualizationToolbarProps) {
? 'hide'
: 'show';
return (
-
-
- {
- setOpen(!open);
- }}
- >
- {i18n.translate('xpack.lens.xyChart.settingsLabel', { defaultMessage: 'Settings' })}
-
- }
- isOpen={open}
- closePopover={() => {
- setOpen(false);
- }}
- anchorPosition="downRight"
- >
-
+
+
+
-
- {
- return {
- value: id,
- dropdownDisplay: (
- <>
- {title}
-
- {description}
-
- >
- ),
- inputDisplay: title,
- };
+ setState({ ...state, fittingFunction: value })}
- itemLayoutAlign="top"
- hasDividers
- />
-
-
-
-
- value === legendMode)!.id}
- onChange={(optionId) => {
- const newMode = legendOptions.find(({ id }) => id === optionId)!.value;
- if (newMode === 'auto') {
- setState({
- ...state,
- legend: { ...state.legend, isVisible: true, showSingleSeries: false },
- });
- } else if (newMode === 'show') {
- setState({
- ...state,
- legend: { ...state.legend, isVisible: true, showSingleSeries: true },
- });
- } else if (newMode === 'hide') {
- setState({
- ...state,
- legend: { ...state.legend, isVisible: false, showSingleSeries: false },
- });
- }
- }}
- />
-
-
- {
+ >
+ {
+ return {
+ value: id,
+ dropdownDisplay: (
+ <>
+ {title}
+
+ {description}
+
+ >
+ ),
+ inputDisplay: title,
+ };
+ })}
+ valueOfSelected={state?.fittingFunction || 'None'}
+ onChange={(value) => setState({ ...state, fittingFunction: value })}
+ itemLayoutAlign="top"
+ hasDividers
+ />
+
+
+
+ {
+ const newMode = legendOptions.find(({ id }) => id === optionId)!.value;
+ if (newMode === 'auto') {
setState({
...state,
- legend: { ...state.legend, position: e.target.value as Position },
+ legend: { ...state.legend, isVisible: true, showSingleSeries: false },
});
- }}
- />
-
-
-
- onTickLabelsVisibilitySettingsChange(id)}
- buttonSize="compressed"
- isFullWidth
- type="multi"
- />
-
- {
+ setState({
+ ...state,
+ legend: { ...state.legend, position: id as Position },
+ });
+ }}
+ />
+
+
+
+
+
- onGridlinesVisibilitySettingsChange(id)}
- buttonSize="compressed"
- isFullWidth
- type="multi"
- />
-
-
-
-
- {i18n.translate('xpack.lens.xyChart.axisTitles', { defaultMessage: 'Axis titles' })}
-
-
-
- X-axis
-
-
- setState({ ...state, showXAxisTitle: target.checked })
- }
- checked={state?.showXAxisTitle ?? true}
- />
-
-
+ condition={
+ Object.keys(axisGroups.find((group) => group.groupId === 'left') || {}).length === 0
}
>
- onXTitleChange(target.value)}
- aria-label={i18n.translate('xpack.lens.xyChart.overwriteXaxis', {
- defaultMessage: 'Overwrite X-axis title',
- })}
+ setState({ ...state, yTitle: value })}
+ areTickLabelsVisible={tickLabelsVisibilitySettings.yLeft}
+ toggleTickLabelsVisibility={onTickLabelsVisibilitySettingsChange}
+ areGridlinesVisible={gridlinesVisibilitySettings.yLeft}
+ toggleGridlinesVisibility={onGridlinesVisibilitySettingsChange}
+ isDisabled={
+ Object.keys(axisGroups.find((group) => group.groupId === 'left') || {}).length === 0
+ }
+ isAxisTitleVisible={axisTitlesVisibilitySettings.yLeft}
+ toggleAxisTitleVisibility={onAxisTitlesVisibilitySettingsChange}
/>
-
-
- Y-axis
-
-
- setState({ ...state, showYAxisTitle: target.checked })
- }
- checked={state?.showYAxisTitle ?? true}
- />
-
-
+
+
setState({ ...state, xTitle: value })}
+ areTickLabelsVisible={tickLabelsVisibilitySettings.x}
+ toggleTickLabelsVisibility={onTickLabelsVisibilitySettingsChange}
+ areGridlinesVisible={gridlinesVisibilitySettings.x}
+ toggleGridlinesVisibility={onGridlinesVisibilitySettingsChange}
+ isAxisTitleVisible={axisTitlesVisibilitySettings.x}
+ toggleAxisTitleVisibility={onAxisTitlesVisibilitySettingsChange}
+ />
+ group.groupId === 'right') || {}).length === 0
}
>
- onYTitleChange(target.value)}
- aria-label={i18n.translate('xpack.lens.xyChart.overwriteYaxis', {
- defaultMessage: 'Overwrite Y-axis title',
- })}
+ setState({ ...state, yRightTitle: value })}
+ areTickLabelsVisible={tickLabelsVisibilitySettings.yRight}
+ toggleTickLabelsVisibility={onTickLabelsVisibilitySettingsChange}
+ areGridlinesVisible={gridlinesVisibilitySettings.yRight}
+ toggleGridlinesVisibility={onGridlinesVisibilitySettingsChange}
+ isDisabled={
+ Object.keys(axisGroups.find((group) => group.groupId === 'right') || {}).length ===
+ 0
+ }
+ isAxisTitleVisible={axisTitlesVisibilitySettings.yRight}
+ toggleAxisTitleVisibility={onAxisTitlesVisibilitySettingsChange}
/>
-
-
+
+
);
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx
index c9c27193c437e..1d809f222eb00 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.test.tsx
@@ -215,22 +215,29 @@ const sampleLayer: LayerArgs = {
const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
xTitle: '',
yTitle: '',
+ yRightTitle: '',
legend: {
type: 'lens_xy_legendConfig',
isVisible: false,
position: Position.Top,
},
- showXAxisTitle: true,
- showYAxisTitle: true,
+ axisTitlesVisibilitySettings: {
+ type: 'lens_xy_axisTitlesVisibilityConfig',
+ x: true,
+ yLeft: true,
+ yRight: true,
+ },
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
},
gridlinesVisibilitySettings: {
type: 'lens_xy_gridlinesConfig',
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
},
layers,
});
@@ -291,7 +298,8 @@ describe('xy_expression', () => {
test('tickLabelsConfig produces the correct arguments', () => {
const args: AxesSettingsConfig = {
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
};
const result = tickLabelsConfig.fn(null, args, createMockExecutionContext());
@@ -305,7 +313,8 @@ describe('xy_expression', () => {
test('gridlinesConfig produces the correct arguments', () => {
const args: AxesSettingsConfig = {
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
};
const result = gridlinesConfig.fn(null, args, createMockExecutionContext());
@@ -1417,7 +1426,12 @@ describe('xy_expression', () => {
test('it should set the tickLabel visibility on the x axis if the tick labels is hidden', () => {
const { data, args } = sampleArgs();
- args.tickLabelsVisibilitySettings = { x: false, y: true, type: 'lens_xy_tickLabelsConfig' };
+ args.tickLabelsVisibilitySettings = {
+ x: false,
+ yLeft: true,
+ yRight: true,
+ type: 'lens_xy_tickLabelsConfig',
+ };
const instance = shallow(
{
test('it should set the tickLabel visibility on the y axis if the tick labels is hidden', () => {
const { data, args } = sampleArgs();
- args.tickLabelsVisibilitySettings = { x: true, y: false, type: 'lens_xy_tickLabelsConfig' };
+ args.tickLabelsVisibilitySettings = {
+ x: true,
+ yLeft: false,
+ yRight: false,
+ type: 'lens_xy_tickLabelsConfig',
+ };
const instance = shallow(
{
test('it should set the tickLabel visibility on the x axis if the tick labels is shown', () => {
const { data, args } = sampleArgs();
- args.tickLabelsVisibilitySettings = { x: true, y: true, type: 'lens_xy_tickLabelsConfig' };
+ args.tickLabelsVisibilitySettings = {
+ x: true,
+ yLeft: true,
+ yRight: true,
+ type: 'lens_xy_tickLabelsConfig',
+ };
const instance = shallow(
{
test('it should set the tickLabel visibility on the y axis if the tick labels is shown', () => {
const { data, args } = sampleArgs();
- args.tickLabelsVisibilitySettings = { x: false, y: true, type: 'lens_xy_tickLabelsConfig' };
+ args.tickLabelsVisibilitySettings = {
+ x: false,
+ yLeft: true,
+ yRight: true,
+ type: 'lens_xy_tickLabelsConfig',
+ };
const instance = shallow(
{
const args: XYArgs = {
xTitle: '',
yTitle: '',
+ yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
- y: true,
+ yLeft: true,
+ yRight: true,
},
gridlinesVisibilitySettings: {
type: 'lens_xy_gridlinesConfig',
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
},
layers: [
{
@@ -1635,16 +1667,19 @@ describe('xy_expression', () => {
const args: XYArgs = {
xTitle: '',
yTitle: '',
+ yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
},
gridlinesVisibilitySettings: {
type: 'lens_xy_gridlinesConfig',
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
},
layers: [
{
@@ -1701,16 +1736,19 @@ describe('xy_expression', () => {
const args: XYArgs = {
xTitle: '',
yTitle: '',
+ yRightTitle: '',
legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top },
tickLabelsVisibilitySettings: {
type: 'lens_xy_tickLabelsConfig',
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
},
gridlinesVisibilitySettings: {
type: 'lens_xy_gridlinesConfig',
x: true,
- y: false,
+ yLeft: false,
+ yRight: false,
},
layers: [
{
@@ -1894,7 +1932,12 @@ describe('xy_expression', () => {
test('it should hide the X axis title if the corresponding switch is off', () => {
const { data, args } = sampleArgs();
- args.showXAxisTitle = false;
+ args.axisTitlesVisibilitySettings = {
+ x: false,
+ yLeft: true,
+ yRight: true,
+ type: 'lens_xy_axisTitlesVisibilityConfig',
+ };
const component = shallow(
{
test('it should show the X axis gridlines if the setting is on', () => {
const { data, args } = sampleArgs();
- args.gridlinesVisibilitySettings = { x: true, y: false, type: 'lens_xy_gridlinesConfig' };
+ args.gridlinesVisibilitySettings = {
+ x: true,
+ yLeft: false,
+ yRight: false,
+ type: 'lens_xy_gridlinesConfig',
+ };
const component = shallow(
,
- index: number
+ groupId: string
) => {
- if (index > 0 && args.yTitle) return;
+ const yTitle = groupId === 'right' ? args.yRightTitle : args.yTitle;
return (
- args.yTitle ||
+ yTitle ||
axisSeries
.map(
(series) =>
@@ -322,6 +333,24 @@ export function XYChart({
);
};
+ const getYAxesStyle = (groupId: string) => {
+ const style = {
+ tickLabel: {
+ visible:
+ groupId === 'right'
+ ? tickLabelsVisibilitySettings?.yRight
+ : tickLabelsVisibilitySettings?.yLeft,
+ },
+ axisTitle: {
+ visible:
+ groupId === 'right'
+ ? axisTitlesVisibilitySettings?.yRight
+ : axisTitlesVisibilitySettings?.yLeft,
+ },
+ };
+ return style;
+ };
+
return (
- {yAxesConfiguration.map((axis, index) => (
+ {yAxesConfiguration.map((axis) => (
axis.formatter.convert(d)}
- style={{
- tickLabel: {
- visible: tickLabelsVisibilitySettings?.y,
- },
- axisTitle: {
- visible: showYAxisTitle,
- },
- }}
+ tickFormat={(d) => axis.formatter?.convert(d) || ''}
+ style={getYAxesStyle(axis.groupId)}
/>
))}
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts
index ea5cff80695a3..09a2cc652a9b3 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts
@@ -555,10 +555,9 @@ describe('xy_suggestions', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
fittingFunction: 'None',
- showXAxisTitle: true,
- showYAxisTitle: true,
- gridlinesVisibilitySettings: { x: true, y: true },
- tickLabelsVisibilitySettings: { x: true, y: false },
+ axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false },
preferredSeriesType: 'bar',
layers: [
{
@@ -597,10 +596,9 @@ describe('xy_suggestions', () => {
legend: { isVisible: true, position: 'bottom' },
preferredSeriesType: 'bar',
fittingFunction: 'None',
- showXAxisTitle: true,
- showYAxisTitle: true,
- gridlinesVisibilitySettings: { x: true, y: true },
- tickLabelsVisibilitySettings: { x: true, y: false },
+ axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false },
layers: [
{
accessors: ['price', 'quantity'],
@@ -710,10 +708,9 @@ describe('xy_suggestions', () => {
legend: { isVisible: true, position: 'bottom' },
preferredSeriesType: 'bar',
fittingFunction: 'None',
- showXAxisTitle: true,
- showYAxisTitle: true,
- gridlinesVisibilitySettings: { x: true, y: true },
- tickLabelsVisibilitySettings: { x: true, y: false },
+ axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false },
layers: [
{
accessors: ['price', 'quantity'],
@@ -753,10 +750,9 @@ describe('xy_suggestions', () => {
legend: { isVisible: true, position: 'bottom' },
preferredSeriesType: 'bar',
fittingFunction: 'None',
- showXAxisTitle: true,
- showYAxisTitle: true,
- gridlinesVisibilitySettings: { x: true, y: true },
- tickLabelsVisibilitySettings: { x: true, y: false },
+ axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false },
layers: [
{
accessors: ['price'],
@@ -797,10 +793,9 @@ describe('xy_suggestions', () => {
legend: { isVisible: true, position: 'bottom' },
preferredSeriesType: 'bar',
fittingFunction: 'None',
- showXAxisTitle: true,
- showYAxisTitle: true,
- gridlinesVisibilitySettings: { x: true, y: true },
- tickLabelsVisibilitySettings: { x: true, y: false },
+ axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
+ tickLabelsVisibilitySettings: { x: true, yLeft: false, yRight: false },
layers: [
{
accessors: ['price', 'quantity'],
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts
index 42fc538874b93..e6286523d8e2e 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts
@@ -489,15 +489,21 @@ function buildSuggestion({
fittingFunction: currentState?.fittingFunction || 'None',
xTitle: currentState?.xTitle,
yTitle: currentState?.yTitle,
- showXAxisTitle: currentState?.showXAxisTitle ?? true,
- showYAxisTitle: currentState?.showYAxisTitle ?? true,
+ yRightTitle: currentState?.yRightTitle,
+ axisTitlesVisibilitySettings: currentState?.axisTitlesVisibilitySettings || {
+ x: true,
+ yLeft: true,
+ yRight: true,
+ },
tickLabelsVisibilitySettings: currentState?.tickLabelsVisibilitySettings || {
x: true,
- y: true,
+ yLeft: true,
+ yRight: true,
},
gridlinesVisibilitySettings: currentState?.gridlinesVisibilitySettings || {
x: true,
- y: true,
+ yLeft: true,
+ yRight: true,
},
preferredSeriesType: seriesType,
layers: Object.keys(existingLayer).length ? keptLayers : [...keptLayers, newLayer],
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 68f6bc166cd1d..e54d6739c0600 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -9684,8 +9684,6 @@
"xpack.lens.pieChart.fitInsideOnlyLabel": "内部のみ",
"xpack.lens.pieChart.hiddenNumbersLabel": "グラフから非表示",
"xpack.lens.pieChart.labelPositionLabel": "ラベル位置",
- "xpack.lens.pieChart.legendDisplayLabel": "凡例表示",
- "xpack.lens.pieChart.legendDisplayLegend": "凡例表示",
"xpack.lens.pieChart.nestedLegendLabel": "ネストされた凡例",
"xpack.lens.pieChart.numberLabels": "ラベル値",
"xpack.lens.pieChart.showCategoriesLabel": "内部または外部",
@@ -9711,7 +9709,6 @@
"xpack.lens.xyChart.chartTypeLegend": "チャートタイプ",
"xpack.lens.xyChart.fittingDisabledHelpText": "この設定は折れ線グラフとエリアグラフでのみ適用されます。",
"xpack.lens.xyChart.fittingFunction.help": "欠測値の処理方法を定義",
- "xpack.lens.xyChart.fittingLabel": "欠測値を埋める",
"xpack.lens.xyChart.help": "X/Y チャート",
"xpack.lens.xyChart.isVisible.help": "判例の表示・非表示を指定します。",
"xpack.lens.xyChart.legend.help": "チャートの凡例を構成します。",
@@ -9720,7 +9717,6 @@
"xpack.lens.xyChart.renderer.help": "X/Y チャートを再レンダリング",
"xpack.lens.xyChart.seriesColor.auto": "自動",
"xpack.lens.xyChart.seriesColor.label": "系列色",
- "xpack.lens.xyChart.settingsLabel": "設定",
"xpack.lens.xyChart.splitSeries": "系列を分割",
"xpack.lens.xyChart.title.help": "軸のタイトル",
"xpack.lens.xyChart.xAxisLabel": "X 軸",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index cb43cefdc3655..4c8ccd56c1c01 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -9690,8 +9690,6 @@
"xpack.lens.pieChart.fitInsideOnlyLabel": "仅内部",
"xpack.lens.pieChart.hiddenNumbersLabel": "在图表中隐藏",
"xpack.lens.pieChart.labelPositionLabel": "标签位置",
- "xpack.lens.pieChart.legendDisplayLabel": "图例显示",
- "xpack.lens.pieChart.legendDisplayLegend": "图例显示",
"xpack.lens.pieChart.nestedLegendLabel": "嵌套图例",
"xpack.lens.pieChart.numberLabels": "标签值",
"xpack.lens.pieChart.showCategoriesLabel": "内部或外部",
@@ -9717,7 +9715,6 @@
"xpack.lens.xyChart.chartTypeLegend": "图表类型",
"xpack.lens.xyChart.fittingDisabledHelpText": "此设置仅适用于折线图和非堆叠面积图。",
"xpack.lens.xyChart.fittingFunction.help": "定义处理缺失值的方式",
- "xpack.lens.xyChart.fittingLabel": "填充缺失值",
"xpack.lens.xyChart.help": "X/Y 图表",
"xpack.lens.xyChart.isVisible.help": "指定图例是否可见。",
"xpack.lens.xyChart.legend.help": "配置图表图例。",
@@ -9726,7 +9723,6 @@
"xpack.lens.xyChart.renderer.help": "X/Y 图表呈现器",
"xpack.lens.xyChart.seriesColor.auto": "自动",
"xpack.lens.xyChart.seriesColor.label": "系列颜色",
- "xpack.lens.xyChart.settingsLabel": "设置",
"xpack.lens.xyChart.splitSeries": "拆分序列",
"xpack.lens.xyChart.title.help": "轴标题",
"xpack.lens.xyChart.xAxisLabel": "X 轴",