Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lens] Add value labels to Heatmap #106406

Merged
merged 11 commits into from
Oct 25, 2021
3 changes: 1 addition & 2 deletions x-pack/plugins/lens/common/expressions/xy_chart/xy_args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import type { LayerArgs } from './layer_config';
import type { LegendConfigResult } from './legend_config';
import type { TickLabelsConfigResult } from './tick_labels_config';
import type { LabelsOrientationConfigResult } from './labels_orientation_config';

export type ValueLabelConfig = 'hide' | 'inside' | 'outside';
import type { ValueLabelConfig } from '../../types';

export type XYCurveType = 'LINEAR' | 'CURVE_MONOTONE_X';

Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/lens/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ export interface CustomPaletteParams {
export type RequiredPaletteParamTypes = Required<CustomPaletteParams>;

export type LayerType = 'data' | 'referenceLine';

// Shared by XY Chart and Heatmap as for now
export type ValueLabelConfig = 'hide' | 'inside' | 'outside';
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ export const HeatmapComponent: FC<HeatmapRenderProps> = ({
maxHeight: 'fill',
label: {
visible: args.gridConfig.isCellLabelVisible ?? false,
minFontSize: 8,
maxFontSize: 18,
useGlobalMinFontSize: true, // override the min if there's a different directive upstream
},
border: {
strokeWidth: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Position } from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import type { VisualizationToolbarProps } from '../types';
import { LegendSettingsPopover } from '../shared_components';
import { LegendSettingsPopover, ToolbarPopover, ValueLabelsSettings } from '../shared_components';
import type { HeatmapVisualizationState } from './types';

const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [
Expand All @@ -37,11 +37,29 @@ export const HeatmapToolbar = memo(
const legendMode = state.legend.isVisible ? 'show' : 'hide';

return (
<EuiFlexGroup gutterSize="m" justifyContent="spaceBetween">
<EuiFlexGroup gutterSize="m" justifyContent="spaceBetween" responsive={false}>
<EuiFlexItem>
<EuiFlexGroup gutterSize="none" responsive={false}>
<ToolbarPopover
title={i18n.translate('xpack.lens.shared.curveLabel', {
defaultMessage: 'Visual options',
})}
type="visualOptions"
groupPosition="left"
buttonDataTestSubj="lnsVisualOptionsButton"
>
<ValueLabelsSettings
valueLabels={state?.gridConfig.isCellLabelVisible ? 'inside' : 'hide'}
onValueLabelChange={(newMode) => {
setState({
...state,
gridConfig: { ...state.gridConfig, isCellLabelVisible: newMode === 'inside' },
});
}}
/>
</ToolbarPopover>
<LegendSettingsPopover
groupPosition={'none'}
groupPosition={'right'}
legendOptions={legendOptions}
mode={legendMode}
onDisplayChange={(optionId) => {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/lens/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export type {
XYLayerConfig,
LegendConfig,
SeriesType,
ValueLabelConfig,
YAxisMode,
XYCurveType,
YConfig,
} from '../common/expressions';
export type { ValueLabelConfig } from '../common/types';
export type { DatatableVisualizationState } from './datatable_visualization/visualization';
export type {
IndexPatternPersistedState,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/lens/public/shared_components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export * from './coloring';
export { useDebouncedValue } from './debounced_value';
export * from './helpers';
export { LegendActionPopover } from './legend_action_popover';
export { ValueLabelsSettings } from './value_labels_settings';
export * from './static_header';
Original file line number Diff line number Diff line change
@@ -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 { shallowWithIntl as shallow } from '@kbn/test/jest';
import { ValueLabelsSettings, VisualOptionsProps } from './value_labels_settings';

describe('Value labels Settings', () => {
let props: VisualOptionsProps;
beforeEach(() => {
props = {
onValueLabelChange: jest.fn(),
};
});

it('should not render the component if not enabled', () => {
const component = shallow(<ValueLabelsSettings {...props} isVisible={false} />);
expect(component.find('[data-test-subj="lens-value-labels-visibility-btn"]').length).toEqual(0);
});

it('should set hide as default value', () => {
const component = shallow(<ValueLabelsSettings {...props} />);
expect(
component.find('[data-test-subj="lens-value-labels-visibility-btn"]').prop('idSelected')
).toEqual(`value_labels_hide`);
});

it('should have called onValueLabelChange function on ButtonGroup change', () => {
const component = shallow(<ValueLabelsSettings {...props} />);
component
.find('[data-test-subj="lens-value-labels-visibility-btn"]')
.simulate('change', 'value_labels_inside');
expect(props.onValueLabelChange).toHaveBeenCalled();
});

it('should render the passed value if given', () => {
const component = shallow(<ValueLabelsSettings {...props} valueLabels="inside" />);
expect(
component.find('[data-test-subj="lens-value-labels-visibility-btn"]').prop('idSelected')
).toEqual(`value_labels_inside`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
import { ValueLabelConfig } from '../../common/types';

const valueLabelsOptions: Array<{
id: string;
value: ValueLabelConfig;
label: string;
'data-test-subj': string;
}> = [
{
id: `value_labels_hide`,
value: 'hide',
label: i18n.translate('xpack.lens.shared.valueLabelsVisibility.auto', {
defaultMessage: 'Hide',
}),
'data-test-subj': 'lns_valueLabels_hide',
},
{
id: `value_labels_inside`,
value: 'inside',
label: i18n.translate('xpack.lens.shared.valueLabelsVisibility.inside', {
defaultMessage: 'Show',
}),
'data-test-subj': 'lns_valueLabels_inside',
},
];

export interface VisualOptionsProps {
isVisible?: boolean;
valueLabels?: ValueLabelConfig;
onValueLabelChange: (newMode: ValueLabelConfig) => void;
}

export const ValueLabelsSettings: FC<VisualOptionsProps> = ({
isVisible = true,
valueLabels = 'hide',
onValueLabelChange,
}) => {
if (!isVisible) {
return null;
}
const label = i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', {
defaultMessage: 'Labels',
});
const isSelected =
valueLabelsOptions.find(({ value }) => value === valueLabels)?.id || 'value_labels_hide';
return (
<EuiFormRow display="columnCompressed" label={<span>{label}</span>}>
<EuiButtonGroup
isFullWidth
legend={label}
data-test-subj="lens-value-labels-visibility-btn"
name="valueLabelsDisplay"
buttonSize="compressed"
options={valueLabelsOptions}
idSelected={isSelected}
onChange={(modeId) => {
const newMode = valueLabelsOptions.find(({ id }) => id === modeId);
if (newMode) {
onValueLabelChange(newMode.value);
}
}}
/>
</EuiFormRow>
);
};
2 changes: 1 addition & 1 deletion x-pack/plugins/lens/public/xy_visualization/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { LensIconChartLine } from '../assets/chart_line';
import type { VisualizationType } from '../types';
import type {
SeriesType,
ValueLabelConfig,
LegendConfig,
AxisExtentConfig,
XYLayerConfig,
Expand All @@ -29,6 +28,7 @@ import type {
FittingFunction,
LabelsOrientationConfig,
} from '../../common/expressions';
import type { ValueLabelConfig } from '../../common/types';

// Persisted parts of the state
export interface XYState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React from 'react';
import { i18n } from '@kbn/i18n';
import { ToolbarPopover, TooltipWrapper } from '../../../shared_components';
import { ToolbarPopover, TooltipWrapper, ValueLabelsSettings } from '../../../shared_components';
import { MissingValuesOptions } from './missing_values_option';
import { LineCurveOption } from './line_curve_option';
import { FillOpacityOption } from './fill_opacity_option';
Expand Down Expand Up @@ -102,14 +102,17 @@ export const VisualOptionsPopover: React.FC<VisualOptionsPopoverProps> = ({
}}
/>

<MissingValuesOptions
isValueLabelsEnabled={isValueLabelsEnabled}
isFittingEnabled={isFittingEnabled}
valueLabels={state?.valueLabels}
fittingFunction={state?.fittingFunction}
<ValueLabelsSettings
isVisible={isValueLabelsEnabled}
valueLabels={state?.valueLabels ?? 'hide'}
onValueLabelChange={(newMode) => {
setState({ ...state, valueLabels: newMode });
}}
/>

<MissingValuesOptions
isFittingEnabled={isFittingEnabled}
fittingFunction={state?.fittingFunction}
onFittingFnChange={(newVal) => {
setState({ ...state, fittingFunction: newVal });
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,70 +7,23 @@

import React from 'react';
import { shallowWithIntl as shallow, mountWithIntl as mount } from '@kbn/test/jest';
import { EuiSuperSelect, EuiButtonGroup } from '@elastic/eui';
import { EuiSuperSelect } from '@elastic/eui';
import { MissingValuesOptions } from './missing_values_option';

describe('Missing values option', () => {
it('should show currently selected fitting function', () => {
const component = shallow(
<MissingValuesOptions
onFittingFnChange={jest.fn()}
onValueLabelChange={jest.fn()}
fittingFunction={'Carry'}
valueLabels={'hide'}
/>
<MissingValuesOptions onFittingFnChange={jest.fn()} fittingFunction={'Carry'} />
);

expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry');
});

it('should show currently selected value labels display setting', () => {
const component = mount(
<MissingValuesOptions
onFittingFnChange={jest.fn()}
onValueLabelChange={jest.fn()}
fittingFunction={'Carry'}
valueLabels={'inside'}
/>
);

expect(component.find(EuiButtonGroup).prop('idSelected')).toEqual('value_labels_inside');
});

it('should show display field when enabled', () => {
const component = mount(
<MissingValuesOptions
onFittingFnChange={jest.fn()}
onValueLabelChange={jest.fn()}
fittingFunction={'Carry'}
valueLabels={'inside'}
/>
);

expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true);
});

it('should hide in display value label option when disabled', () => {
const component = mount(
<MissingValuesOptions
onFittingFnChange={jest.fn()}
onValueLabelChange={jest.fn()}
fittingFunction={'Carry'}
valueLabels={'inside'}
isValueLabelsEnabled={false}
/>
);

expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(false);
});

it('should show the fitting option when enabled', () => {
const component = mount(
<MissingValuesOptions
onFittingFnChange={jest.fn()}
onValueLabelChange={jest.fn()}
fittingFunction={'Carry'}
valueLabels={'inside'}
isFittingEnabled={true}
/>
);
Expand All @@ -82,9 +35,7 @@ describe('Missing values option', () => {
const component = mount(
<MissingValuesOptions
onFittingFnChange={jest.fn()}
onValueLabelChange={jest.fn()}
fittingFunction={'Carry'}
valueLabels={'inside'}
isFittingEnabled={false}
/>
);
Expand Down
Loading