[XY] Support vis_dimension type for accessors (#129612)

* added xy plugin.

* Added expressionXY limits.

* Added xy expression functions to the expression_xy plugin.

* Moved xy to a separate plugin.

* Small refactoring.

* Fixed types.

* Fixed import of scss.

* Fixed imports.

* Added required plugins.

* Fixed import

* Fixed types.

* Changed expression names.

* Fixed bugs, caused by the refactoring process.

* Fixed lens snapshots.

* Removed new line.

* Fixed xy_chart tests.

* Added lazy loading for xy chart.

* Fixed xy chart test.

* Fixed broken chart selectors.

* Fixed dashboard tests.

* dashboard test fixed.

* Fixed heatmap vis.

* Smokescreen test fixed.

* more fixes.

* async dashboard tests fixed.

* Fixed xy smokescreen tests selectors.

* fixed show_underlying_data tests.

* Updated snapshots.

* updated limits.

* Fixed more selectors

* Fixed persistent context test.

* Fixed some more test at ml.

* Fixed types and imports

* Fixed handlers.inspectorAdapters.tables.logDatatable

* Fixed logDatatable

* Translations fixed.

* Fixed "Visualize App ... cleans filters and query" test.

cleans filters and query

* Fixed "lens disable auto-apply tests" test.

* Updated dashboard tests.

* Fixed translations.

* Expression tests fixed.

* Cleaned up expression_xy.

* cleaned up lens xy_visualization.

* fixed more tests.

* Fix of tsvb.

* Fixed more tests.

* Fixed xy chart limits.

* Fixed new tests.

* Fixed types.

* Added extended layers expressions.

* Added support of tables at layers.

* Fixed tests.

* Fixed more tests.

* Fixed lens types.

* Added tables to layers.

* Checks fixed.

* updated tests.

* Fixed types.

* First try to fix merge conflicts.

* Fixed annotatations.

* Fixed types.

* Updated snapshots

* Fixed tests.

* Fixed dependencies.

* Fixed i18n.

* Moved XY state types to lens.

* Fixed more types.

* Update src/plugins/chart_expressions/expression_xy/README.md

Co-authored-by: Marta Bondyra <marta.bondyra@gmail.com>

* [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs'

* Removed yConfig from *Layers types

* Fixed styles.

* Fixed types.

* Removed not used utils and styles.

* Fixed types and tests.

* updated size.

* Added right behavior, related to the tables, comming from the expression.

* Fixed reference lines.

* Fixed jsdoc.

* Added annotations to layeredXyVIs.

* Fixed limits.

* Refactored the implementation to be reusable.

* Fixed undefined layers.

* Fixed empty arrays problems.

* Fixed input translations and removed not used arguments.

* Fixed missing required args error, and added required to arguments.

* Simplified expression configuration.

* Added strict to all the expressions.

* refactored code, according to the nit.

* Support visdimension type for accessors in data_layer and reference_line_layer

* Fix checks

* Moved dataLayer to the separate component.

* Fixed jest tests.

* Fixed tests.

* Some updates

* Refactored dataLayers helpers and xy_chart.

* More fixes of the expression

Added extendedYConfig for dataLayers.
Added yConfig for referenceLineLayers.
Fixed undefined id at tooltip.

* Fixed tests and snapshots.

* Icons at annotations and reference lines are strict.

* Fix CI

* axis extent validation added.

* Added checks to the legend config.

* fillOpacity usage validation is added.

* Fixed valueLabels argument options. Removed not used. Added validation for usage.

* Removed not used tests and imports.

* Fixed valueLabels and added migrations.

* Fixed type checks.

* Added test for the migrations.

* Fixed imports.

* Fixed types

* Fixed i18n checks.

# Conflicts:
#	src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx

* Fixed imports and types.

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Update src/plugins/chart_expressions/expression_xy/common/expression_functions/extended_annotation_layer.ts

Co-authored-by: Marta Bondyra <marta.bondyra@gmail.com>

* Removed extra extends.

* Update src/plugins/chart_expressions/expression_xy/common/expression_functions/annotation_layer.ts

Co-authored-by: Marta Bondyra <marta.bondyra@gmail.com>

* Added guard.

* Fixed the code duplication.

* Removed table from the annotation layer.

* Changed the `convertActiveDataFromIndexesToLayers` location.

* Added tests for convertActiveDataFromIndexesToLayers

* Reduced the bundle size a little bit.

* Reused strings and args.

* Refactored expression functions. Added asynchronous behavior.

* Fixed tests.

* Updated limits.

* Updated the limit.

* Fixed types.

* fixed types.

* Turned back layerIds.

* Removed convertActiveData from Lens.

* Added test to the layerIds generator.

* Fixed types.

* Fixed problems with resetting of the inspector.

* Fixed migrations.

* Removed types.

* Removed tones of `areFormatted` calculations.

* Fixed `isTimeViz` and `isHistogramViz` by replacing filteredLayers with dataLayers.

* Removed referenceLineLayers from the `groupAxesByType` fn.

* Added validation to the layeredXyVis.

* Fixed extent validation.

* Removed comments.

* Reduced limit.

* Added optimizations.

* Fixed floatingColumns error.

* Fixed types.

* Updated limits.

* Turned back extent validation.

* Fixed stacked error.

* Parallelized async import of functions.

* Decreased the complexity of the algorithm.

* Fixed snapshot.

* Some fixes

* Fix checks

* Fixed types

* Fix types

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Added validation to the accessors.

* Validated accessors at the referenceLineLayer and extendedReferenceLineLayer.

* Fixed some style issues.

* Fixed imports.

* One more fix of import.

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Move extended data layer fn in async file

* Some improvements

* Fix checks

* Fix validation

Co-authored-by: Yaroslav Kuznietsov <kuznetsov.yaroslav.yk@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Marta Bondyra <marta.bondyra@gmail.com>
This commit is contained in:
Uladzislau Lasitsa 2022-05-16 13:22:32 +03:00 committed by GitHub
parent 3177d0ec22
commit dd2a9b5425
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 299 additions and 132 deletions

View file

@ -16,16 +16,15 @@ type CommonDataLayerFnArgs = {
[key in keyof CommonDataLayerArgs]: ArgumentType<CommonDataLayerArgs[key]>;
};
export const commonDataLayerArgs: CommonDataLayerFnArgs = {
export const commonDataLayerArgs: Omit<
CommonDataLayerFnArgs,
'accessors' | 'xAccessor' | 'splitAccessor'
> = {
hide: {
types: ['boolean'],
default: false,
help: strings.getHideHelp(),
},
xAccessor: {
types: ['string'],
help: strings.getXAccessorHelp(),
},
seriesType: {
aliases: ['_'],
types: ['string'],
@ -51,15 +50,6 @@ export const commonDataLayerArgs: CommonDataLayerFnArgs = {
default: YScaleTypes.LINEAR,
strict: true,
},
splitAccessor: {
types: ['string'],
help: strings.getSplitAccessorHelp(),
},
accessors: {
types: ['string'],
help: strings.getAccessorsHelp(),
multi: true,
},
yConfig: {
types: [Y_CONFIG],
help: strings.getYConfigHelp(),

View file

@ -12,12 +12,7 @@ import { ReferenceLineLayerFn, ExtendedReferenceLineLayerFn } from '../types';
type CommonReferenceLineLayerFn = ReferenceLineLayerFn | ExtendedReferenceLineLayerFn;
export const commonReferenceLineLayerArgs: CommonReferenceLineLayerFn['args'] = {
accessors: {
types: ['string'],
help: strings.getRLAccessorsHelp(),
multi: true,
},
export const commonReferenceLineLayerArgs: Omit<CommonReferenceLineLayerFn['args'], 'accessors'> = {
yConfig: {
types: [EXTENDED_Y_CONFIG],
help: strings.getRLYConfigHelp(),

View file

@ -7,10 +7,9 @@
*/
import { ExtendedDataLayerFn } from '../types';
import { EXTENDED_DATA_LAYER, LayerTypes } from '../constants';
import { EXTENDED_DATA_LAYER } from '../constants';
import { strings } from '../i18n';
import { commonDataLayerArgs } from './common_data_layer_args';
import { getAccessors } from '../helpers';
export const extendedDataLayerFunction: ExtendedDataLayerFn = {
name: EXTENDED_DATA_LAYER,
@ -20,6 +19,19 @@ export const extendedDataLayerFunction: ExtendedDataLayerFn = {
inputTypes: ['datatable'],
args: {
...commonDataLayerArgs,
xAccessor: {
types: ['string'],
help: strings.getXAccessorHelp(),
},
splitAccessor: {
types: ['string'],
help: strings.getSplitAccessorHelp(),
},
accessors: {
types: ['string'],
help: strings.getAccessorsHelp(),
multi: true,
},
table: {
types: ['datatable'],
help: strings.getTableHelp(),
@ -29,14 +41,8 @@ export const extendedDataLayerFunction: ExtendedDataLayerFn = {
help: strings.getLayerIdHelp(),
},
},
fn(input, args) {
const table = args.table ?? input;
return {
type: EXTENDED_DATA_LAYER,
...args,
...getAccessors(args, table),
layerType: LayerTypes.DATA,
table,
};
async fn(input, args, context) {
const { extendedDataLayerFn } = await import('./extended_data_layer_fn');
return await extendedDataLayerFn(input, args, context);
},
};

View file

@ -0,0 +1,29 @@
/*
* 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 { validateAccessor } from '@kbn/visualizations-plugin/common/utils';
import { ExtendedDataLayerArgs, ExtendedDataLayerFn } from '../types';
import { EXTENDED_DATA_LAYER, LayerTypes } from '../constants';
import { getAccessors } from '../helpers';
export const extendedDataLayerFn: ExtendedDataLayerFn['fn'] = async (data, args, context) => {
const table = args.table ?? data;
const accessors = getAccessors<string, ExtendedDataLayerArgs>(args, table);
validateAccessor(accessors.xAccessor, table.columns);
validateAccessor(accessors.splitAccessor, table.columns);
accessors.accessors.forEach((accessor) => validateAccessor(accessor, table.columns));
return {
type: EXTENDED_DATA_LAYER,
...args,
layerType: LayerTypes.DATA,
...accessors,
table,
};
};

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { validateAccessor } from '@kbn/visualizations-plugin/common/utils';
import { LayerTypes, EXTENDED_REFERENCE_LINE_LAYER } from '../constants';
import { ExtendedReferenceLineLayerFn } from '../types';
import { strings } from '../i18n';
@ -19,6 +20,11 @@ export const extendedReferenceLineLayerFunction: ExtendedReferenceLineLayerFn =
inputTypes: ['datatable'],
args: {
...commonReferenceLineLayerArgs,
accessors: {
types: ['string'],
help: strings.getRLAccessorsHelp(),
multi: true,
},
table: {
types: ['datatable'],
help: strings.getTableHelp(),
@ -29,12 +35,16 @@ export const extendedReferenceLineLayerFunction: ExtendedReferenceLineLayerFn =
},
},
fn(input, args) {
const table = args.table ?? input;
const accessors = args.accessors ?? [];
accessors.forEach((accessor) => validateAccessor(accessor, table.columns));
return {
type: EXTENDED_REFERENCE_LINE_LAYER,
...args,
accessors: args.accessors ?? [],
layerType: LayerTypes.REFERENCELINE,
table: args.table ?? input,
table,
};
},
};

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { validateAccessor } from '@kbn/visualizations-plugin/common/utils';
import { LayerTypes, REFERENCE_LINE_LAYER } from '../constants';
import { ReferenceLineLayerFn } from '../types';
import { strings } from '../i18n';
@ -17,13 +18,23 @@ export const referenceLineLayerFunction: ReferenceLineLayerFn = {
type: REFERENCE_LINE_LAYER,
help: strings.getRLHelp(),
inputTypes: ['datatable'],
args: { ...commonReferenceLineLayerArgs },
args: {
...commonReferenceLineLayerArgs,
accessors: {
types: ['string', 'vis_dimension'],
help: strings.getRLAccessorsHelp(),
multi: true,
},
},
fn(table, args) {
const accessors = args.accessors ?? [];
accessors.forEach((accessor) => validateAccessor(accessor, table.columns));
return {
type: REFERENCE_LINE_LAYER,
...args,
accessors: args.accessors ?? [],
layerType: LayerTypes.REFERENCELINE,
accessors,
table,
};
},

View file

@ -20,6 +20,19 @@ export const xyVisFunction: XyVisFn = {
args: {
...commonXYArgs,
...commonDataLayerArgs,
xAccessor: {
types: ['string', 'vis_dimension'],
help: strings.getXAccessorHelp(),
},
splitAccessor: {
types: ['string', 'vis_dimension'],
help: strings.getSplitAccessorHelp(),
},
accessors: {
types: ['string', 'vis_dimension'],
help: strings.getAccessorsHelp(),
multi: true,
},
referenceLineLayers: {
types: [REFERENCE_LINE_LAYER],
help: strings.getReferenceLineLayerHelp(),

View file

@ -12,6 +12,7 @@ import {
validateAccessor,
} from '@kbn/visualizations-plugin/common/utils';
import type { Datatable } from '@kbn/expressions-plugin/common';
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
import { LayerTypes, XY_VIS_RENDERER, DATA_LAYER } from '../constants';
import { appendLayerIds, getAccessors } from '../helpers';
import { DataLayerConfigResult, XYLayerConfig, XyVisFn, XYArgs } from '../types';
@ -37,7 +38,7 @@ const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult
yConfig: args.yConfig,
layerType: LayerTypes.DATA,
table,
...getAccessors(args, table),
...getAccessors<string | ExpressionValueVisDimension, XYArgs>(args, table),
});
export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
@ -64,6 +65,10 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
const dataLayers: DataLayerConfigResult[] = [createDataLayer(args, data)];
validateAccessor(dataLayers[0].xAccessor, data.columns);
validateAccessor(dataLayers[0].splitAccessor, data.columns);
dataLayers[0].accessors.forEach((accessor) => validateAccessor(accessor, data.columns));
const layers: XYLayerConfig[] = [
...appendLayerIds(dataLayers, 'dataLayers'),
...appendLayerIds(referenceLineLayers, 'referenceLineLayers'),

View file

@ -7,7 +7,7 @@
*/
import { Datatable, PointSeriesColumnNames } from '@kbn/expressions-plugin/common';
import { WithLayerId, DataLayerArgs } from '../types';
import { WithLayerId } from '../types';
function isWithLayerId<T>(layer: T): layer is T & WithLayerId {
return (layer as T & WithLayerId).layerId ? true : false;
@ -27,10 +27,13 @@ export function appendLayerIds<T>(
}));
}
export function getAccessors(args: DataLayerArgs, table: Datatable) {
let splitAccessor = args.splitAccessor;
let xAccessor = args.xAccessor;
let accessors = args.accessors ?? [];
export function getAccessors<T, U extends { splitAccessor?: T; xAccessor?: T; accessors: 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)) {
const y = table.columns.find((column) => column.id === PointSeriesColumnNames.Y)?.id;
xAccessor = table.columns.find((column) => column.id === PointSeriesColumnNames.X)?.id;

View file

@ -12,8 +12,7 @@ import type { PaletteOutput } from '@kbn/coloring';
import { Datatable, ExpressionFunctionDefinition } from '@kbn/expressions-plugin';
import { LegendSize } from '@kbn/visualizations-plugin/public';
import { EventAnnotationOutput } from '@kbn/event-annotation-plugin/common';
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common';
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
import {
AxisExtentModes,
FillStyles,
@ -96,11 +95,11 @@ export interface YConfig {
}
export interface DataLayerArgs {
accessors: string[];
accessors: Array<ExpressionValueVisDimension | string>;
seriesType: SeriesType;
xAccessor?: string;
xAccessor?: string | ExpressionValueVisDimension;
hide?: boolean;
splitAccessor?: string;
splitAccessor?: string | ExpressionValueVisDimension;
columnToLabel?: string; // Actually a JSON key-value pair
yScaleType: YScaleType;
xScaleType: XScaleType;
@ -276,7 +275,7 @@ export type ExtendedAnnotationLayerConfigResult = ExtendedAnnotationLayerArgs &
};
export interface ReferenceLineLayerArgs {
accessors: string[];
accessors: Array<ExpressionValueVisDimension | string>;
columnToLabel?: string;
yConfig?: ExtendedYConfigResult[];
}
@ -385,7 +384,7 @@ export type ExtendedDataLayerFn = ExpressionFunctionDefinition<
typeof EXTENDED_DATA_LAYER,
Datatable,
ExtendedDataLayerArgs,
ExtendedDataLayerConfigResult
Promise<ExtendedDataLayerConfigResult>
>;
export type ReferenceLineLayerFn = ExpressionFunctionDefinition<

View file

@ -34,7 +34,6 @@ import type {
CommonXYAnnotationLayerConfig,
CollectiveConfig,
} from '../../common';
import { AnnotationIcon, hasIcon, Marker, MarkerBody } from '../helpers';
import { mapVerticalToHorizontalPlacement, LINES_MARKER_SIZE } from '../helpers';

View file

@ -15,6 +15,8 @@ import {
import React, { FC } from 'react';
import { PaletteRegistry } from '@kbn/coloring';
import { FormatFactory } from '@kbn/field-formats-plugin/common';
import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils';
import {
CommonXYDataLayerConfig,
EndValue,
@ -71,7 +73,8 @@ export const DataLayers: FC<Props> = ({
<>
{layers.flatMap((layer) =>
layer.accessors.map((accessor, accessorIndex) => {
const { seriesType, columnToLabel, layerId } = layer;
const { seriesType, columnToLabel, layerId, table } = layer;
const yColumnId = getAccessorByDimension(accessor, table.columns);
const columnToLabelMap: Record<string, string> = columnToLabel
? JSON.parse(columnToLabel)
: {};
@ -84,12 +87,12 @@ export const DataLayers: FC<Props> = ({
const isPercentage = seriesType.includes('percentage');
const yAxis = yAxesConfiguration.find((axisConfiguration) =>
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === yColumnId)
);
const seriesProps = getSeriesProps({
layer,
accessor,
accessor: yColumnId,
chartHasMoreThanOneBarSeries,
colorAssignments,
formatFactory,

View file

@ -8,6 +8,7 @@
import React from 'react';
import type { LegendAction, XYChartSeriesIdentifier } from '@elastic/charts';
import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils';
import type { FilterEvent } from '../types';
import type { CommonXYDataLayerConfig } from '../../common';
import type { FormatFactory } from '../types';
@ -23,7 +24,11 @@ export const getLegendAction = (
React.memo(({ series: [xySeries] }) => {
const series = xySeries as XYChartSeriesIdentifier;
const layerIndex = dataLayers.findIndex((l) =>
series.seriesKeys.some((key: string | number) => l.accessors.includes(key.toString()))
series.seriesKeys.some((key: string | number) =>
l.accessors.some(
(accessor) => getAccessorByDimension(accessor, l.table.columns) === key.toString()
)
)
);
if (layerIndex === -1) {
@ -36,11 +41,12 @@ export const getLegendAction = (
}
const splitLabel = series.seriesKeys[0] as string;
const accessor = layer.splitAccessor;
const { table } = layer;
const splitColumn = table.columns.find(({ id }) => id === layer.splitAccessor);
const formatter = formatFactory(splitColumn && getFormat(splitColumn.meta));
const accessor = getAccessorByDimension(layer.splitAccessor, table.columns);
const formatter = formatFactory(
accessor ? getFormat(table.columns, layer.splitAccessor) : undefined
);
const rowIndex = table.rows.findIndex((row) => {
if (formattedDatatables[layer.layerId]?.formattedColumns[accessor]) {

View file

@ -6,11 +6,15 @@
* Side Public License, v 1.
*/
import { uniq } from 'lodash';
import { isUndefined, uniq } from 'lodash';
import React from 'react';
import moment from 'moment';
import { Endzones } from '@kbn/charts-plugin/public';
import { search } from '@kbn/data-plugin/public';
import {
getAccessorByDimension,
getColumnByAccessor,
} from '@kbn/visualizations-plugin/common/utils';
import type { CommonXYDataLayerConfig } from '../../common';
export interface XDomain {
@ -22,7 +26,7 @@ export interface XDomain {
export const getAppliedTimeRange = (layers: CommonXYDataLayerConfig[]) => {
return layers
.map(({ xAccessor, table }) => {
const xColumn = table.columns.find((col) => col.id === xAccessor);
const xColumn = xAccessor ? getColumnByAccessor(xAccessor, table.columns) : null;
const timeRange =
xColumn && search.aggs.getDateHistogramMetaDataByDatatableColumn(xColumn)?.timeRange;
if (timeRange) {
@ -57,9 +61,11 @@ export const getXDomain = (
if (isHistogram && isFullyQualified(baseDomain)) {
const xValues = uniq(
layers
.flatMap<number>(({ table, xAccessor }) =>
table.rows.map((row) => row[xAccessor!].valueOf())
)
.flatMap<number>(({ table, xAccessor }) => {
const accessor = xAccessor && getAccessorByDimension(xAccessor, table.columns);
return table.rows.map((row) => accessor && row[accessor] && row[accessor].valueOf());
})
.filter((v) => !isUndefined(v))
.sort()
);

View file

@ -33,6 +33,10 @@ import { EmptyPlaceholder } from '@kbn/charts-plugin/public';
import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public';
import { ChartsPluginSetup, ChartsPluginStart, useActiveCursor } from '@kbn/charts-plugin/public';
import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common';
import {
getAccessorByDimension,
getColumnByAccessor,
} from '@kbn/visualizations-plugin/common/utils';
import {
DEFAULT_LEGEND_SIZE,
LegendSizeToPixels,
@ -184,9 +188,15 @@ export function XYChart({
}
// use formatting hint of first x axis column to format ticks
const xAxisColumn = dataLayers[0]?.table.columns.find(({ id }) => id === dataLayers[0].xAccessor);
const xAxisColumn = dataLayers[0].xAccessor
? getColumnByAccessor(dataLayers[0].xAccessor, dataLayers[0]?.table.columns)
: undefined;
const xAxisFormatter = formatFactory(xAxisColumn && getFormat(xAxisColumn.meta));
const xAxisFormatter = formatFactory(
dataLayers[0].xAccessor
? getFormat(dataLayers[0].table.columns, dataLayers[0].xAccessor)
: undefined
);
// This is a safe formatter for the xAccessor that abstracts the knowledge of already formatted layers
const safeXAccessorLabelRenderer = (value: unknown): string =>
@ -257,12 +267,14 @@ export function XYChart({
const annotationsLayers = getAnnotationsLayers(layers);
const firstTable = dataLayers[0]?.table;
const xColumnId = firstTable?.columns.find((col) => col.id === dataLayers[0]?.xAccessor)?.id;
const columnId = dataLayers[0]?.xAccessor
? getColumnByAccessor(dataLayers[0]?.xAccessor, firstTable.columns)?.id
: null;
const groupedLineAnnotations = getAnnotationsGroupedByInterval(
annotationsLayers,
minInterval,
xColumnId ? firstTable.rows[0]?.[xColumnId] : undefined,
columnId ? firstTable.rows[0]?.[columnId] : undefined,
xAxisFormatter
);
const rangeAnnotations = getRangeAnnotations(annotationsLayers);
@ -370,7 +382,11 @@ export function XYChart({
const xyGeometry = geometry as GeometryValue;
const layerIndex = dataLayers.findIndex((l) =>
xySeries.seriesKeys.some((key: string | number) => l.accessors.includes(key.toString()))
xySeries.seriesKeys.some((key: string | number) =>
l.accessors.some(
(accessor) => getAccessorByDimension(accessor, l.table.columns) === key.toString()
)
)
);
if (layerIndex === -1) {
@ -380,48 +396,53 @@ export function XYChart({
const layer = dataLayers[layerIndex];
const { table } = layer;
const xColumn = table.columns.find((col) => col.id === layer.xAccessor);
const xColumn = layer.xAccessor && getColumnByAccessor(layer.xAccessor, table.columns);
const xAccessor = layer.xAccessor
? getAccessorByDimension(layer.xAccessor, table.columns)
: undefined;
const currentXFormatter =
layer.xAccessor &&
formattedDatatables[layer.layerId]?.formattedColumns[layer.xAccessor] &&
xColumn
? formatFactory(getFormat(xColumn.meta))
xAccessor && formattedDatatables[layer.layerId]?.formattedColumns[xAccessor] && xColumn
? formatFactory(layer.xAccessor ? getFormat(table.columns, layer.xAccessor) : undefined)
: xAxisFormatter;
const rowIndex = table.rows.findIndex((row) => {
if (layer.xAccessor) {
if (formattedDatatables[layer.layerId]?.formattedColumns[layer.xAccessor]) {
if (xAccessor) {
if (formattedDatatables[layer.layerId]?.formattedColumns[xAccessor]) {
// stringify the value to compare with the chart value
return currentXFormatter.convert(row[layer.xAccessor]) === xyGeometry.x;
return currentXFormatter.convert(row[xAccessor]) === xyGeometry.x;
}
return row[layer.xAccessor] === xyGeometry.x;
return row[xAccessor] === xyGeometry.x;
}
});
const points = [
{
row: rowIndex,
column: table.columns.findIndex((col) => col.id === layer.xAccessor),
value: layer.xAccessor ? table.rows[rowIndex][layer.xAccessor] : xyGeometry.x,
column: table.columns.findIndex((col) => col.id === xAccessor),
value: xAccessor ? table.rows[rowIndex][xAccessor] : xyGeometry.x,
},
];
if (xySeries.seriesKeys.length > 1) {
const pointValue = xySeries.seriesKeys[0];
const splitAccessor = layer.splitAccessor
? getAccessorByDimension(layer.splitAccessor, table.columns)
: undefined;
const splitColumn = table.columns.find(({ id }) => id === layer.splitAccessor);
const splitFormatter = formatFactory(splitColumn && getFormat(splitColumn.meta));
const splitFormatter = formatFactory(
layer.splitAccessor ? getFormat(table.columns, layer.splitAccessor) : undefined
);
points.push({
row: table.rows.findIndex((row) => {
if (layer.splitAccessor) {
if (formattedDatatables[layer.layerId]?.formattedColumns[layer.splitAccessor]) {
return splitFormatter.convert(row[layer.splitAccessor]) === pointValue;
if (splitAccessor) {
if (formattedDatatables[layer.layerId]?.formattedColumns[splitAccessor]) {
return splitFormatter.convert(row[splitAccessor]) === pointValue;
}
return row[layer.splitAccessor] === pointValue;
return row[splitAccessor] === pointValue;
}
}),
column: table.columns.findIndex((col) => col.id === layer.splitAccessor),
column: table.columns.findIndex((col) => col.id === splitAccessor),
value: pointValue,
});
}
@ -441,8 +462,9 @@ export function XYChart({
}
const { table } = dataLayers[0];
const xAxisColumnIndex = table.columns.findIndex((el) => el.id === dataLayers[0].xAccessor);
const xAccessor =
dataLayers[0].xAccessor && getAccessorByDimension(dataLayers[0].xAccessor, table.columns);
const xAxisColumnIndex = table.columns.findIndex((el) => el.id === xAccessor);
const context: BrushEvent['data'] = { range: [min, max], table, column: xAxisColumnIndex };
onSelectRange(context);

View file

@ -7,6 +7,7 @@
*/
import type { IFieldFormat, SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils';
import { FormatFactory } from '../types';
import { AxisExtentConfig, CommonXYDataLayerConfig, ExtendedYConfig, YConfig } from '../../common';
import { isDataLayer } from './visualization';
@ -52,10 +53,12 @@ export function groupAxesByType(layers: CommonXYDataLayerConfig[]) {
const { table } = layer;
layer.accessors.forEach((accessor) => {
const yConfig: Array<YConfig | ExtendedYConfig> | undefined = layer.yConfig;
const yAccessor = getAccessorByDimension(accessor, table?.columns || []);
const mode =
yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode || 'auto';
const col = table.columns?.find((column) => column.id === accessor);
let formatter: SerializedFieldFormat = col?.meta ? getFormat(col.meta) : { id: 'number' };
yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === yAccessor)?.axisMode || 'auto';
let formatter: SerializedFieldFormat = getFormat(table.columns, accessor) || {
id: 'number',
};
if (
isDataLayer(layer) &&
layer.seriesType.includes('percentage') &&
@ -70,7 +73,7 @@ export function groupAxesByType(layers: CommonXYDataLayerConfig[]) {
}
series[mode].push({
layer: layer.layerId,
accessor,
accessor: yAccessor,
fieldFormat: formatter,
});
});

View file

@ -8,6 +8,7 @@
import { uniq, mapValues } from 'lodash';
import { euiLightVars } from '@kbn/ui-theme';
import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils';
import { FormatFactory } from '../types';
import { isDataLayer } from './visualization';
import { CommonXYDataLayerConfig, CommonXYLayerConfig } from '../../common';
@ -48,9 +49,10 @@ export function getColorAssignments(
if (!layer.splitAccessor) {
return { numberOfSeries: layer.accessors.length, splits: [] };
}
const splitAccessor = layer.splitAccessor;
const splitAccessor = getAccessorByDimension(layer.splitAccessor, layer.table.columns);
const column = layer.table.columns?.find(({ id }) => id === splitAccessor);
const columnFormatter = column && formatFactory(getFormat(column.meta));
const columnFormatter =
column && formatFactory(getFormat(layer.table.columns, layer.splitAccessor));
const splits =
!column || !layer.table
? []
@ -88,7 +90,9 @@ export function getColorAssignments(
(sortedLayer.splitAccessor && splitRank !== -1
? splitRank * sortedLayer.accessors.length
: 0) +
sortedLayer.accessors.indexOf(yAccessor)
sortedLayer.accessors.findIndex(
(accessor) => getAccessorByDimension(accessor, sortedLayer.table.columns) === yAccessor
)
);
},
};

View file

@ -24,6 +24,11 @@ import {
SerializedFieldFormat,
} from '@kbn/field-formats-plugin/common';
import { Datatable } from '@kbn/expressions-plugin';
import {
getFormatByAccessor,
getAccessorByDimension,
} from '@kbn/visualizations-plugin/common/utils';
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
import { PaletteRegistry, SeriesLayer } from '@kbn/coloring';
import { CommonXYDataLayerConfig, XScaleType } from '../../common';
import { FormatFactory } from '../types';
@ -53,7 +58,8 @@ type GetSeriesPropsFn = (config: {
type GetSeriesNameFn = (
data: XYChartSeriesIdentifier,
config: {
layer: CommonXYDataLayerConfig;
splitColumnId?: string;
accessorsCount: number;
splitHint: SerializedFieldFormat<FieldFormatParams> | undefined;
splitFormatter: FieldFormat;
alreadyFormattedColumns: Record<string, boolean>;
@ -112,11 +118,21 @@ export const getFormattedRow = (
export const getFormattedTable = (
table: Datatable,
formatFactory: FormatFactory,
xAccessor: string | undefined,
xAccessor: string | ExpressionValueVisDimension | undefined,
accessors: Array<string | ExpressionValueVisDimension>,
xScaleType: XScaleType
): { table: Datatable; formattedColumns: Record<string, true> } => {
const columnsFormatters = table.columns.reduce<Record<string, IFieldFormat>>(
(formatters, { id, meta }) => ({ ...formatters, [id]: formatFactory(getFormat(meta)) }),
(formatters, { id, meta }) => {
const accessor: string | ExpressionValueVisDimension | undefined = accessors.find(
(a) => getAccessorByDimension(a, table.columns) === id
);
return {
...formatters,
[id]: formatFactory(accessor ? getFormat(table.columns, accessor) : meta.params),
};
},
{}
);
@ -129,7 +145,7 @@ export const getFormattedTable = (
row,
table.columns,
columnsFormatters,
xAccessor,
xAccessor ? getAccessorByDimension(xAccessor, table.columns) : undefined,
xScaleType
);
return {
@ -154,28 +170,43 @@ export const getFormattedTablesByLayers = (
formatFactory: FormatFactory
): DatatablesWithFormatInfo =>
layers.reduce(
(formattedDatatables, { layerId, table, xAccessor, xScaleType }) => ({
(formattedDatatables, { layerId, table, xAccessor, splitAccessor, accessors, xScaleType }) => ({
...formattedDatatables,
[layerId]: getFormattedTable(table, formatFactory, xAccessor, xScaleType),
[layerId]: getFormattedTable(
table,
formatFactory,
xAccessor,
[xAccessor, splitAccessor, ...accessors].filter<string | ExpressionValueVisDimension>(
(a): a is string | ExpressionValueVisDimension => a !== undefined
),
xScaleType
),
}),
{}
);
const getSeriesName: GetSeriesNameFn = (
data,
{ layer, splitHint, splitFormatter, alreadyFormattedColumns, columnToLabelMap }
{
splitColumnId,
accessorsCount,
splitHint,
splitFormatter,
alreadyFormattedColumns,
columnToLabelMap,
}
) => {
// For multiple y series, the name of the operation is used on each, either:
// * Key - Y name
// * Formatted value - Y name
if (layer.splitAccessor && layer.accessors.length > 1) {
const formatted = alreadyFormattedColumns[layer.splitAccessor];
if (splitColumnId && accessorsCount > 1) {
const formatted = alreadyFormattedColumns[splitColumnId];
const result = data.seriesKeys
.map((key: string | number, i) => {
if (i === 0 && splitHint && layer.splitAccessor && !formatted) {
if (i === 0 && splitHint && splitColumnId && !formatted) {
return splitFormatter.convert(key);
}
return layer.splitAccessor && i === 0 ? key : columnToLabelMap[key] ?? null;
return splitColumnId && i === 0 ? key : columnToLabelMap[key] ?? null;
})
.join(' - ');
return result;
@ -184,7 +215,7 @@ const getSeriesName: GetSeriesNameFn = (
// For formatted split series, format the key
// This handles splitting by dates, for example
if (splitHint) {
if (layer.splitAccessor && alreadyFormattedColumns[layer.splitAccessor]) {
if (splitColumnId && alreadyFormattedColumns[splitColumnId]) {
return data.seriesKeys[0];
}
return splitFormatter.convert(data.seriesKeys[0]);
@ -192,7 +223,7 @@ const getSeriesName: GetSeriesNameFn = (
// This handles both split and single-y cases:
// * If split series without formatting, show the value literally
// * If single Y, the seriesKey will be the accessor, so we show the human-readable name
return layer.splitAccessor ? data.seriesKeys[0] : columnToLabelMap[data.seriesKeys[0]] ?? null;
return splitColumnId ? data.seriesKeys[0] : columnToLabelMap[data.seriesKeys[0]] ?? null;
};
const getPointConfig = (xAccessor?: string, emphasizeFitting?: boolean) => ({
@ -249,13 +280,18 @@ export const getSeriesProps: GetSeriesPropsFn = ({
const isStacked = layer.seriesType.includes('stacked');
const isPercentage = layer.seriesType.includes('percentage');
const isBarChart = layer.seriesType.includes('bar');
const xColumnId = layer.xAccessor && getAccessorByDimension(layer.xAccessor, table.columns);
const splitColumnId =
layer.splitAccessor && getAccessorByDimension(layer.splitAccessor, table.columns);
const enableHistogramMode =
layer.isHistogram &&
(isStacked || !layer.splitAccessor) &&
(isStacked || !isBarChart || !chartHasMoreThanOneBarSeries);
const formatter = table?.columns.find((column) => column.id === accessor)?.meta?.params;
const splitHint = table?.columns.find((col) => col.id === layer.splitAccessor)?.meta?.params;
const splitHint = layer.splitAccessor
? getFormatByAccessor(layer.splitAccessor, table.columns)
: undefined;
const splitFormatter = formatFactory(splitHint);
// what if row values are not primitive? That is the case of, for instance, Ranges
@ -267,15 +303,15 @@ export const getSeriesProps: GetSeriesPropsFn = ({
// To not display them in the legend, they need to be filtered out.
let rows = formattedTable.rows.filter(
(row) =>
!(layer.xAccessor && typeof row[layer.xAccessor] === 'undefined') &&
!(xColumnId && typeof row[xColumnId] === 'undefined') &&
!(
layer.splitAccessor &&
typeof row[layer.splitAccessor] === 'undefined' &&
splitColumnId &&
typeof row[splitColumnId] === 'undefined' &&
typeof row[accessor] === 'undefined'
)
);
if (!layer.xAccessor) {
if (!xColumnId) {
rows = rows.map((row) => ({
...row,
unifiedX: i18n.translate('expressionXY.xyChart.emptyXLabel', {
@ -285,13 +321,13 @@ export const getSeriesProps: GetSeriesPropsFn = ({
}
return {
splitSeriesAccessors: layer.splitAccessor ? [layer.splitAccessor] : [],
stackAccessors: isStacked ? [layer.xAccessor as string] : [],
id: layer.splitAccessor ? `${layer.splitAccessor}-${accessor}` : `${accessor}`,
xAccessor: layer.xAccessor || 'unifiedX',
splitSeriesAccessors: splitColumnId ? [splitColumnId] : [],
stackAccessors: isStacked ? [xColumnId as string] : [],
id: splitColumnId ? `${splitColumnId}-${accessor}` : accessor,
xAccessor: xColumnId || 'unifiedX',
yAccessors: [accessor],
data: rows,
xScaleType: layer.xAccessor ? layer.xScaleType : 'ordinal',
xScaleType: xColumnId ? layer.xScaleType : 'ordinal',
yScaleType:
formatter?.id === 'bytes' && layer.yScaleType === ScaleType.Linear
? ScaleType.LinearBinary
@ -310,19 +346,20 @@ export const getSeriesProps: GetSeriesPropsFn = ({
stackMode: isPercentage ? StackMode.Percentage : undefined,
timeZone,
areaSeriesStyle: {
point: getPointConfig(layer.xAccessor, emphasizeFitting),
point: getPointConfig(xColumnId, emphasizeFitting),
...(fillOpacity && { area: { opacity: fillOpacity } }),
...(emphasizeFitting && {
fit: { area: { opacity: fillOpacity || 0.5 }, line: getLineConfig() },
}),
},
lineSeriesStyle: {
point: getPointConfig(layer.xAccessor, emphasizeFitting),
point: getPointConfig(xColumnId, emphasizeFitting),
...(emphasizeFitting && { fit: { line: getLineConfig() } }),
},
name(d) {
return getSeriesName(d, {
layer,
splitColumnId,
accessorsCount: layer.accessors.length,
splitHint,
splitFormatter,
alreadyFormattedColumns: formattedColumns,

View file

@ -6,6 +6,22 @@
* Side Public License, v 1.
*/
import { DatatableColumnMeta } from '@kbn/expressions-plugin';
import { DatatableColumn } from '@kbn/expressions-plugin';
import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
import { getFormatByAccessor, getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
export const getFormat = (meta?: DatatableColumnMeta) => meta?.params || { id: meta?.type };
export const getFormat = (
columns: DatatableColumn[],
accessor: string | ExpressionValueVisDimension
) => {
const type = getColumnByAccessor(accessor, columns)?.meta.type;
return getFormatByAccessor(
accessor,
columns,
type
? {
id: type,
}
: undefined
);
};

View file

@ -7,6 +7,7 @@
*/
import { search } from '@kbn/data-plugin/public';
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
import { XYChartProps } from '../../common';
import { getFilteredLayers } from './layers';
import { isDataLayer } from './visualization';
@ -15,9 +16,10 @@ export function calculateMinInterval({ args: { layers } }: XYChartProps) {
const filteredLayers = getFilteredLayers(layers);
if (filteredLayers.length === 0) return;
const isTimeViz = filteredLayers.every((l) => isDataLayer(l) && l.xScaleType === 'time');
const xColumn = filteredLayers[0].table.columns.find(
(column) => isDataLayer(filteredLayers[0]) && column.id === filteredLayers[0].xAccessor
);
const xColumn =
isDataLayer(filteredLayers[0]) &&
filteredLayers[0].xAccessor &&
getColumnByAccessor(filteredLayers[0].xAccessor, filteredLayers[0].table.columns);
if (!xColumn) return;
if (!isTimeViz) {

View file

@ -7,6 +7,8 @@
*/
import { Datatable } from '@kbn/expressions-plugin/common';
import { getAccessorByDimension } from '@kbn/visualizations-plugin/common/utils';
import type { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common/expression_functions';
import {
CommonXYDataLayerConfig,
CommonXYLayerConfig,
@ -18,20 +20,24 @@ export function getFilteredLayers(layers: CommonXYLayerConfig[]) {
return layers.filter<CommonXYReferenceLineLayerConfig | CommonXYDataLayerConfig>(
(layer): layer is CommonXYReferenceLineLayerConfig | CommonXYDataLayerConfig => {
let table: Datatable | undefined;
let accessors: string[] = [];
let accessors: Array<ExpressionValueVisDimension | string> = [];
let xAccessor: undefined | string | number;
let splitAccessor: undefined | string | number;
if (isDataLayer(layer)) {
xAccessor = layer.xAccessor;
splitAccessor = layer.splitAccessor;
}
if (isDataLayer(layer) || isReferenceLayer(layer)) {
table = layer.table;
accessors = layer.accessors;
}
if (isDataLayer(layer)) {
xAccessor =
layer.xAccessor && table && getAccessorByDimension(layer.xAccessor, table.columns);
splitAccessor =
layer.splitAccessor &&
table &&
getAccessorByDimension(layer.splitAccessor, table.columns);
}
return !(
!accessors.length ||
!table ||

View file

@ -7,6 +7,7 @@
*/
import { i18n } from '@kbn/i18n';
import { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common';
import { FieldFormatParams, SerializedFieldFormat } from '@kbn/field-formats-plugin/common/types';
import { ExpressionValueVisDimension } from '../expression_functions';
const getAccessorByIndex = (accessor: number, columns: Datatable['columns']) =>
@ -66,11 +67,12 @@ export function getAccessor(dimension: string | ExpressionValueVisDimension) {
export function getFormatByAccessor(
dimension: string | ExpressionValueVisDimension,
columns: DatatableColumn[]
columns: DatatableColumn[],
defaultColumnFormat?: SerializedFieldFormat<FieldFormatParams>
) {
return typeof dimension === 'string'
? getColumnByAccessor(dimension, columns)?.meta.params
: dimension.format;
? getColumnByAccessor(dimension, columns)?.meta.params || defaultColumnFormat
: dimension.format || defaultColumnFormat;
}
export const getColumnByAccessor = (