Skip to content

Commit

Permalink
[Lens] Thresholds: auto fit thresholds into vertical axis (elastic#11…
Browse files Browse the repository at this point in the history
…3238)

* ✨ Make threshold fit into view automatically

* 🐛 do not compute axis threshold extends if no threshold is present

* ✅ One more fix for 0-based extends and tests

* 📝 fix typo

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
dej611 and kibanamachine authored Oct 6, 2021
1 parent 634cd80 commit 3fe1eab
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 3 deletions.
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

0 comments on commit 3fe1eab

Please sign in to comment.