mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Lens] Thresholds: auto fit thresholds into vertical axis (#113238)
* ✨ 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>
This commit is contained in:
parent
634cd80c7e
commit
3fe1eab5a5
2 changed files with 126 additions and 3 deletions
|
@ -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', () => {
|
||||
|
@ -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', () => {
|
||||
|
|
|
@ -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'];
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue