diff --git a/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts b/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts index 4a79644d7fe54..f5808cee9d000 100644 --- a/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts +++ b/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts @@ -35,9 +35,30 @@ export const metricChart: ExpressionFunctionDefinition< title: { types: ['string'], help: i18n.translate('xpack.lens.metric.title.help', { - defaultMessage: 'The chart title.', + defaultMessage: 'The visualization title.', }), }, + size: { + types: ['string'], + help: i18n.translate('xpack.lens.metric.size.help', { + defaultMessage: 'The visualization text size.', + }), + default: 'xl', + }, + titlePosition: { + types: ['string'], + help: i18n.translate('xpack.lens.metric.titlePosition.help', { + defaultMessage: 'The visualization title position.', + }), + default: 'bottom', + }, + textAlign: { + types: ['string'], + help: i18n.translate('xpack.lens.metric.textAlignPosition.help', { + defaultMessage: 'The visualization text alignment position.', + }), + default: 'center', + }, description: { types: ['string'], help: '', diff --git a/x-pack/plugins/lens/common/expressions/metric_chart/types.ts b/x-pack/plugins/lens/common/expressions/metric_chart/types.ts index 8a52506a83ec8..00f7d7454061f 100644 --- a/x-pack/plugins/lens/common/expressions/metric_chart/types.ts +++ b/x-pack/plugins/lens/common/expressions/metric_chart/types.ts @@ -18,6 +18,9 @@ export interface MetricState { layerType: LayerType; colorMode?: ColorMode; palette?: PaletteOutput; + titlePosition?: 'top' | 'bottom'; + size?: string; + textAlign?: 'left' | 'right' | 'center'; } export interface MetricConfig extends Omit { diff --git a/x-pack/plugins/lens/public/metric_visualization/auto_scale.test.tsx b/x-pack/plugins/lens/public/metric_visualization/auto_scale.test.tsx index b84ad685e81e6..b7584ffa9eb7e 100644 --- a/x-pack/plugins/lens/public/metric_visualization/auto_scale.test.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/auto_scale.test.tsx @@ -53,6 +53,7 @@ describe('AutoScale', () => { style="display:flex;justify-content:center;align-items:center;max-width:100%;max-height:100%;overflow:hidden;line-height:1.5" >

diff --git a/x-pack/plugins/lens/public/metric_visualization/auto_scale.tsx b/x-pack/plugins/lens/public/metric_visualization/auto_scale.tsx index 9047937093134..7e47405f9258a 100644 --- a/x-pack/plugins/lens/public/metric_visualization/auto_scale.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/auto_scale.tsx @@ -7,11 +7,16 @@ import React from 'react'; import { throttle } from 'lodash'; +import classNames from 'classnames'; import { EuiResizeObserver } from '@elastic/eui'; +import { MetricState } from '../../common/expressions'; interface Props extends React.HTMLAttributes { children: React.ReactNode | React.ReactNode[]; minScale?: number; + size?: MetricState['size']; + titlePosition?: MetricState['titlePosition']; + textAlign?: MetricState['textAlign']; } interface State { @@ -56,7 +61,7 @@ export class AutoScale extends React.Component { }; render() { - const { children, minScale, ...rest } = this.props; + const { children, minScale, size, textAlign, titlePosition, ...rest } = this.props; const { scale } = this.state; const style = this.props.style || {}; @@ -85,6 +90,12 @@ export class AutoScale extends React.Component { style={{ transform: `scale(${scale})`, }} + className={classNames('lnsMetricExpression__containerScale', { + alignLeft: textAlign === 'left', + alignRight: textAlign === 'right', + alignCenter: textAlign === 'center', + [`titleSize${size?.toUpperCase()}`]: Boolean(size), + })} > {children}

diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.scss b/x-pack/plugins/lens/public/metric_visualization/expression.scss index bf72015637356..fdd22690207fa 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.scss +++ b/x-pack/plugins/lens/public/metric_visualization/expression.scss @@ -8,14 +8,80 @@ text-align: center; .lnsMetricExpression__value { - font-size: $euiSizeXXL * 2; + font-size: $euiFontSizeXXL * 2; font-weight: $euiFontWeightSemiBold; border-radius: $euiBorderRadius; - padding: 0 $euiSize; } .lnsMetricExpression__title { - font-size: $euiSizeXL; + font-size: $euiFontSizeXXL; color: $euiTextColor; + &.reverseOrder { + order: 1; + } } -} \ No newline at end of file + + .lnsMetricExpression__containerScale { + display: flex; + align-items: center; + flex-direction: column; + &.alignLeft { + align-items: start; + } + &.alignRight { + align-items: end; + } + &.alignCenter { + align-items: center; + } + &.titleSizeXS { + .lnsMetricExpression__title { + font-size: $euiFontSizeXS; + } + .lnsMetricExpression__value { + font-size: $euiFontSizeXS * 2; + } + } + &.titleSizeS { + .lnsMetricExpression__title { + font-size: $euiFontSizeS; + } + .lnsMetricExpression__value { + font-size: $euiFontSizeM * 2.25; + } + } + &.titleSizeM { + .lnsMetricExpression__title { + font-size: $euiFontSizeM; + } + .lnsMetricExpression__value { + font-size: $euiFontSizeL * 2; + } + } + &.titleSizeL { + .lnsMetricExpression__title { + font-size: $euiFontSizeL; + } + .lnsMetricExpression__value { + font-size: $euiFontSizeXL * 2; + } + } + &.titleSizeXL { + .lnsMetricExpression__title { + font-size: $euiFontSizeXL; + } + .lnsMetricExpression__value { + font-size: $euiFontSizeXXL * 2; + } + } + + &.titleSizeXXL { + .lnsMetricExpression__title { + font-size: $euiFontSizeXXL; + } + .lnsMetricExpression__value { + font-size: $euiFontSizeXXL * 3; + } + } + } +} diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx b/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx index 3ec2f4c285c4e..5d5369f4e62d0 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx @@ -99,18 +99,18 @@ describe('metric_expression', () => { -
- 3 -
My fanci metric chart
+
+ 3 +
`); @@ -137,18 +137,18 @@ describe('metric_expression', () => { -
- last -
My fanci metric chart
+
+ last +
`); @@ -174,18 +174,18 @@ describe('metric_expression', () => { -
- 3 -
My fanci metric chart
+
+ 3 +
`); @@ -292,18 +292,18 @@ describe('metric_expression', () => { -
- 0 -
My fanci metric chart
+
+ 0 +
`); diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.tsx b/x-pack/plugins/lens/public/metric_visualization/expression.tsx index d84abcc0b1005..c10e337126c46 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/expression.tsx @@ -9,6 +9,7 @@ import './expression.scss'; import { I18nProvider } from '@kbn/i18n-react'; import React from 'react'; import ReactDOM from 'react-dom'; +import classNames from 'classnames'; import { IUiSettingsClient, ThemeServiceStart } from 'kibana/public'; import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; import type { @@ -109,7 +110,7 @@ export function MetricChart({ formatFactory, uiSettings, }: MetricChartProps & { formatFactory: FormatFactory; uiSettings: IUiSettingsClient }) { - const { metricTitle, accessor, mode, colorMode, palette } = args; + const { metricTitle, accessor, mode, colorMode, palette, titlePosition, textAlign, size } = args; const firstTable = Object.values(data.tables)[0]; const getEmptyState = () => ( @@ -144,19 +145,21 @@ export function MetricChart({ return ( - -
- {value} -
+ {mode === 'full' && (
{metricTitle}
)} +
+ {value} +
); diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/align_options.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/align_options.tsx new file mode 100644 index 0000000000000..d97aa08661005 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/align_options.tsx @@ -0,0 +1,57 @@ +/* + * 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 { EuiButtonGroup } from '@elastic/eui'; +import { MetricState } from '../../../common/expressions'; + +export interface TitlePositionProps { + state: MetricState; + setState: (newState: MetricState) => void; +} + +const alignButtonIcons = [ + { + id: `left`, + label: i18n.translate('xpack.lens.metricChart.alignLabel.left', { + defaultMessage: 'Align left', + }), + iconType: 'editorAlignLeft', + }, + { + id: `center`, + label: i18n.translate('xpack.lens.metricChart.alignLabel.center', { + defaultMessage: 'Align center', + }), + iconType: 'editorAlignCenter', + }, + { + id: `right`, + label: i18n.translate('xpack.lens.metricChart.alignLabel.right', { + defaultMessage: 'Align right', + }), + iconType: 'editorAlignRight', + }, +]; + +export const AlignOptions: React.FC = ({ state, setState }) => { + return ( + { + setState({ ...state, textAlign: id as MetricState['textAlign'] }); + }} + isIconOnly + buttonSize="compressed" + /> + ); +}; diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/appearance_options_popover.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/appearance_options_popover.tsx new file mode 100644 index 0000000000000..973c1e0eedf39 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/appearance_options_popover.tsx @@ -0,0 +1,46 @@ +/* + * 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 { ToolbarPopover, TooltipWrapper } from '../../shared_components'; +import { TitlePositionOptions } from './title_position_option'; +import { FramePublicAPI } from '../../types'; +import { MetricState } from '../../../common/expressions'; +import { TextFormattingOptions } from './text_formatting_options'; + +export interface VisualOptionsPopoverProps { + state: MetricState; + setState: (newState: MetricState) => void; + datasourceLayers: FramePublicAPI['datasourceLayers']; +} + +export const AppearanceOptionsPopover: React.FC = ({ + state, + setState, +}) => { + return ( + + + + + + + ); +}; diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/index.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/index.tsx new file mode 100644 index 0000000000000..5a6566a863a3d --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/index.tsx @@ -0,0 +1,35 @@ +/* + * 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, { memo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, htmlIdGenerator } from '@elastic/eui'; +import type { VisualizationToolbarProps } from '../../types'; + +import { AppearanceOptionsPopover } from './appearance_options_popover'; +import { MetricState } from '../../../common/expressions'; + +export const MetricToolbar = memo(function MetricToolbar( + props: VisualizationToolbarProps +) { + const { state, setState, frame } = props; + + return ( + + + + + + + + ); +}); + +export const idPrefix = htmlIdGenerator()(); diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/size_options.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/size_options.tsx new file mode 100644 index 0000000000000..d33d72751a203 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/size_options.tsx @@ -0,0 +1,98 @@ +/* + * 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 { EuiButtonIcon, EuiSuperSelect } from '@elastic/eui'; +import { MetricState } from '../../../common/expressions'; + +export interface TitlePositionProps { + state: MetricState; + setState: (newState: MetricState) => void; +} + +const titleSizes = [ + { + id: 'xs', + label: i18n.translate('xpack.lens.metricChart.metricSize.extraSmall', { + defaultMessage: 'XS', + }), + }, + { + id: 's', + label: i18n.translate('xpack.lens.metricChart.metricSize.small', { + defaultMessage: 'S', + }), + }, + { + id: 'm', + label: i18n.translate('xpack.lens.metricChart.metricSize.medium', { + defaultMessage: 'M', + }), + }, + { + id: 'l', + label: i18n.translate('xpack.lens.metricChart.metricSize.large', { + defaultMessage: 'L', + }), + }, + { + id: 'xl', + label: i18n.translate('xpack.lens.metricChart.metricSize.extraLarge', { + defaultMessage: 'XL', + }), + }, + { + id: 'xxl', + label: i18n.translate('xpack.lens.metricChart.metricSize.xxl', { + defaultMessage: 'XXL', + }), + }, +]; + +export const SizeOptions: React.FC = ({ state, setState }) => { + const currSizeIndex = titleSizes.findIndex((size) => size.id === (state.size || 'xl')); + + const changeSize = (change: number) => { + setState({ ...state, size: titleSizes[currSizeIndex + change].id }); + }; + + return ( + changeSize(1)} + isDisabled={currSizeIndex === titleSizes.length - 1} + /> + } + prepend={ + changeSize(-1)} + isDisabled={currSizeIndex === 0} + /> + } + data-test-subj="lnsMetricSizeSelect" + compressed + options={titleSizes.map((position) => { + return { + value: position.id, + dropdownDisplay: position.label, + inputDisplay: position.label, + }; + })} + valueOfSelected={state.size ?? 'xl'} + onChange={(value) => { + setState({ ...state, size: value }); + }} + itemLayoutAlign="top" + hasDividers + fullWidth + /> + ); +}; diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/text_formatting_options.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/text_formatting_options.tsx new file mode 100644 index 0000000000000..9215d27ebb874 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/text_formatting_options.tsx @@ -0,0 +1,43 @@ +/* + * 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, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { MetricState } from '../../../common/expressions'; +import { SizeOptions } from './size_options'; +import { AlignOptions } from './align_options'; + +export interface TitlePositionProps { + state: MetricState; + setState: (newState: MetricState) => void; +} + +export const TextFormattingOptions: React.FC = ({ state, setState }) => { + return ( + + {i18n.translate('xpack.lens.metricChart.textFormattingLabel', { + defaultMessage: 'Text formatting', + })} + + } + > + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/title_position_option.tsx b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/title_position_option.tsx new file mode 100644 index 0000000000000..c35567bb69537 --- /dev/null +++ b/x-pack/plugins/lens/public/metric_visualization/metric_config_panel/title_position_option.tsx @@ -0,0 +1,49 @@ +/* + * 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 { EuiButtonGroup, EuiFormRow } from '@elastic/eui'; +import { MetricState } from '../../../common/expressions'; + +export interface TitlePositionProps { + state: MetricState; + setState: (newState: MetricState) => void; +} + +const titlePositions = [ + { id: 'top', label: 'Top' }, + { id: 'bottom', label: 'Bottom' }, +]; + +export const TitlePositionOptions: React.FC = ({ state, setState }) => { + return ( + + {i18n.translate('xpack.lens.metricChart.titlePositionLabel', { + defaultMessage: 'Title position', + })} + + } + > + { + setState({ ...state, titlePosition: value as MetricState['titlePosition'] }); + }} + buttonSize="compressed" + /> + + ); +}; diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts b/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts index bba08c1aa2442..fedd58f3b0807 100644 --- a/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts @@ -300,9 +300,18 @@ describe('metric_visualization', () => { "full", ], "palette": Array [], + "size": Array [ + "xl", + ], + "textAlign": Array [ + "center", + ], "title": Array [ "", ], + "titlePosition": Array [ + "bottom", + ], }, "function": "lens_metric_chart", "type": "function", diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx index d760e489323c6..ad8c6123562e6 100644 --- a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx @@ -21,6 +21,7 @@ import type { MetricConfig, MetricState } from '../../common/expressions'; import { layerTypes } from '../../common'; import { CUSTOM_PALETTE, shiftPalette } from '../shared_components'; import { MetricDimensionEditor } from './dimension_editor'; +import { MetricToolbar } from './metric_config_panel'; const toExpression = ( paletteService: PaletteRegistry, @@ -59,6 +60,9 @@ const toExpression = ( function: 'lens_metric_chart', arguments: { title: [attributes?.title || ''], + size: [state?.size || 'xl'], + titlePosition: [state?.titlePosition || 'bottom'], + textAlign: [state?.textAlign || 'center'], description: [attributes?.description || ''], metricTitle: [operation?.label || ''], accessor: [state.accessor], @@ -189,6 +193,17 @@ export const getMetricVisualization = ({ return { ...prevState, accessor: undefined, colorMode: ColorMode.None, palette: undefined }; }, + renderToolbar(domElement, props) { + render( + + + + + , + domElement + ); + }, + renderDimensionEditor(domElement, props) { render(