[Lens] Add support for curved, linear and stepped lines (#158896)

Adds support for `straight`, `smooth` (curved) and `step` (after) curves
on line and area charts in Lens.
This commit is contained in:
Nick Partridge 2023-06-08 08:18:58 -07:00 committed by GitHub
parent 48e783972a
commit afdf3f64fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 110 additions and 61 deletions

View file

@ -9,7 +9,7 @@
export const PLUGIN_ID = 'expressionXy';
export const PLUGIN_NAME = 'expressionXy';
export { LayerTypes } from './constants';
export { LayerTypes, XYCurveTypes } from './constants';
export type {
AllowedXYOverrides,

View file

@ -14,6 +14,6 @@ export function plugin() {
return new ExpressionXyPlugin();
}
export { LayerTypes } from '../common';
export { LayerTypes, XYCurveTypes } from '../common';
export type { ExpressionXyPluginSetup, ExpressionXyPluginStart } from './types';

View file

@ -7,7 +7,8 @@
*/
import { Position } from '@elastic/charts';
import { XYConfiguration, XYLayerConfig } from '@kbn/visualizations-plugin/common';
import { XYCurveTypes } from '@kbn/visualizations-plugin/public';
import type { XYConfiguration, XYLayerConfig } from '@kbn/visualizations-plugin/common';
import { Panel } from '../../../../../common/types';
import { getYExtents } from './extents';
@ -18,6 +19,7 @@ export const getConfigurationForTimeseries = (
const extents = getYExtents(model);
return {
layers,
curveType: model.series[0].steps === 1 ? XYCurveTypes.CURVE_STEP_AFTER : undefined,
fillOpacity: Number(model.series[0].fill) ?? 0.3,
legend: {
isVisible: Boolean(model.show_legend),

View file

@ -53,7 +53,7 @@ export {
DEFAULT_LEGEND_SIZE,
} from '../common/constants';
export type { SavedVisState, VisParams, Dimension } from '../common';
export { prepareLogTable } from '../common';
export { prepareLogTable, XYCurveTypes } from '../common';
export type { ExpressionValueVisDimension } from '../common/expression_functions/vis_dimension';
export type {
ExpressionValueXYDimension,

View file

@ -88,12 +88,12 @@ export const VisualOptionsPopover: React.FC<VisualOptionsPopoverProps> = ({
isDisabled={isDisabled}
>
<LineCurveOption
isCurveTypeEnabled={isCurveTypeEnabled}
enabled={isCurveTypeEnabled}
value={state?.curveType}
onChange={(id) => {
onChange={(curveType) => {
setState({
...state,
curveType: id,
curveType,
});
}}
/>

View file

@ -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 type { XYCurveType } from '@kbn/expression-xy-plugin/common';
import { XYCurveTypes } from '@kbn/expression-xy-plugin/public';
import { i18n } from '@kbn/i18n';
export interface LineCurveDefinitions {
type: Extract<XYCurveType, 'LINEAR' | 'CURVE_MONOTONE_X' | 'CURVE_STEP_AFTER'>;
title: string;
description?: string;
}
export const lineCurveDefinitions: LineCurveDefinitions[] = [
{
type: XYCurveTypes.LINEAR,
title: i18n.translate('xpack.lens.lineCurve.straight', {
defaultMessage: 'Straight',
}),
description: i18n.translate('xpack.lens.lineCurveDescription.straight', {
defaultMessage: 'Straight line between points',
}),
},
{
type: XYCurveTypes.CURVE_MONOTONE_X,
title: i18n.translate('xpack.lens.lineCurve.smooth', {
defaultMessage: 'Smooth',
}),
description: i18n.translate('xpack.lens.lineCurveDescription.smooth', {
defaultMessage: 'Smoothed line between points',
}),
},
{
type: XYCurveTypes.CURVE_STEP_AFTER,
title: i18n.translate('xpack.lens.lineCurve.step', {
defaultMessage: 'Step',
}),
description: i18n.translate('xpack.lens.lineCurveDescription.step', {
defaultMessage: 'Stepped line between points',
}),
},
];

View file

@ -7,35 +7,33 @@
import React from 'react';
import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test-jest-helpers';
import { EuiSwitch } from '@elastic/eui';
import { EuiSuperSelect } from '@elastic/eui';
import { LineCurveOption } from './line_curve_option';
import { lineCurveDefinitions } from './line_curve_definitions';
describe('Line curve option', () => {
it('should show currently selected line curve option', () => {
const component = shallow(<LineCurveOption onChange={jest.fn()} value={'CURVE_MONOTONE_X'} />);
it.each(lineCurveDefinitions.map((v) => v.type))(
'should show currently line curve option - %s',
(type) => {
const component = shallow(<LineCurveOption onChange={jest.fn()} value={type} />);
expect(component.find(EuiSwitch).prop('checked')).toEqual(true);
});
expect(component.find(EuiSuperSelect).first().prop('valueOfSelected')).toEqual(type);
}
);
it('should show currently curving disabled', () => {
const component = shallow(<LineCurveOption onChange={jest.fn()} value={'LINEAR'} />);
expect(component.find(EuiSwitch).prop('checked')).toEqual(false);
});
it('should show curving option when enabled', () => {
it('should show line curve option when enabled', () => {
const component = mount(
<LineCurveOption onChange={jest.fn()} value={'LINEAR'} isCurveTypeEnabled={true} />
<LineCurveOption onChange={jest.fn()} value={'LINEAR'} enabled={true} />
);
expect(component.exists('[data-test-subj="lnsCurveStyleToggle"]')).toEqual(true);
expect(component.exists('[data-test-subj="lnsCurveStyleSelect"]')).toEqual(true);
});
it('should hide curve option when disabled', () => {
it('should hide line curve option when disabled', () => {
const component = mount(
<LineCurveOption onChange={jest.fn()} value={'LINEAR'} isCurveTypeEnabled={false} />
<LineCurveOption onChange={jest.fn()} value={'LINEAR'} enabled={false} />
);
expect(component.exists('[data-test-subj="lnsCurveStyleToggle"]')).toEqual(false);
expect(component.exists('[data-test-subj="lnsCurveStyleSelect"]')).toEqual(false);
});
});

View file

@ -7,46 +7,48 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiSwitch } from '@elastic/eui';
import { EuiFormRow, EuiSuperSelect, EuiText } from '@elastic/eui';
import type { XYCurveType } from '@kbn/expression-xy-plugin/common';
import { XYCurveTypes } from '@kbn/expression-xy-plugin/public';
import { lineCurveDefinitions } from './line_curve_definitions';
export interface LineCurveOptionProps {
/**
* Currently selected value
*/
value?: XYCurveType;
/**
* Callback on display option change
*/
onChange: (id: XYCurveType) => void;
isCurveTypeEnabled?: boolean;
onChange: (type: XYCurveType) => void;
enabled?: boolean;
}
export const LineCurveOption: React.FC<LineCurveOptionProps> = ({
onChange,
value,
isCurveTypeEnabled = true,
value = XYCurveTypes.LINEAR,
enabled = true,
}) => {
return isCurveTypeEnabled ? (
return enabled ? (
<EuiFormRow
display="columnCompressedSwitch"
label={i18n.translate('xpack.lens.xyChart.curveStyleLabel', {
defaultMessage: 'Curve lines',
display="columnCompressed"
label={i18n.translate('xpack.lens.xyChart.lineInterpolationLabel', {
defaultMessage: 'Line interpolation',
})}
>
<EuiSwitch
showLabel={false}
label="Curved"
checked={value === 'CURVE_MONOTONE_X'}
compressed={true}
onChange={(e) => {
if (e.target.checked) {
onChange('CURVE_MONOTONE_X');
} else {
onChange('LINEAR');
}
}}
data-test-subj="lnsCurveStyleToggle"
<EuiSuperSelect
data-test-subj="lnsCurveStyleSelect"
compressed
options={lineCurveDefinitions.map(({ type, title, description }) => ({
value: type,
dropdownDisplay: (
<>
<strong>{title}</strong>
<EuiText size="xs" color="subdued">
<p>{description}</p>
</EuiText>
</>
),
inputDisplay: title,
}))}
valueOfSelected={value}
onChange={onChange}
itemLayoutAlign="top"
hasDividers
/>
</EuiFormRow>
) : null;

View file

@ -21188,7 +21188,6 @@
"xpack.lens.xyChart.axisSide.top": "Haut",
"xpack.lens.xyChart.bottomAxisDisabledHelpText": "Ce paramètre s'applique uniquement lorsque l'axe du bas est activé.",
"xpack.lens.xyChart.bottomAxisLabel": "Axe du bas",
"xpack.lens.xyChart.curveStyleLabel": "Courbes",
"xpack.lens.xyChart.defaultAnnotationLabel": "Événement",
"xpack.lens.xyChart.defaultRangeAnnotationLabel": "Plage d'événements",
"xpack.lens.xyChart.endValuesLabel": "Valeurs de fin",

View file

@ -21188,7 +21188,6 @@
"xpack.lens.xyChart.axisSide.top": "トップ",
"xpack.lens.xyChart.bottomAxisDisabledHelpText": "この設定は、下の軸が有効であるときにのみ適用されます。",
"xpack.lens.xyChart.bottomAxisLabel": "下の軸",
"xpack.lens.xyChart.curveStyleLabel": "曲線",
"xpack.lens.xyChart.defaultAnnotationLabel": "イベント",
"xpack.lens.xyChart.defaultRangeAnnotationLabel": "イベント範囲",
"xpack.lens.xyChart.endValuesLabel": "終了値",

View file

@ -21188,7 +21188,6 @@
"xpack.lens.xyChart.axisSide.top": "顶部",
"xpack.lens.xyChart.bottomAxisDisabledHelpText": "此设置仅在启用底轴时应用。",
"xpack.lens.xyChart.bottomAxisLabel": "底轴",
"xpack.lens.xyChart.curveStyleLabel": "曲线",
"xpack.lens.xyChart.defaultAnnotationLabel": "事件",
"xpack.lens.xyChart.defaultRangeAnnotationLabel": "事件范围",
"xpack.lens.xyChart.endValuesLabel": "结束值",

View file

@ -161,7 +161,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.lens.editDimensionColor('#ff0000');
await PageObjects.lens.openVisualOptions();
await PageObjects.lens.useCurvedLines();
await PageObjects.lens.setCurvedLines('CURVE_MONOTONE_X');
await PageObjects.lens.editMissingValues('Linear');
await PageObjects.lens.assertMissingValues('Linear');

View file

@ -7,6 +7,7 @@
import expect from '@kbn/expect';
import { setTimeout as setTimeoutAsync } from 'timers/promises';
import type { FittingFunction, XYCurveType } from '@kbn/lens-plugin/public';
import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
import { FtrProviderContext } from '../ftr_provider_context';
import { logWrapper } from './log_wrapper';
@ -787,10 +788,12 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
expect(await (await testSubjects.find(input)).getAttribute('value')).to.eql(value);
});
},
async useCurvedLines() {
await testSubjects.click('lnsCurveStyleToggle');
async setCurvedLines(option: XYCurveType) {
await testSubjects.click('lnsCurveStyleSelect');
const optionSelector = await find.byCssSelector(`#${option}`);
await optionSelector.click();
},
async editMissingValues(option: string) {
async editMissingValues(option: FittingFunction) {
await testSubjects.click('lnsMissingValuesSelect');
const optionSelector = await find.byCssSelector(`#${option}`);
await optionSelector.click();

View file

@ -305,7 +305,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.lens.editDimensionColor('#ff0000');
await PageObjects.lens.openVisualOptions();
await PageObjects.lens.useCurvedLines();
await PageObjects.lens.setCurvedLines('CURVE_MONOTONE_X');
await PageObjects.lens.editMissingValues('Linear');
await PageObjects.lens.assertMissingValues(termTranslator('Linear'));

View file

@ -127,6 +127,7 @@
"@kbn/server-route-repository",
"@kbn/core-saved-objects-common",
"@kbn/core-http-common",
"@kbn/slo-schema"
"@kbn/slo-schema",
"@kbn/lens-plugin"
]
}