Skip to content

Commit

Permalink
[Lens] Select line chart by default if the x-axis contains a timestamp (
Browse files Browse the repository at this point in the history
elastic#190786)

## Summary

Partially resolves elastic#184102

When dropping a field to a workspace that will create the date histogram
visualization, it converts to line. It doesn't do it when dropping to a
dimension as it's more complicated thing. The reason for it is that we
would have to:
- decide if we also want to switch to line when choosing a field from
the config panel
- allow for an extra behavior (switching visualization types) for the
onDrop callback except for only modifying dimensions.


## Release notes

When dropping a field into the Lens workspace, the default time-series
visualization is a line chart instead of a histogram.
  • Loading branch information
mbondyra authored Oct 21, 2024
1 parent 5fe8aad commit 2d744ff
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 36 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/lens/public/visualizations/xy/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export const SeriesTypes = {
BAR_HORIZONTAL_PERCENTAGE_STACKED: 'bar_horizontal_percentage_stacked',
} as const;

export const defaultSeriesType = SeriesTypes.BAR_STACKED;

export type YAxisMode = $Values<typeof YAxisModes>;
export type SeriesType = $Values<typeof SeriesTypes>;
export interface AxesSettingsConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ import {
} from './annotations/helpers';
import {
checkXAccessorCompatibility,
defaultSeriesType,
getAnnotationLayerTitle,
getAnnotationsLayers,
getAxisName,
Expand All @@ -112,6 +111,7 @@ import {
} from './visualization_helpers';
import { getAxesConfiguration, groupAxesByType } from './axes_configuration';
import type { XYByValueAnnotationLayerConfig, XYState } from './types';
import { defaultSeriesType } from './types';
import { defaultAnnotationLabel } from './annotations/helpers';
import { onDropForVisualization } from '../../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils';
import { createAnnotationActions } from './annotations/actions';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,6 @@ export function getDescription(state?: State, layerId?: string) {
}

export const defaultIcon = IconChartBarStacked;
export const defaultSeriesType = 'bar_stacked';

export const supportedDataLayer = {
type: layerTypes.DATA,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,41 @@ describe('xy_suggestions', () => {
expect((suggestions[0].state.layers[0] as XYDataLayerConfig).seriesType).toEqual('line');
});

test('suggests line if changeType is initial and date column is involved', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
valueLabels: 'hide',
fittingFunction: 'None',
preferredSeriesType: 'bar_stacked',
layers: [
{
accessors: [],
layerId: 'first',
layerType: LayerTypes.DATA,
seriesType: 'bar_stacked',
splitAccessor: undefined,
xAccessor: '',
},
],
};
const suggestions = getSuggestions({
table: {
isMultiRow: true,
columns: [numCol('price'), dateCol('date')],
layerId: 'first',
changeType: 'initial',
},
state: currentState,
keptLayerIds: ['first'],
});

expect(suggestions).toHaveLength(1);

expect(suggestions[0].hide).toEqual(false);
expect(suggestions[0].state.preferredSeriesType).toEqual('line');
expect((suggestions[0].state.layers[0] as XYDataLayerConfig).seriesType).toEqual('line');
});

test('makes a visible seriesType suggestion for unchanged table without split', () => {
const currentState: XYState = {
legend: { isVisible: true, position: 'bottom' },
Expand Down
42 changes: 20 additions & 22 deletions x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
XYLayerConfig,
XYDataLayerConfig,
SeriesType,
defaultSeriesType,
} from './types';
import { flipSeriesType, getIconForSeries } from './state_helpers';
import { getDataLayers, isDataLayer } from './visualization_helpers';
Expand Down Expand Up @@ -101,39 +102,35 @@ function getSuggestionForColumns(
allowMixed?: boolean
): VisualizationSuggestion<State> | Array<VisualizationSuggestion<State>> | undefined {
const [buckets, values] = partition(table.columns, (col) => col.operation.isBucketed);
const sharedArgs = {
layerId: table.layerId,
changeType: table.changeType,
currentState,
tableLabel: table.label,
keptLayerIds,
requestedSeriesType: seriesType,
mainPalette,
allowMixed,
};

if (buckets.length === 1 || buckets.length === 2) {
const [x, splitBy] = getBucketMappings(table, currentState);
const [xValue, splitBy] = getBucketMappings(table, currentState);
return getSuggestionsForLayer({
layerId: table.layerId,
changeType: table.changeType,
xValue: x,
...sharedArgs,
xValue,
yValues: values,
splitBy,
currentState,
tableLabel: table.label,
keptLayerIds,
requestedSeriesType: seriesType,
mainPalette,
allowMixed,
});
} else if (buckets.length === 0) {
const [yValues, [xValue, splitBy]] = partition(
prioritizeColumns(values),
(col) => col.operation.dataType === 'number' && !col.operation.isBucketed
);
return getSuggestionsForLayer({
layerId: table.layerId,
changeType: table.changeType,
...sharedArgs,
xValue,
yValues,
splitBy,
currentState,
tableLabel: table.label,
keptLayerIds,
requestedSeriesType: seriesType,
mainPalette,
allowMixed,
});
}
}
Expand Down Expand Up @@ -236,6 +233,9 @@ function getSuggestionsForLayer({
allowMixed,
};

if (changeType === 'initial' && xValue?.operation.dataType === 'date') {
return buildSuggestion({ ...options, seriesType: 'line' });
}
// handles the simplest cases, acting as a chart switcher
if (!currentState && changeType === 'unchanged') {
// Chart switcher needs to include every chart type
Expand Down Expand Up @@ -434,18 +434,16 @@ function getSeriesType(
layerId: string,
xValue?: TableSuggestionColumn
): SeriesType {
const defaultType = 'bar_stacked';

const oldLayer = getExistingLayer(currentState, layerId);
const oldLayerSeriesType = oldLayer && isDataLayer(oldLayer) ? oldLayer.seriesType : false;

const closestSeriesType =
oldLayerSeriesType || (currentState && currentState.preferredSeriesType) || defaultType;
oldLayerSeriesType || (currentState && currentState.preferredSeriesType) || defaultSeriesType;

// Attempt to keep the seriesType consistent on initial add of a layer
// Ordinal scales should always use a bar because there is no interpolation between buckets
if (xValue && xValue.operation.scale && xValue.operation.scale === 'ordinal') {
return closestSeriesType.startsWith('bar') ? closestSeriesType : defaultType;
return closestSeriesType.startsWith('bar') ? closestSeriesType : defaultSeriesType;
}

return closestSeriesType;
Expand Down
34 changes: 22 additions & 12 deletions x-pack/test/functional/apps/lens/group1/multiple_data_views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {

function assertMatchesExpectedData(
state: DebugState,
expectedData: Array<Array<{ x: number; y: number }>>
expectedData: Array<Array<{ x: number; y: number }>>,
chartType: 'bars' | 'lines' = 'bars'
) {
expect(
state?.bars?.map(({ bars }) =>
bars.map((bar) => ({
x: bar.x,
y: Math.floor(bar.y * 100) / 100,
}))
)
).to.eql(expectedData);
if (chartType === 'lines') {
expect(
state?.lines
?.map(({ points }) =>
points
.map((point) => ({ x: point.x, y: Math.floor(point.y * 100) / 100 }))
.sort(({ x }, { x: x2 }) => x - x2)
)
.filter((a) => a.length > 0)
).to.eql(expectedData);
} else {
expect(
state?.bars?.map(({ bars }) =>
bars.map((point) => ({ x: point.x, y: Math.floor(point.y * 100) / 100 }))
)
).to.eql(expectedData);
}
}

describe('lens with multiple data views', () => {
Expand Down Expand Up @@ -93,13 +103,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await testSubjects.click('fieldToggle-DistanceKilometers');

const data = await lens.getCurrentChartDebugState('xyVisChart');
assertMatchesExpectedData(data, [expectedLogstashData, expectedFlightsData]);
assertMatchesExpectedData(data, [expectedLogstashData, expectedFlightsData], 'lines');
});

it('ignores global filters on layers using a data view without the filter field', async () => {
await filterBar.addFilter({ field: 'Carrier', operation: 'exists' });
const data = await lens.getCurrentChartDebugState('xyVisChart');
assertMatchesExpectedData(data, [expectedLogstashData, expectedFlightsData]);
assertMatchesExpectedData(data, [expectedLogstashData, expectedFlightsData], 'lines');
await lens.save(visTitle);
});

Expand All @@ -110,7 +120,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {

await visualize.openSavedVisualization(visTitle);
const data = await lens.getCurrentChartDebugState('xyVisChart');
assertMatchesExpectedData(data, [expectedFlightsData]);
assertMatchesExpectedData(data, [expectedFlightsData], 'lines');
});
});
}

0 comments on commit 2d744ff

Please sign in to comment.