mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[XY] Mark size configuration. (#130361)
* Added tests for the case when markSizeRatio and markSizeAccessor are specified. * Added markSizeAccessor to extendedDataLayer and xyVis. * Fixed markSizeRatio default value. * Added `size` support from `pointseries`. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d9e6ef3f23
commit
dd8bd6fdb3
21 changed files with 438 additions and 42 deletions
|
@ -10,7 +10,7 @@ import { Position } from '@elastic/charts';
|
|||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { Datatable, DatatableRow } from '@kbn/expressions-plugin';
|
||||
import { LayerTypes } from '../constants';
|
||||
import { DataLayerConfig, XYProps } from '../types';
|
||||
import { DataLayerConfig, ExtendedDataLayerConfig, XYProps } from '../types';
|
||||
|
||||
export const mockPaletteOutput: PaletteOutput = {
|
||||
type: 'palette',
|
||||
|
@ -35,7 +35,7 @@ export const createSampleDatatableWithRows = (rows: DatatableRow[]): Datatable =
|
|||
id: 'c',
|
||||
name: 'c',
|
||||
meta: {
|
||||
type: 'date',
|
||||
type: 'string',
|
||||
field: 'order_date',
|
||||
sourceParams: { type: 'date-histogram', params: { interval: 'auto' } },
|
||||
params: { id: 'string' },
|
||||
|
@ -61,6 +61,21 @@ export const sampleLayer: DataLayerConfig = {
|
|||
table: createSampleDatatableWithRows([]),
|
||||
};
|
||||
|
||||
export const sampleExtendedLayer: ExtendedDataLayerConfig = {
|
||||
layerId: 'first',
|
||||
type: 'extendedDataLayer',
|
||||
layerType: LayerTypes.DATA,
|
||||
seriesType: 'line',
|
||||
xAccessor: 'c',
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: 'd',
|
||||
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
|
||||
xScaleType: 'ordinal',
|
||||
isHistogram: false,
|
||||
palette: mockPaletteOutput,
|
||||
table: createSampleDatatableWithRows([]),
|
||||
};
|
||||
|
||||
export const createArgsWithLayers = (
|
||||
layers: DataLayerConfig | DataLayerConfig[] = sampleLayer
|
||||
): XYProps => ({
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`extendedDataLayerConfig throws the error if markSizeAccessor doesn't have the corresponding column in the table 1`] = `"Provided column name or index is invalid: nonsense"`;
|
||||
|
||||
exports[`extendedDataLayerConfig throws the error if markSizeAccessor is provided to the not line/area chart 1`] = `"\`markSizeAccessor\` can't be used. Dots are applied only for line or area charts"`;
|
|
@ -0,0 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`layeredXyVis it should throw error if markSizeRatio is lower then 1 or greater then 100 1`] = `"Mark size ratio must be greater or equal to 1 and less or equal to 100"`;
|
||||
|
||||
exports[`layeredXyVis it should throw error if markSizeRatio is lower then 1 or greater then 100 2`] = `"Mark size ratio must be greater or equal to 1 and less or equal to 100"`;
|
||||
|
||||
exports[`layeredXyVis it should throw error if markSizeRatio is specified if no markSizeAccessor is present 1`] = `"Mark size ratio can be applied only with \`markSizeAccessor\`"`;
|
|
@ -1,5 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`xyVis it should throw error if markSizeRatio is lower then 1 or greater then 100 1`] = `"Mark size ratio must be greater or equal to 1 and less or equal to 100"`;
|
||||
|
||||
exports[`xyVis it should throw error if markSizeRatio is lower then 1 or greater then 100 2`] = `"Mark size ratio must be greater or equal to 1 and less or equal to 100"`;
|
||||
|
||||
exports[`xyVis it should throw error if markSizeRatio is specified while markSizeAccessor is not 1`] = `"Mark size ratio can be applied only with \`markSizeAccessor\`"`;
|
||||
|
||||
exports[`xyVis it should throw error if minTimeBarInterval applied for not time bar chart 1`] = `"\`minTimeBarInterval\` argument is applicable only for time bar charts."`;
|
||||
|
||||
exports[`xyVis it should throw error if minTimeBarInterval is invalid 1`] = `"Provided x-axis interval is invalid. The interval should include quantity and unit names. Examples: 1d, 24h, 1w."`;
|
||||
|
|
|
@ -128,6 +128,10 @@ export const commonXYArgs: CommonXYFn['args'] = {
|
|||
types: ['string'],
|
||||
help: strings.getAriaLabelHelp(),
|
||||
},
|
||||
markSizeRatio: {
|
||||
types: ['number'],
|
||||
help: strings.getMarkSizeRatioHelp(),
|
||||
},
|
||||
minTimeBarInterval: {
|
||||
types: ['string'],
|
||||
help: strings.getMinTimeBarIntervalHelp(),
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExtendedDataLayerArgs } from '../types';
|
||||
import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks';
|
||||
import { mockPaletteOutput, sampleArgs } from '../__mocks__';
|
||||
import { LayerTypes } from '../constants';
|
||||
import { extendedDataLayerFunction } from './extended_data_layer';
|
||||
|
||||
describe('extendedDataLayerConfig', () => {
|
||||
test('produces the correct arguments', async () => {
|
||||
const { data } = sampleArgs();
|
||||
const args: ExtendedDataLayerArgs = {
|
||||
seriesType: 'line',
|
||||
xAccessor: 'c',
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: 'd',
|
||||
xScaleType: 'linear',
|
||||
isHistogram: false,
|
||||
palette: mockPaletteOutput,
|
||||
markSizeAccessor: 'b',
|
||||
};
|
||||
|
||||
const result = await extendedDataLayerFunction.fn(data, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'extendedDataLayer',
|
||||
layerType: LayerTypes.DATA,
|
||||
...args,
|
||||
table: data,
|
||||
});
|
||||
});
|
||||
|
||||
test('throws the error if markSizeAccessor is provided to the not line/area chart', async () => {
|
||||
const { data } = sampleArgs();
|
||||
const args: ExtendedDataLayerArgs = {
|
||||
seriesType: 'bar',
|
||||
xAccessor: 'c',
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: 'd',
|
||||
xScaleType: 'linear',
|
||||
isHistogram: false,
|
||||
palette: mockPaletteOutput,
|
||||
markSizeAccessor: 'b',
|
||||
};
|
||||
|
||||
expect(
|
||||
extendedDataLayerFunction.fn(data, args, createMockExecutionContext())
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test("throws the error if markSizeAccessor doesn't have the corresponding column in the table", async () => {
|
||||
const { data } = sampleArgs();
|
||||
const args: ExtendedDataLayerArgs = {
|
||||
seriesType: 'line',
|
||||
xAccessor: 'c',
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: 'd',
|
||||
xScaleType: 'linear',
|
||||
isHistogram: false,
|
||||
palette: mockPaletteOutput,
|
||||
markSizeAccessor: 'nonsense',
|
||||
};
|
||||
|
||||
expect(
|
||||
extendedDataLayerFunction.fn(data, args, createMockExecutionContext())
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
|
@ -32,6 +32,10 @@ export const extendedDataLayerFunction: ExtendedDataLayerFn = {
|
|||
help: strings.getAccessorsHelp(),
|
||||
multi: true,
|
||||
},
|
||||
markSizeAccessor: {
|
||||
types: ['string'],
|
||||
help: strings.getMarkSizeAccessorHelp(),
|
||||
},
|
||||
table: {
|
||||
types: ['datatable'],
|
||||
help: strings.getTableHelp(),
|
||||
|
|
|
@ -10,6 +10,7 @@ import { validateAccessor } from '@kbn/visualizations-plugin/common/utils';
|
|||
import { ExtendedDataLayerArgs, ExtendedDataLayerFn } from '../types';
|
||||
import { EXTENDED_DATA_LAYER, LayerTypes } from '../constants';
|
||||
import { getAccessors, normalizeTable } from '../helpers';
|
||||
import { validateMarkSizeForChartType } from './validate';
|
||||
|
||||
export const extendedDataLayerFn: ExtendedDataLayerFn['fn'] = async (data, args, context) => {
|
||||
const table = args.table ?? data;
|
||||
|
@ -18,6 +19,8 @@ export const extendedDataLayerFn: ExtendedDataLayerFn['fn'] = async (data, args,
|
|||
validateAccessor(accessors.xAccessor, table.columns);
|
||||
validateAccessor(accessors.splitAccessor, table.columns);
|
||||
accessors.accessors.forEach((accessor) => validateAccessor(accessor, table.columns));
|
||||
validateMarkSizeForChartType(args.markSizeAccessor, args.seriesType);
|
||||
validateAccessor(args.markSizeAccessor, table.columns);
|
||||
|
||||
const normalizedTable = normalizeTable(table, accessors.xAccessor);
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { layeredXyVisFunction } from '.';
|
||||
import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks';
|
||||
import { sampleArgs, sampleExtendedLayer } from '../__mocks__';
|
||||
import { XY_VIS } from '../constants';
|
||||
|
||||
describe('layeredXyVis', () => {
|
||||
test('it renders with the specified data and args', async () => {
|
||||
const { data, args } = sampleArgs();
|
||||
const { layers, ...rest } = args;
|
||||
const result = await layeredXyVisFunction.fn(
|
||||
data,
|
||||
{ ...rest, layers: [sampleExtendedLayer] },
|
||||
createMockExecutionContext()
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'render',
|
||||
as: XY_VIS,
|
||||
value: { args: { ...rest, layers: [sampleExtendedLayer] } },
|
||||
});
|
||||
});
|
||||
|
||||
test('it should throw error if markSizeRatio is lower then 1 or greater then 100', async () => {
|
||||
const { data, args } = sampleArgs();
|
||||
const { layers, ...rest } = args;
|
||||
|
||||
expect(
|
||||
layeredXyVisFunction.fn(
|
||||
data,
|
||||
{
|
||||
...rest,
|
||||
markSizeRatio: 0,
|
||||
layers: [sampleExtendedLayer],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
)
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
|
||||
expect(
|
||||
layeredXyVisFunction.fn(
|
||||
data,
|
||||
{
|
||||
...rest,
|
||||
markSizeRatio: 101,
|
||||
layers: [sampleExtendedLayer],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
)
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('it should throw error if markSizeRatio is specified if no markSizeAccessor is present', async () => {
|
||||
const { data, args } = sampleArgs();
|
||||
const { layers, ...rest } = args;
|
||||
|
||||
expect(
|
||||
layeredXyVisFunction.fn(
|
||||
data,
|
||||
{
|
||||
...rest,
|
||||
markSizeRatio: 10,
|
||||
layers: [sampleExtendedLayer],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
)
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
|
@ -10,7 +10,12 @@ import { XY_VIS_RENDERER } from '../constants';
|
|||
import { appendLayerIds, getDataLayers } from '../helpers';
|
||||
import { LayeredXyVisFn } from '../types';
|
||||
import { logDatatables } from '../utils';
|
||||
import { validateMinTimeBarInterval, hasBarLayer } from './validate';
|
||||
import {
|
||||
validateMarkSizeRatioLimits,
|
||||
validateMinTimeBarInterval,
|
||||
hasBarLayer,
|
||||
errors,
|
||||
} from './validate';
|
||||
|
||||
export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) => {
|
||||
const layers = appendLayerIds(args.layers ?? [], 'layers');
|
||||
|
@ -19,7 +24,14 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers)
|
|||
|
||||
const dataLayers = getDataLayers(layers);
|
||||
const hasBar = hasBarLayer(dataLayers);
|
||||
validateMarkSizeRatioLimits(args.markSizeRatio);
|
||||
validateMinTimeBarInterval(dataLayers, hasBar, args.minTimeBarInterval);
|
||||
const hasMarkSizeAccessors =
|
||||
dataLayers.filter((dataLayer) => dataLayer.markSizeAccessor !== undefined).length > 0;
|
||||
|
||||
if (!hasMarkSizeAccessors && args.markSizeRatio !== undefined) {
|
||||
throw new Error(errors.markSizeRatioWithoutAccessor());
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
|
@ -28,6 +40,7 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers)
|
|||
args: {
|
||||
...args,
|
||||
layers,
|
||||
markSizeRatio: hasMarkSizeAccessors && !args.markSizeRatio ? 10 : args.markSizeRatio,
|
||||
ariaLabel:
|
||||
args.ariaLabel ??
|
||||
(handlers.variables?.embeddableTitle as string) ??
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isValidInterval } from '@kbn/data-plugin/common';
|
||||
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
|
||||
import { AxisExtentModes, ValueLabelModes } from '../constants';
|
||||
import {
|
||||
SeriesType,
|
||||
AxisExtentConfigResult,
|
||||
DataLayerConfigResult,
|
||||
CommonXYDataLayerConfigResult,
|
||||
|
@ -18,7 +20,23 @@ import {
|
|||
} from '../types';
|
||||
import { isTimeChart } from '../helpers';
|
||||
|
||||
const errors = {
|
||||
export const errors = {
|
||||
markSizeAccessorForNonLineOrAreaChartsError: () =>
|
||||
i18n.translate(
|
||||
'expressionXY.reusable.function.dataLayer.errors.markSizeAccessorForNonLineOrAreaChartsError',
|
||||
{
|
||||
defaultMessage:
|
||||
"`markSizeAccessor` can't be used. Dots are applied only for line or area charts",
|
||||
}
|
||||
),
|
||||
markSizeRatioLimitsError: () =>
|
||||
i18n.translate('expressionXY.reusable.function.xyVis.errors.markSizeLimitsError', {
|
||||
defaultMessage: 'Mark size ratio must be greater or equal to 1 and less or equal to 100',
|
||||
}),
|
||||
markSizeRatioWithoutAccessor: () =>
|
||||
i18n.translate('expressionXY.reusable.function.xyVis.errors.markSizeRatioWithoutAccessor', {
|
||||
defaultMessage: 'Mark size ratio can be applied only with `markSizeAccessor`',
|
||||
}),
|
||||
extendBoundsAreInvalidError: () =>
|
||||
i18n.translate('expressionXY.reusable.function.xyVis.errors.extendBoundsAreInvalidError', {
|
||||
defaultMessage:
|
||||
|
@ -117,6 +135,30 @@ export const validateValueLabels = (
|
|||
}
|
||||
};
|
||||
|
||||
export const validateMarkSizeForChartType = (
|
||||
markSizeAccessor: ExpressionValueVisDimension | string | undefined,
|
||||
seriesType: SeriesType
|
||||
) => {
|
||||
if (markSizeAccessor && !seriesType.includes('line') && !seriesType.includes('area')) {
|
||||
throw new Error(errors.markSizeAccessorForNonLineOrAreaChartsError());
|
||||
}
|
||||
};
|
||||
|
||||
export const validateMarkSizeRatioLimits = (markSizeRatio?: number) => {
|
||||
if (markSizeRatio !== undefined && (markSizeRatio < 1 || markSizeRatio > 100)) {
|
||||
throw new Error(errors.markSizeRatioLimitsError());
|
||||
}
|
||||
};
|
||||
|
||||
export const validateMarkSizeRatioWithAccessor = (
|
||||
markSizeRatio: number | undefined,
|
||||
markSizeAccessor: ExpressionValueVisDimension | string | undefined
|
||||
) => {
|
||||
if (markSizeRatio !== undefined && !markSizeAccessor) {
|
||||
throw new Error(errors.markSizeRatioWithoutAccessor());
|
||||
}
|
||||
};
|
||||
|
||||
export const validateMinTimeBarInterval = (
|
||||
dataLayers: CommonXYDataLayerConfigResult[],
|
||||
hasBar: boolean,
|
||||
|
|
|
@ -50,6 +50,37 @@ describe('xyVis', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('it should throw error if markSizeRatio is lower then 1 or greater then 100', async () => {
|
||||
const { data, args } = sampleArgs();
|
||||
const { layers, ...rest } = args;
|
||||
expect(
|
||||
xyVisFunction.fn(
|
||||
data,
|
||||
{
|
||||
...rest,
|
||||
...{ ...sampleLayer, markSizeAccessor: 'b' },
|
||||
markSizeRatio: 0,
|
||||
referenceLineLayers: [],
|
||||
annotationLayers: [],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
)
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
|
||||
expect(
|
||||
xyVisFunction.fn(
|
||||
data,
|
||||
{
|
||||
...rest,
|
||||
...{ ...sampleLayer, markSizeAccessor: 'b' },
|
||||
markSizeRatio: 101,
|
||||
referenceLineLayers: [],
|
||||
annotationLayers: [],
|
||||
},
|
||||
createMockExecutionContext()
|
||||
)
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
test('it should throw error if minTimeBarInterval is invalid', async () => {
|
||||
const { data, args } = sampleArgs();
|
||||
const { layers, ...rest } = args;
|
||||
|
@ -129,4 +160,24 @@ describe('xyVis', () => {
|
|||
)
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('it should throw error if markSizeRatio is specified while markSizeAccessor is not', async () => {
|
||||
const { data, args } = sampleArgs();
|
||||
const { layers, ...rest } = args;
|
||||
const { layerId, layerType, table, type, ...restLayerArgs } = sampleLayer;
|
||||
|
||||
expect(
|
||||
xyVisFunction.fn(
|
||||
data,
|
||||
{
|
||||
...rest,
|
||||
...restLayerArgs,
|
||||
referenceLineLayers: [],
|
||||
annotationLayers: [],
|
||||
markSizeRatio: 5,
|
||||
},
|
||||
createMockExecutionContext()
|
||||
)
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -51,6 +51,10 @@ export const xyVisFunction: XyVisFn = {
|
|||
types: ['vis_dimension', 'string'],
|
||||
help: strings.getSplitRowAccessorHelp(),
|
||||
},
|
||||
markSizeAccessor: {
|
||||
types: ['vis_dimension', 'string'],
|
||||
help: strings.getMarkSizeAccessorHelp(),
|
||||
},
|
||||
},
|
||||
async fn(data, args, handlers) {
|
||||
const { xyVisFn } = await import('./xy_vis_fn');
|
||||
|
|
|
@ -23,8 +23,11 @@ import {
|
|||
hasHistogramBarLayer,
|
||||
validateExtent,
|
||||
validateFillOpacity,
|
||||
validateMarkSizeRatioLimits,
|
||||
validateValueLabels,
|
||||
validateMinTimeBarInterval,
|
||||
validateMarkSizeForChartType,
|
||||
validateMarkSizeRatioWithAccessor,
|
||||
} from './validate';
|
||||
|
||||
const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult => {
|
||||
|
@ -63,6 +66,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
|
|||
isHistogram,
|
||||
yConfig,
|
||||
palette,
|
||||
markSizeAccessor,
|
||||
...restArgs
|
||||
} = args;
|
||||
|
||||
|
@ -72,6 +76,9 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
|
|||
validateAccessor(dataLayers[0].splitAccessor, data.columns);
|
||||
dataLayers[0].accessors.forEach((accessor) => validateAccessor(accessor, data.columns));
|
||||
|
||||
validateMarkSizeForChartType(dataLayers[0].markSizeAccessor, args.seriesType);
|
||||
validateAccessor(dataLayers[0].markSizeAccessor, data.columns);
|
||||
|
||||
const layers: XYLayerConfig[] = [
|
||||
...appendLayerIds(dataLayers, 'dataLayers'),
|
||||
...appendLayerIds(referenceLineLayers, 'referenceLineLayers'),
|
||||
|
@ -105,6 +112,8 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
|
|||
const hasNotHistogramBars = !hasHistogramBarLayer(dataLayers);
|
||||
|
||||
validateValueLabels(args.valueLabels, hasBar, hasNotHistogramBars);
|
||||
validateMarkSizeRatioWithAccessor(args.markSizeRatio, dataLayers[0].markSizeAccessor);
|
||||
validateMarkSizeRatioLimits(args.markSizeRatio);
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
|
@ -113,6 +122,8 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
|
|||
args: {
|
||||
...restArgs,
|
||||
layers,
|
||||
markSizeRatio:
|
||||
dataLayers[0].markSizeAccessor && !args.markSizeRatio ? 10 : args.markSizeRatio,
|
||||
ariaLabel:
|
||||
args.ariaLabel ??
|
||||
(handlers.variables?.embeddableTitle as string) ??
|
||||
|
|
|
@ -35,19 +35,24 @@ export function getDataLayers(layers: XYExtendedLayerConfigResult[]) {
|
|||
);
|
||||
}
|
||||
|
||||
export function getAccessors<T, U extends { splitAccessor?: T; xAccessor?: T; accessors: T[] }>(
|
||||
args: U,
|
||||
table: Datatable
|
||||
) {
|
||||
export function getAccessors<
|
||||
T,
|
||||
U extends { splitAccessor?: T; xAccessor?: T; accessors: T[]; markSizeAccessor?: T }
|
||||
>(args: U, table: Datatable) {
|
||||
let splitAccessor: T | string | undefined = args.splitAccessor;
|
||||
let xAccessor: T | string | undefined = args.xAccessor;
|
||||
let accessors: Array<T | string> = args.accessors ?? [];
|
||||
if (!splitAccessor && !xAccessor && !(accessors && accessors.length)) {
|
||||
let markSizeAccessor: T | string | undefined = args.markSizeAccessor;
|
||||
|
||||
if (!splitAccessor && !xAccessor && !(accessors && accessors.length) && !markSizeAccessor) {
|
||||
const y = table.columns.find((column) => column.id === PointSeriesColumnNames.Y)?.id;
|
||||
xAccessor = table.columns.find((column) => column.id === PointSeriesColumnNames.X)?.id;
|
||||
splitAccessor = table.columns.find((column) => column.id === PointSeriesColumnNames.COLOR)?.id;
|
||||
accessors = y ? [y] : [];
|
||||
markSizeAccessor = table.columns.find(
|
||||
(column) => column.id === PointSeriesColumnNames.SIZE
|
||||
)?.id;
|
||||
}
|
||||
|
||||
return { splitAccessor, xAccessor, accessors };
|
||||
return { splitAccessor, xAccessor, accessors, markSizeAccessor };
|
||||
}
|
||||
|
|
|
@ -121,6 +121,10 @@ export const strings = {
|
|||
i18n.translate('expressionXY.xyVis.ariaLabel.help', {
|
||||
defaultMessage: 'Specifies the aria label of the xy chart',
|
||||
}),
|
||||
getMarkSizeRatioHelp: () =>
|
||||
i18n.translate('expressionXY.xyVis.markSizeRatio.help', {
|
||||
defaultMessage: 'Specifies the ratio of the dots at the line and area charts',
|
||||
}),
|
||||
getMinTimeBarIntervalHelp: () =>
|
||||
i18n.translate('expressionXY.xyVis.xAxisInterval.help', {
|
||||
defaultMessage: 'Specifies the min interval for time bar chart',
|
||||
|
@ -169,6 +173,10 @@ export const strings = {
|
|||
i18n.translate('expressionXY.dataLayer.accessors.help', {
|
||||
defaultMessage: 'The columns to display on the y axis.',
|
||||
}),
|
||||
getMarkSizeAccessorHelp: () =>
|
||||
i18n.translate('expressionXY.dataLayer.markSizeAccessor.help', {
|
||||
defaultMessage: 'Mark size accessor',
|
||||
}),
|
||||
getYConfigHelp: () =>
|
||||
i18n.translate('expressionXY.dataLayer.yConfig.help', {
|
||||
defaultMessage: 'Additional configuration for y axes',
|
||||
|
|
|
@ -100,6 +100,7 @@ export interface DataLayerArgs {
|
|||
xAccessor?: string | ExpressionValueVisDimension;
|
||||
hide?: boolean;
|
||||
splitAccessor?: string | ExpressionValueVisDimension;
|
||||
markSizeAccessor?: string | ExpressionValueVisDimension;
|
||||
columnToLabel?: string; // Actually a JSON key-value pair
|
||||
xScaleType: XScaleType;
|
||||
isHistogram: boolean;
|
||||
|
@ -118,10 +119,12 @@ export interface ExtendedDataLayerArgs {
|
|||
xAccessor?: string;
|
||||
hide?: boolean;
|
||||
splitAccessor?: string;
|
||||
markSizeAccessor?: string;
|
||||
columnToLabel?: string; // Actually a JSON key-value pair
|
||||
xScaleType: XScaleType;
|
||||
isHistogram: boolean;
|
||||
palette: PaletteOutput;
|
||||
// palette will always be set on the expression
|
||||
yConfig?: YConfigResult[];
|
||||
table?: Datatable;
|
||||
}
|
||||
|
@ -203,6 +206,7 @@ export interface XYArgs extends DataLayerArgs {
|
|||
hideEndzones?: boolean;
|
||||
valuesInLegend?: boolean;
|
||||
ariaLabel?: string;
|
||||
markSizeRatio?: number;
|
||||
minTimeBarInterval?: string;
|
||||
splitRowAccessor?: ExpressionValueVisDimension | string;
|
||||
splitColumnAccessor?: ExpressionValueVisDimension | string;
|
||||
|
@ -231,6 +235,7 @@ export interface LayeredXYArgs {
|
|||
hideEndzones?: boolean;
|
||||
valuesInLegend?: boolean;
|
||||
ariaLabel?: string;
|
||||
markSizeRatio?: number;
|
||||
minTimeBarInterval?: string;
|
||||
}
|
||||
|
||||
|
@ -257,6 +262,7 @@ export interface XYProps {
|
|||
hideEndzones?: boolean;
|
||||
valuesInLegend?: boolean;
|
||||
ariaLabel?: string;
|
||||
markSizeRatio?: number;
|
||||
minTimeBarInterval?: string;
|
||||
splitRowAccessor?: ExpressionValueVisDimension | string;
|
||||
splitColumnAccessor?: ExpressionValueVisDimension | string;
|
||||
|
|
|
@ -324,6 +324,7 @@ exports[`XYChart component it renders area 1`] = `
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -645,7 +646,7 @@ exports[`XYChart component it renders area 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -735,7 +736,7 @@ exports[`XYChart component it renders area 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -868,6 +869,7 @@ exports[`XYChart component it renders bar 1`] = `
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -1189,7 +1191,7 @@ exports[`XYChart component it renders bar 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -1279,7 +1281,7 @@ exports[`XYChart component it renders bar 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -1412,6 +1414,7 @@ exports[`XYChart component it renders horizontal bar 1`] = `
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -1733,7 +1736,7 @@ exports[`XYChart component it renders horizontal bar 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -1823,7 +1826,7 @@ exports[`XYChart component it renders horizontal bar 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -1956,6 +1959,7 @@ exports[`XYChart component it renders line 1`] = `
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -2277,7 +2281,7 @@ exports[`XYChart component it renders line 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -2367,7 +2371,7 @@ exports[`XYChart component it renders line 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -2500,6 +2504,7 @@ exports[`XYChart component it renders stacked area 1`] = `
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -2821,7 +2826,7 @@ exports[`XYChart component it renders stacked area 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -2911,7 +2916,7 @@ exports[`XYChart component it renders stacked area 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -3044,6 +3049,7 @@ exports[`XYChart component it renders stacked bar 1`] = `
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -3365,7 +3371,7 @@ exports[`XYChart component it renders stacked bar 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -3455,7 +3461,7 @@ exports[`XYChart component it renders stacked bar 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -3588,6 +3594,7 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = `
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -3909,7 +3916,7 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -3999,7 +4006,7 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = `
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -4132,6 +4139,7 @@ exports[`XYChart component split chart should render split chart if both, splitR
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -4210,7 +4218,7 @@ exports[`XYChart component split chart should render split chart if both, splitR
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -4708,7 +4716,7 @@ exports[`XYChart component split chart should render split chart if both, splitR
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -4798,7 +4806,7 @@ exports[`XYChart component split chart should render split chart if both, splitR
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -4931,6 +4939,7 @@ exports[`XYChart component split chart should render split chart if splitColumnA
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -5009,7 +5018,7 @@ exports[`XYChart component split chart should render split chart if splitColumnA
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -5506,7 +5515,7 @@ exports[`XYChart component split chart should render split chart if splitColumnA
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -5596,7 +5605,7 @@ exports[`XYChart component split chart should render split chart if splitColumnA
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -5729,6 +5738,7 @@ exports[`XYChart component split chart should render split chart if splitRowAcce
|
|||
"maxLines": 0,
|
||||
},
|
||||
},
|
||||
"markSizeRatio": undefined,
|
||||
}
|
||||
}
|
||||
tooltip={
|
||||
|
@ -5807,7 +5817,7 @@ exports[`XYChart component split chart should render split chart if splitRowAcce
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -6304,7 +6314,7 @@ exports[`XYChart component split chart should render split chart if splitRowAcce
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
@ -6394,7 +6404,7 @@ exports[`XYChart component split chart should render split chart if splitRowAcce
|
|||
},
|
||||
"type": "date-histogram",
|
||||
},
|
||||
"type": "date",
|
||||
"type": "string",
|
||||
},
|
||||
"name": "c",
|
||||
},
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
AreaSeries,
|
||||
Axis,
|
||||
BarSeries,
|
||||
ColorVariant,
|
||||
Fit,
|
||||
GeometryValue,
|
||||
GroupBy,
|
||||
|
@ -687,6 +688,40 @@ describe('XYChart component', () => {
|
|||
expect(component.find(Settings).at(0).prop('showLegendExtra')).toEqual(true);
|
||||
});
|
||||
|
||||
test('applies the mark size ratio', () => {
|
||||
const { args } = sampleArgs();
|
||||
const markSizeRatioArg = { markSizeRatio: 50 };
|
||||
const component = shallow(
|
||||
<XYChart {...defaultProps} args={{ ...args, ...markSizeRatioArg }} />
|
||||
);
|
||||
expect(component.find(Settings).at(0).prop('theme')).toEqual(
|
||||
expect.objectContaining(markSizeRatioArg)
|
||||
);
|
||||
});
|
||||
|
||||
test('applies the mark size accessor', () => {
|
||||
const { args } = sampleArgs();
|
||||
const markSizeAccessorArg = { markSizeAccessor: 'b' };
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
{...defaultProps}
|
||||
args={{ ...args, layers: [{ ...args.layers[0], ...markSizeAccessorArg }] }}
|
||||
/>
|
||||
);
|
||||
const dataLayers = component.find(DataLayers).dive();
|
||||
const lineArea = dataLayers.find(LineSeries).at(0);
|
||||
expect(lineArea.prop('markSizeAccessor')).toEqual(markSizeAccessorArg.markSizeAccessor);
|
||||
const expectedSeriesStyle = expect.objectContaining({
|
||||
point: expect.objectContaining({
|
||||
visible: true,
|
||||
fill: ColorVariant.Series,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lineArea.prop('areaSeriesStyle')).toEqual(expectedSeriesStyle);
|
||||
expect(lineArea.prop('lineSeriesStyle')).toEqual(expectedSeriesStyle);
|
||||
});
|
||||
|
||||
test('it renders bar', () => {
|
||||
const { args } = sampleArgs();
|
||||
const component = shallow(
|
||||
|
@ -2132,6 +2167,7 @@ describe('XYChart component', () => {
|
|||
mode: 'full',
|
||||
type: 'axisExtentConfig',
|
||||
},
|
||||
markSizeRatio: 1,
|
||||
layers: [
|
||||
{
|
||||
layerId: 'first',
|
||||
|
@ -2219,6 +2255,7 @@ describe('XYChart component', () => {
|
|||
mode: 'full',
|
||||
type: 'axisExtentConfig',
|
||||
},
|
||||
markSizeRatio: 1,
|
||||
yLeftScale: 'linear',
|
||||
yRightScale: 'linear',
|
||||
layers: [
|
||||
|
@ -2292,6 +2329,7 @@ describe('XYChart component', () => {
|
|||
mode: 'full',
|
||||
type: 'axisExtentConfig',
|
||||
},
|
||||
markSizeRatio: 1,
|
||||
yLeftScale: 'linear',
|
||||
yRightScale: 'linear',
|
||||
layers: [
|
||||
|
|
|
@ -48,17 +48,15 @@ import {
|
|||
getAnnotationsLayers,
|
||||
getDataLayers,
|
||||
Series,
|
||||
getFormattedTablesByLayers,
|
||||
validateExtent,
|
||||
getFormat,
|
||||
} from '../helpers';
|
||||
import {
|
||||
getFormattedTablesByLayers,
|
||||
getFilteredLayers,
|
||||
getReferenceLayers,
|
||||
isDataLayer,
|
||||
getAxesConfiguration,
|
||||
GroupsConfiguration,
|
||||
getLinesCausedPaddings,
|
||||
validateExtent,
|
||||
} from '../helpers';
|
||||
import { getXDomain, XyEndzones } from './x_domain';
|
||||
import { getLegendAction } from './legend_action';
|
||||
|
@ -571,6 +569,7 @@ export function XYChart({
|
|||
shouldRotate
|
||||
),
|
||||
},
|
||||
markSizeRatio: args.markSizeRatio,
|
||||
}}
|
||||
baseTheme={chartBaseTheme}
|
||||
tooltip={{
|
||||
|
|
|
@ -226,9 +226,14 @@ const getSeriesName: GetSeriesNameFn = (
|
|||
return splitColumnId ? data.seriesKeys[0] : columnToLabelMap[data.seriesKeys[0]] ?? null;
|
||||
};
|
||||
|
||||
const getPointConfig = (xAccessor?: string, emphasizeFitting?: boolean) => ({
|
||||
visible: !xAccessor,
|
||||
const getPointConfig = (
|
||||
xAccessor: string | undefined,
|
||||
markSizeAccessor: string | undefined,
|
||||
emphasizeFitting?: boolean
|
||||
) => ({
|
||||
visible: !xAccessor || markSizeAccessor !== undefined,
|
||||
radius: xAccessor && !emphasizeFitting ? 5 : 0,
|
||||
fill: markSizeAccessor ? ColorVariant.Series : undefined,
|
||||
});
|
||||
|
||||
const getLineConfig = () => ({ visible: true, stroke: ColorVariant.Series, opacity: 1, dash: [] });
|
||||
|
@ -276,7 +281,7 @@ export const getSeriesProps: GetSeriesPropsFn = ({
|
|||
fillOpacity,
|
||||
formattedDatatableInfo,
|
||||
}): SeriesSpec => {
|
||||
const { table } = layer;
|
||||
const { table, markSizeAccessor } = layer;
|
||||
const isStacked = layer.seriesType.includes('stacked');
|
||||
const isPercentage = layer.seriesType.includes('percentage');
|
||||
const isBarChart = layer.seriesType.includes('bar');
|
||||
|
@ -294,6 +299,14 @@ export const getSeriesProps: GetSeriesPropsFn = ({
|
|||
: undefined;
|
||||
const splitFormatter = formatFactory(splitHint);
|
||||
|
||||
const markSizeColumnId = markSizeAccessor
|
||||
? getAccessorByDimension(markSizeAccessor, table.columns)
|
||||
: undefined;
|
||||
|
||||
const markFormatter = formatFactory(
|
||||
markSizeAccessor ? getFormat(table.columns, markSizeAccessor) : undefined
|
||||
);
|
||||
|
||||
// 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
|
||||
|
@ -326,6 +339,8 @@ export const getSeriesProps: GetSeriesPropsFn = ({
|
|||
id: splitColumnId ? `${splitColumnId}-${accessor}` : accessor,
|
||||
xAccessor: xColumnId || 'unifiedX',
|
||||
yAccessors: [accessor],
|
||||
markSizeAccessor: markSizeColumnId,
|
||||
markFormat: (value) => markFormatter.convert(value),
|
||||
data: rows,
|
||||
xScaleType: xColumnId ? layer.xScaleType : 'ordinal',
|
||||
yScaleType:
|
||||
|
@ -346,14 +361,14 @@ export const getSeriesProps: GetSeriesPropsFn = ({
|
|||
stackMode: isPercentage ? StackMode.Percentage : undefined,
|
||||
timeZone,
|
||||
areaSeriesStyle: {
|
||||
point: getPointConfig(xColumnId, emphasizeFitting),
|
||||
point: getPointConfig(xColumnId, markSizeColumnId, emphasizeFitting),
|
||||
...(fillOpacity && { area: { opacity: fillOpacity } }),
|
||||
...(emphasizeFitting && {
|
||||
fit: { area: { opacity: fillOpacity || 0.5 }, line: getLineConfig() },
|
||||
}),
|
||||
},
|
||||
lineSeriesStyle: {
|
||||
point: getPointConfig(xColumnId, emphasizeFitting),
|
||||
point: getPointConfig(xColumnId, markSizeColumnId, emphasizeFitting),
|
||||
...(emphasizeFitting && { fit: { line: getLineConfig() } }),
|
||||
},
|
||||
name(d) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue