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] Extends Axis bounds for XY chart when using Interval operation #134020

Merged
merged 9 commits into from
Jun 15, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ export const createArgsWithLayers = (
yLeft: false,
yRight: false,
},
xExtent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
yLeftExtent: {
mode: 'full',
type: 'axisExtentConfig',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export const commonXYArgs: CommonXYFn['args'] = {
types: ['string'],
help: strings.getYRightTitleHelp(),
},
xExtent: {
types: [AXIS_EXTENT_CONFIG],
help: strings.getXExtentHelp(),
default: `{${AXIS_EXTENT_CONFIG}}`,
},
yLeftExtent: {
types: [AXIS_EXTENT_CONFIG],
help: strings.getYLeftExtentHelp(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export const strings = {
i18n.translate('expressionXY.xyVis.yRightTitle.help', {
defaultMessage: 'Y right axis title',
}),
getXExtentHelp: () =>
i18n.translate('expressionXY.xyVis.xExtent.help', {
defaultMessage: 'X axis extents',
}),
getYLeftExtentHelp: () =>
i18n.translate('expressionXY.xyVis.yLeftExtent.help', {
defaultMessage: 'Y left axis extents',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export interface XYArgs extends DataLayerArgs {
xTitle: string;
yTitle: string;
yRightTitle: string;
xExtent: AxisExtentConfigResult;
yLeftExtent: AxisExtentConfigResult;
yRightExtent: AxisExtentConfigResult;
yLeftScale: YScaleType;
Expand Down Expand Up @@ -230,6 +231,7 @@ export interface LayeredXYArgs {
xTitle: string;
yTitle: string;
yRightTitle: string;
xExtent: AxisExtentConfigResult;
yLeftExtent: AxisExtentConfigResult;
yRightExtent: AxisExtentConfigResult;
yLeftScale: YScaleType;
Expand Down Expand Up @@ -261,6 +263,7 @@ export interface XYProps {
xTitle: string;
yTitle: string;
yRightTitle: string;
xExtent: AxisExtentConfigResult;
yLeftExtent: AxisExtentConfigResult;
yRightExtent: AxisExtentConfigResult;
yLeftScale: YScaleType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
getAccessorByDimension,
getColumnByAccessor,
} from '@kbn/visualizations-plugin/common/utils';
import type { CommonXYDataLayerConfig } from '../../common';
import type { AxisExtentConfigResult, CommonXYDataLayerConfig } from '../../common';

export interface XDomain {
min?: number;
Expand Down Expand Up @@ -43,7 +43,8 @@ export const getXDomain = (
layers: CommonXYDataLayerConfig[],
minInterval: number | undefined,
isTimeViz: boolean,
isHistogram: boolean
isHistogram: boolean,
xExtent?: AxisExtentConfigResult
) => {
const appliedTimeRange = getAppliedTimeRange(layers)?.timeRange;
const from = appliedTimeRange?.from;
Expand All @@ -59,6 +60,16 @@ export const getXDomain = (
: undefined;

if (isHistogram && isFullyQualified(baseDomain)) {
if (xExtent && !isTimeViz) {
return {
extendedDomain: {
min: xExtent.lowerBound ?? NaN,
max: xExtent.upperBound ?? NaN,
minInterval: baseDomain.minInterval,
},
baseDomain,
};
}
const xValues = uniq(
layers
.flatMap<number>(({ table, xAccessor }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,40 @@ describe('XYChart component', () => {
});
});

describe('x axis extents', () => {
const { args } = sampleArgs();

test('it passes custom x axis extents to elastic-charts settings spec', () => {
{
const component = shallow(
<XYChart
{...defaultProps}
args={{
...args,
layers: [
{
...(args.layers[0] as DataLayerConfig),
isHistogram: true,
},
],
xExtent: {
type: 'axisExtentConfig',
mode: 'custom',
lowerBound: 123,
upperBound: 456,
},
}}
/>
);
expect(component.find(Settings).prop('xDomain')).toEqual({
min: 123,
max: 456,
minInterval: 50,
});
}
});
});

describe('y axis extents', () => {
const { args } = sampleArgs();

Expand Down Expand Up @@ -2256,6 +2290,10 @@ describe('XYChart component', () => {
yLeft: 0,
yRight: 0,
},
xExtent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
yLeftExtent: {
mode: 'full',
type: 'axisExtentConfig',
Expand Down Expand Up @@ -2347,6 +2385,10 @@ describe('XYChart component', () => {
yLeft: 0,
yRight: 0,
},
xExtent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
yLeftExtent: {
mode: 'full',
type: 'axisExtentConfig',
Expand Down Expand Up @@ -2423,6 +2465,10 @@ describe('XYChart component', () => {
yLeft: 0,
yRight: 0,
},
xExtent: {
mode: 'dataBounds',
type: 'axisExtentConfig',
},
yLeftExtent: {
mode: 'full',
type: 'axisExtentConfig',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export function XYChart({
gridlinesVisibilitySettings,
valueLabels,
hideEndzones,
xExtent,
yLeftExtent,
yRightExtent,
valuesInLegend,
Expand Down Expand Up @@ -279,7 +280,8 @@ export function XYChart({
dataLayers,
minInterval,
isTimeViz,
isHistogramViz
isHistogramViz,
xExtent
);

const getYAxesTitles = (axisSeries: Series[]) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* 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 { EuiFormRow, EuiButtonGroup, htmlIdGenerator } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { RangeInputField } from '../range_input_field';
import { validateAxisDomain } from './helpers';
import { UnifiedAxisExtentConfig } from './types';

const idPrefix = htmlIdGenerator()();
export function BucketAxisBoundsControl({
testSubjPrefix,
extent,
setExtent,
dataBounds,
}: {
testSubjPrefix: string;
extent: UnifiedAxisExtentConfig;
setExtent: (newExtent: UnifiedAxisExtentConfig | undefined) => void;
dataBounds: { min: number; max: number } | undefined;
}) {
const boundaryError = !validateAxisDomain(extent);
return (
<>
<EuiFormRow
display="columnCompressed"
fullWidth
label={i18n.translate('xpack.lens.axisExtent.label', {
defaultMessage: 'Bounds',
})}
>
<EuiButtonGroup
isFullWidth
legend={i18n.translate('xpack.lens.axisExtent.label', {
defaultMessage: 'Bounds',
})}
data-test-subj={`${testSubjPrefix}_axisBounds_groups`}
name="axisBounds"
buttonSize="compressed"
options={[
{
id: `${idPrefix}dataBounds`,
label: i18n.translate('xpack.lens.axisExtent.dataBounds', {
defaultMessage: 'Data',
}),
'data-test-subj': `${testSubjPrefix}_axisExtent_groups_DataBounds'`,
},
{
id: `${idPrefix}custom`,
label: i18n.translate('xpack.lens.axisExtent.custom', {
defaultMessage: 'Custom',
}),
'data-test-subj': `${testSubjPrefix}_axisExtent_groups_custom'`,
},
]}
idSelected={`${idPrefix}${extent.mode ?? 'dataBounds'}`}
onChange={(id) => {
const newMode = id.replace(idPrefix, '') as UnifiedAxisExtentConfig['mode'];
setExtent({
...extent,
mode: newMode,
lowerBound: newMode === 'custom' && dataBounds ? dataBounds.min : undefined,
upperBound: newMode === 'custom' && dataBounds ? dataBounds.max : undefined,
});
}}
/>
</EuiFormRow>
{extent?.mode === 'custom' && (
<RangeInputField
isInvalid={boundaryError}
label={' '}
error={
boundaryError
? i18n.translate('xpack.lens.boundaryError', {
defaultMessage: 'Lower bound has to be larger than upper bound',
})
: undefined
}
testSubjLayout={`${testSubjPrefix}_axisExtent_customBounds`}
testSubjLower={`${testSubjPrefix}_axisExtent_lowerBound`}
testSubjUpper={`${testSubjPrefix}_axisExtent_upperBound`}
lowerValue={extent.lowerBound ?? ''}
onLowerValueChange={(e) => {
const val = Number(e.target.value);
const isEmptyValue = e.target.value === '' || Number.isNaN(Number(val));
setExtent({
...extent,
lowerBound: isEmptyValue ? undefined : val,
});
}}
onLowerValueBlur={() => {
if (extent.lowerBound === undefined && dataBounds) {
setExtent({
...extent,
lowerBound: dataBounds.min,
});
}
}}
upperValue={extent.upperBound ?? ''}
onUpperValueChange={(e) => {
const val = Number(e.target.value);
const isEmptyValue = e.target.value === '' || Number.isNaN(Number(val));
setExtent({
...extent,
upperBound: isEmptyValue ? undefined : val,
});
}}
onUpperValueBlur={() => {
if (extent.upperBound === undefined && dataBounds) {
setExtent({
...extent,
upperBound: dataBounds.max,
});
}
}}
/>
)}
</>
);
}
Loading