Skip to content

Commit 2c988e9

Browse files
shahzad31kibanamachine
authored andcommitted
[Lens] Allow modifying curve type for line/area series charts (#94675)
1 parent c841686 commit 2c988e9

18 files changed

Lines changed: 696 additions & 366 deletions

File tree

x-pack/plugins/lens/public/shared_components/toolbar_popover.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const typeToIconMap: { [type: string]: string | IconType } = {
1515
labels: 'visText',
1616
values: 'number',
1717
list: 'list',
18+
visualOptions: 'brush',
1819
};
1920

2021
export interface ToolbarPopoverProps {

x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/lens/public/xy_visualization/expression.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
HorizontalAlignment,
2525
ElementClickListener,
2626
BrushEndListener,
27+
CurveType,
2728
} from '@elastic/charts';
2829
import { I18nProvider } from '@kbn/i18n/react';
2930
import {
@@ -179,6 +180,13 @@ export const xyChart: ExpressionFunctionDefinition<
179180
help: 'Layers of visual series',
180181
multi: true,
181182
},
183+
curveType: {
184+
types: ['string'],
185+
options: ['LINEAR', 'CURVE_MONOTONE_X'],
186+
help: i18n.translate('xpack.lens.xyChart.curveType.help', {
187+
defaultMessage: 'Define how curve type is rendered for a line chart',
188+
}),
189+
},
182190
},
183191
fn(data: LensMultiTable, args: XYArgs) {
184192
return {
@@ -773,10 +781,17 @@ export function XYChart({
773781

774782
const index = `${layerIndex}-${accessorIndex}`;
775783

784+
const curveType = args.curveType ? CurveType[args.curveType] : undefined;
785+
776786
switch (seriesType) {
777787
case 'line':
778788
return (
779-
<LineSeries key={index} {...seriesProps} fit={getFitOptions(fittingFunction)} />
789+
<LineSeries
790+
key={index}
791+
{...seriesProps}
792+
fit={getFitOptions(fittingFunction)}
793+
curve={curveType}
794+
/>
780795
);
781796
case 'bar':
782797
case 'bar_stacked':
@@ -804,11 +819,17 @@ export function XYChart({
804819
key={index}
805820
{...seriesProps}
806821
fit={isPercentage ? 'zero' : getFitOptions(fittingFunction)}
822+
curve={curveType}
807823
/>
808824
);
809825
case 'area':
810826
return (
811-
<AreaSeries key={index} {...seriesProps} fit={getFitOptions(fittingFunction)} />
827+
<AreaSeries
828+
key={index}
829+
{...seriesProps}
830+
fit={getFitOptions(fittingFunction)}
831+
curve={curveType}
832+
/>
812833
);
813834
default:
814835
return assertNever(seriesType);

x-pack/plugins/lens/public/xy_visualization/to_expression.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export const buildExpression = (
148148
},
149149
],
150150
fittingFunction: [state.fittingFunction || 'None'],
151+
curveType: [state.curveType || 'LINEAR'],
151152
axisTitlesVisibilitySettings: [
152153
{
153154
type: 'expression',

x-pack/plugins/lens/public/xy_visualization/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,11 @@ export interface XYArgs {
413413
};
414414
tickLabelsVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' };
415415
gridlinesVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' };
416+
curveType?: XYCurveType;
416417
}
417418

419+
export type XYCurveType = 'LINEAR' | 'CURVE_MONOTONE_X';
420+
418421
// Persisted parts of the state
419422
export interface XYState {
420423
preferredSeriesType: SeriesType;
@@ -428,6 +431,7 @@ export interface XYState {
428431
axisTitlesVisibilitySettings?: AxesSettingsConfig;
429432
tickLabelsVisibilitySettings?: AxesSettingsConfig;
430433
gridlinesVisibilitySettings?: AxesSettingsConfig;
434+
curveType?: XYCurveType;
431435
}
432436

433437
export type State = XYState;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test/jest';
10+
import { EuiSwitch } from '@elastic/eui';
11+
import { LineCurveOption } from './line_curve_option';
12+
13+
describe('Line curve option', () => {
14+
it('should show currently selected line curve option', () => {
15+
const component = shallow(<LineCurveOption onChange={jest.fn()} value={'CURVE_MONOTONE_X'} />);
16+
17+
expect(component.find(EuiSwitch).prop('checked')).toEqual(true);
18+
});
19+
20+
it('should show currently curving disabled', () => {
21+
const component = shallow(<LineCurveOption onChange={jest.fn()} value={'LINEAR'} />);
22+
23+
expect(component.find(EuiSwitch).prop('checked')).toEqual(false);
24+
});
25+
26+
it('should show curving option when enabled', () => {
27+
const component = mount(
28+
<LineCurveOption onChange={jest.fn()} value={'LINEAR'} isCurveTypeEnabled={true} />
29+
);
30+
31+
expect(component.exists('[data-test-subj="lnsCurveStyleToggle"]')).toEqual(true);
32+
});
33+
34+
it('should hide curve option when disabled', () => {
35+
const component = mount(
36+
<LineCurveOption onChange={jest.fn()} value={'LINEAR'} isCurveTypeEnabled={false} />
37+
);
38+
39+
expect(component.exists('[data-test-subj="lnsCurveStyleToggle"]')).toEqual(false);
40+
});
41+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { i18n } from '@kbn/i18n';
10+
import { EuiFormRow, EuiSpacer, EuiSwitch } from '@elastic/eui';
11+
import { XYCurveType } from '../types';
12+
13+
export interface LineCurveOptionProps {
14+
/**
15+
* Currently selected value
16+
*/
17+
value?: XYCurveType;
18+
/**
19+
* Callback on display option change
20+
*/
21+
onChange: (id: XYCurveType) => void;
22+
isCurveTypeEnabled?: boolean;
23+
}
24+
25+
export const LineCurveOption: React.FC<LineCurveOptionProps> = ({
26+
onChange,
27+
value,
28+
isCurveTypeEnabled = true,
29+
}) => {
30+
return isCurveTypeEnabled ? (
31+
<>
32+
<EuiFormRow
33+
display="columnCompressedSwitch"
34+
label={i18n.translate('xpack.lens.xyChart.curveStyleLabel', {
35+
defaultMessage: 'Curve lines',
36+
})}
37+
>
38+
<EuiSwitch
39+
showLabel={false}
40+
label="Curved"
41+
checked={value === 'CURVE_MONOTONE_X'}
42+
compressed={true}
43+
onChange={(e) => {
44+
if (e.target.checked) {
45+
onChange('CURVE_MONOTONE_X');
46+
} else {
47+
onChange('LINEAR');
48+
}
49+
}}
50+
data-test-subj="lnsCurveStyleToggle"
51+
/>
52+
</EuiFormRow>
53+
<EuiSpacer />
54+
</>
55+
) : null;
56+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { shallowWithIntl as shallow, mountWithIntl as mount } from '@kbn/test/jest';
10+
import { EuiSuperSelect, EuiButtonGroup } from '@elastic/eui';
11+
import { MissingValuesOptions } from './missing_values_option';
12+
13+
describe('Missing values option', () => {
14+
it('should show currently selected fitting function', () => {
15+
const component = shallow(
16+
<MissingValuesOptions
17+
onFittingFnChange={jest.fn()}
18+
onValueLabelChange={jest.fn()}
19+
fittingFunction={'Carry'}
20+
valueLabels={'hide'}
21+
/>
22+
);
23+
24+
expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry');
25+
});
26+
27+
it('should show currently selected value labels display setting', () => {
28+
const component = mount(
29+
<MissingValuesOptions
30+
onFittingFnChange={jest.fn()}
31+
onValueLabelChange={jest.fn()}
32+
fittingFunction={'Carry'}
33+
valueLabels={'inside'}
34+
/>
35+
);
36+
37+
expect(component.find(EuiButtonGroup).prop('idSelected')).toEqual('value_labels_inside');
38+
});
39+
40+
it('should show display field when enabled', () => {
41+
const component = mount(
42+
<MissingValuesOptions
43+
onFittingFnChange={jest.fn()}
44+
onValueLabelChange={jest.fn()}
45+
fittingFunction={'Carry'}
46+
valueLabels={'inside'}
47+
/>
48+
);
49+
50+
expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true);
51+
});
52+
53+
it('should hide in display value label option when disabled', () => {
54+
const component = mount(
55+
<MissingValuesOptions
56+
onFittingFnChange={jest.fn()}
57+
onValueLabelChange={jest.fn()}
58+
fittingFunction={'Carry'}
59+
valueLabels={'inside'}
60+
isValueLabelsEnabled={false}
61+
/>
62+
);
63+
64+
expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(false);
65+
});
66+
67+
it('should show the fitting option when enabled', () => {
68+
const component = mount(
69+
<MissingValuesOptions
70+
onFittingFnChange={jest.fn()}
71+
onValueLabelChange={jest.fn()}
72+
fittingFunction={'Carry'}
73+
valueLabels={'inside'}
74+
isFittingEnabled={true}
75+
/>
76+
);
77+
78+
expect(component.exists('[data-test-subj="lnsMissingValuesSelect"]')).toEqual(true);
79+
});
80+
81+
it('should hide the fitting option when disabled', () => {
82+
const component = mount(
83+
<MissingValuesOptions
84+
onFittingFnChange={jest.fn()}
85+
onValueLabelChange={jest.fn()}
86+
fittingFunction={'Carry'}
87+
valueLabels={'inside'}
88+
isFittingEnabled={false}
89+
/>
90+
);
91+
92+
expect(component.exists('[data-test-subj="lnsMissingValuesSelect"]')).toEqual(false);
93+
});
94+
});

0 commit comments

Comments
 (0)