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] Thresholds: auto fit thresholds into vertical axis #113238

Merged
merged 8 commits into from
Oct 6, 2021
89 changes: 89 additions & 0 deletions x-pack/plugins/lens/public/xy_visualization/expression.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,48 @@ function sampleArgs() {
return { data, args };
}

function sampleArgsWithThreshold(thresholdValue: number = 150) {
const { data, args } = sampleArgs();

return {
data: {
...data,
tables: {
...data.tables,
threshold: {
type: 'datatable',
columns: [
{
id: 'threshold-a',
meta: { params: { id: 'number' }, type: 'number' },
name: 'Static value',
},
],
rows: [{ 'threshold-a': thresholdValue }],
},
},
} as LensMultiTable,
args: {
...args,
layers: [
...args.layers,
{
layerType: layerTypes.THRESHOLD,
accessors: ['threshold-a'],
layerId: 'threshold',
seriesType: 'line',
xScaleType: 'linear',
yScaleType: 'linear',
palette: mockPaletteOutput,
isHistogram: false,
hide: true,
yConfig: [{ axisMode: 'left', forAccessor: 'threshold-a', type: 'lens_xy_yConfig' }],
},
],
} as XYArgs,
};
}

describe('xy_expression', () => {
describe('configs', () => {
test('legendConfig produces the correct arguments', () => {
Expand Down Expand Up @@ -829,6 +871,53 @@ describe('xy_expression', () => {
max: undefined,
});
});

test('it does include threshold values when in full extent mode', () => {
const { data, args } = sampleArgsWithThreshold();

const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
expect(component.find(Axis).find('[id="left"]').prop('domain')).toEqual({
fit: false,
min: 0,
max: 150,
});
});

test('it should ignore threshold values when set to custom extents', () => {
const { data, args } = sampleArgsWithThreshold();

const component = shallow(
<XYChart
{...defaultProps}
data={data}
args={{
...args,
yLeftExtent: {
type: 'lens_xy_axisExtentConfig',
mode: 'custom',
lowerBound: 123,
upperBound: 456,
},
}}
/>
);
expect(component.find(Axis).find('[id="left"]').prop('domain')).toEqual({
fit: false,
min: 123,
max: 456,
});
});

test('it should work for negative values in thresholds', () => {
const { data, args } = sampleArgsWithThreshold(-150);

const component = shallow(<XYChart {...defaultProps} data={data} args={args} />);
expect(component.find(Axis).find('[id="left"]').prop('domain')).toEqual({
fit: false,
min: -150,
max: 5,
});
});
});

test('it has xDomain undefined if the x is not a time scale or a histogram', () => {
Expand Down
40 changes: 37 additions & 3 deletions x-pack/plugins/lens/public/xy_visualization/expression.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export function calculateMinInterval({ args: { layers }, data }: XYChartProps) {
return intervalDuration.as('milliseconds');
}

const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object';

export const getXyChartRenderer = (dependencies: {
formatFactory: FormatFactory;
chartsThemeService: ChartsPluginStart['theme'];
Expand Down Expand Up @@ -395,6 +397,41 @@ export function XYChart({
min = extent.lowerBound;
max = extent.upperBound;
}
} else {
const axisHasThreshold = thresholdLayers.some(({ yConfig }) =>
yConfig?.some(({ axisMode }) => axisMode === axis.groupId)
);
if (!fit && axisHasThreshold) {
// Remove this once the chart will support automatic annotation fit for other type of charts
for (const series of axis.series) {
const table = data.tables[series.layer];
for (const row of table.rows) {
for (const column of table.columns) {
if (column.id === series.accessor) {
const value = row[column.id];
if (typeof value === 'number') {
// keep the 0 in view
max = Math.max(value, max || 0, 0);
min = Math.min(value, min || 0, 0);
}
}
}
}
}
for (const { layerId, yConfig } of thresholdLayers) {
const table = data.tables[layerId];
for (const { axisMode, forAccessor } of yConfig || []) {
if (axis.groupId === axisMode) {
for (const row of table.rows) {
const value = row[forAccessor];
// keep the 0 in view
max = Math.max(value, max || 0, 0);
min = Math.min(value, min || 0, 0);
}
}
}
}
}
}

return {
Expand Down Expand Up @@ -652,9 +689,6 @@ export function XYChart({

const table = data.tables[layerId];

const isPrimitive = (value: unknown): boolean =>
value != null && typeof value !== 'object';

// what if row values are not primitive? That is the case of, for instance, Ranges
// remaps them to their serialized version with the formatHint metadata
// In order to do it we need to make a copy of the table as the raw one is required for more features (filters, etc...) later on
Expand Down