mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[XY] Expression chart. (#127150)
* added xy plugin. * Added expressionXY limits. * Added xy expression functions to the expression_xy plugin. * Moved xy to a separate plugin. * 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. * Fixed "lens disable auto-apply tests" test. * Updated dashboard tests. * Fixed translations. * Expression tests fixed. * Cleaned up expression_xy. * cleaned up lens xy_visualization. * Moved XY state types to lens. * 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 * Removed not used utils and styles. 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:
parent
0ea741dcd5
commit
072fe63c0b
172 changed files with 7946 additions and 5221 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -41,6 +41,7 @@
|
|||
/src/plugins/chart_expressions/expression_heatmap/ @elastic/kibana-vis-editors
|
||||
/src/plugins/chart_expressions/expression_gauge/ @elastic/kibana-vis-editors
|
||||
/src/plugins/chart_expressions/expression_partition_vis/ @elastic/kibana-vis-editors
|
||||
/src/plugins/chart_expressions/expression_xy/ @elastic/kibana-vis-editors
|
||||
/src/plugins/url_forwarding/ @elastic/kibana-vis-editors
|
||||
/packages/kbn-tinymath/ @elastic/kibana-vis-editors
|
||||
/x-pack/test/functional/apps/lens @elastic/kibana-vis-editors
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"expressionMetric": "src/plugins/expression_metric",
|
||||
"expressionMetricVis": "src/plugins/chart_expressions/expression_metric",
|
||||
"expressionPartitionVis": "src/plugins/chart_expressions/expression_partition_vis",
|
||||
"expressionXY": "src/plugins/chart_expressions/expression_xy",
|
||||
"expressionRepeatImage": "src/plugins/expression_repeat_image",
|
||||
"expressionRevealImage": "src/plugins/expression_reveal_image",
|
||||
"expressions": "src/plugins/expressions",
|
||||
|
|
|
@ -164,6 +164,10 @@ for use in their own application.
|
|||
|Expression Tagcloud plugin adds a tagcloud renderer and function to the expression plugin. The renderer will display the Wordcloud chart.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/chart_expressions/expression_xy/README.md[expressionXY]
|
||||
|Expression XY plugin adds a xy renderer and function to the expression plugin. The renderer will display the xy chart.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/field_formats/README.md[fieldFormats]
|
||||
|Index pattern fields formatters
|
||||
|
||||
|
|
|
@ -124,4 +124,5 @@ pageLoadAssetSize:
|
|||
sessionView: 77750
|
||||
cloudSecurityPosture: 19109
|
||||
visTypeGauge: 24113
|
||||
expressionXY: 41392
|
||||
eventAnnotation: 19334
|
||||
|
|
9
src/plugins/chart_expressions/expression_xy/README.md
Executable file
9
src/plugins/chart_expressions/expression_xy/README.md
Executable file
|
@ -0,0 +1,9 @@
|
|||
# expressionXY
|
||||
|
||||
Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment.
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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 { Position } from '@elastic/charts';
|
||||
import { PaletteOutput } from 'src/plugins/charts/common';
|
||||
import { Datatable, DatatableRow } from 'src/plugins/expressions';
|
||||
import { LayerTypes } from '../constants';
|
||||
import { DataLayerConfigResult, LensMultiTable, XYArgs } from '../types';
|
||||
|
||||
export const mockPaletteOutput: PaletteOutput = {
|
||||
type: 'palette',
|
||||
name: 'mock',
|
||||
params: {},
|
||||
};
|
||||
|
||||
export const createSampleDatatableWithRows = (rows: DatatableRow[]): Datatable => ({
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: 'a',
|
||||
name: 'a',
|
||||
meta: { type: 'number', params: { id: 'number', params: { pattern: '0,0.000' } } },
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
name: 'b',
|
||||
meta: { type: 'number', params: { id: 'number', params: { pattern: '000,0' } } },
|
||||
},
|
||||
{
|
||||
id: 'c',
|
||||
name: 'c',
|
||||
meta: {
|
||||
type: 'date',
|
||||
field: 'order_date',
|
||||
sourceParams: { type: 'date-histogram', params: { interval: 'auto' } },
|
||||
params: { id: 'string' },
|
||||
},
|
||||
},
|
||||
{ id: 'd', name: 'ColD', meta: { type: 'string' } },
|
||||
],
|
||||
rows,
|
||||
});
|
||||
|
||||
export const sampleLayer: DataLayerConfigResult = {
|
||||
type: 'dataLayer',
|
||||
layerId: 'first',
|
||||
layerType: LayerTypes.DATA,
|
||||
seriesType: 'line',
|
||||
xAccessor: 'c',
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: 'd',
|
||||
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
|
||||
xScaleType: 'ordinal',
|
||||
yScaleType: 'linear',
|
||||
isHistogram: false,
|
||||
palette: mockPaletteOutput,
|
||||
};
|
||||
|
||||
export const createArgsWithLayers = (layers: DataLayerConfigResult[] = [sampleLayer]): XYArgs => ({
|
||||
xTitle: '',
|
||||
yTitle: '',
|
||||
yRightTitle: '',
|
||||
legend: {
|
||||
type: 'legendConfig',
|
||||
isVisible: false,
|
||||
position: Position.Top,
|
||||
},
|
||||
valueLabels: 'hide',
|
||||
valuesInLegend: false,
|
||||
axisTitlesVisibilitySettings: {
|
||||
type: 'axisTitlesVisibilityConfig',
|
||||
x: true,
|
||||
yLeft: true,
|
||||
yRight: true,
|
||||
},
|
||||
tickLabelsVisibilitySettings: {
|
||||
type: 'tickLabelsConfig',
|
||||
x: true,
|
||||
yLeft: false,
|
||||
yRight: false,
|
||||
},
|
||||
labelsOrientation: {
|
||||
type: 'labelsOrientationConfig',
|
||||
x: 0,
|
||||
yLeft: -90,
|
||||
yRight: -45,
|
||||
},
|
||||
gridlinesVisibilitySettings: {
|
||||
type: 'gridlinesConfig',
|
||||
x: true,
|
||||
yLeft: false,
|
||||
yRight: false,
|
||||
},
|
||||
yLeftExtent: {
|
||||
mode: 'full',
|
||||
type: 'axisExtentConfig',
|
||||
},
|
||||
yRightExtent: {
|
||||
mode: 'full',
|
||||
type: 'axisExtentConfig',
|
||||
},
|
||||
layers,
|
||||
});
|
||||
|
||||
export function sampleArgs() {
|
||||
const data: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
first: createSampleDatatableWithRows([
|
||||
{ a: 1, b: 2, c: 'I', d: 'Foo' },
|
||||
{ a: 1, b: 5, c: 'J', d: 'Bar' },
|
||||
]),
|
||||
},
|
||||
dateRange: {
|
||||
fromDate: new Date('2019-01-02T05:00:00.000Z'),
|
||||
toDate: new Date('2019-01-03T05:00:00.000Z'),
|
||||
},
|
||||
};
|
||||
|
||||
const args: XYArgs = createArgsWithLayers();
|
||||
|
||||
return { data, args };
|
||||
}
|
111
src/plugins/chart_expressions/expression_xy/common/constants.ts
Normal file
111
src/plugins/chart_expressions/expression_xy/common/constants.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const XY_VIS = 'xyVis';
|
||||
export const Y_CONFIG = 'yConfig';
|
||||
export const MULTITABLE = 'lens_multitable';
|
||||
export const DATA_LAYER = 'dataLayer';
|
||||
export const LEGEND_CONFIG = 'legendConfig';
|
||||
export const XY_VIS_RENDERER = 'xyVis';
|
||||
export const GRID_LINES_CONFIG = 'gridlinesConfig';
|
||||
export const ANNOTATION_LAYER = 'annotationLayer';
|
||||
export const TICK_LABELS_CONFIG = 'tickLabelsConfig';
|
||||
export const AXIS_EXTENT_CONFIG = 'axisExtentConfig';
|
||||
export const REFERENCE_LINE_LAYER = 'referenceLineLayer';
|
||||
export const LABELS_ORIENTATION_CONFIG = 'labelsOrientationConfig';
|
||||
export const AXIS_TITLES_VISIBILITY_CONFIG = 'axisTitlesVisibilityConfig';
|
||||
|
||||
export const LayerTypes = {
|
||||
DATA: 'data',
|
||||
REFERENCELINE: 'referenceLine',
|
||||
ANNOTATIONS: 'annotations',
|
||||
} as const;
|
||||
|
||||
export const FittingFunctions = {
|
||||
NONE: 'None',
|
||||
ZERO: 'Zero',
|
||||
LINEAR: 'Linear',
|
||||
CARRY: 'Carry',
|
||||
LOOKAHEAD: 'Lookahead',
|
||||
} as const;
|
||||
|
||||
export const EndValues = {
|
||||
NONE: 'None',
|
||||
ZERO: 'Zero',
|
||||
NEAREST: 'Nearest',
|
||||
} as const;
|
||||
|
||||
export const YAxisModes = {
|
||||
AUTO: 'auto',
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom',
|
||||
} as const;
|
||||
|
||||
export const AxisExtentModes = {
|
||||
FULL: 'full',
|
||||
CUSTOM: 'custom',
|
||||
DATA_BOUNDS: 'dataBounds',
|
||||
} as const;
|
||||
|
||||
export const LineStyles = {
|
||||
SOLID: 'solid',
|
||||
DASHED: 'dashed',
|
||||
DOTTED: 'dotted',
|
||||
} as const;
|
||||
|
||||
export const FillStyles = {
|
||||
NONE: 'none',
|
||||
ABOVE: 'above',
|
||||
BELOW: 'below',
|
||||
} as const;
|
||||
|
||||
export const IconPositions = {
|
||||
AUTO: 'auto',
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right',
|
||||
ABOVE: 'above',
|
||||
BELOW: 'below',
|
||||
} as const;
|
||||
|
||||
export const SeriesTypes = {
|
||||
BAR: 'bar',
|
||||
LINE: 'line',
|
||||
AREA: 'area',
|
||||
BAR_STACKED: 'bar_stacked',
|
||||
AREA_STACKED: 'area_stacked',
|
||||
BAR_HORIZONTAL: 'bar_horizontal',
|
||||
BAR_PERCENTAGE_STACKED: 'bar_percentage_stacked',
|
||||
BAR_HORIZONTAL_STACKED: 'bar_horizontal_stacked',
|
||||
AREA_PERCENTAGE_STACKED: 'area_percentage_stacked',
|
||||
BAR_HORIZONTAL_PERCENTAGE_STACKED: 'bar_horizontal_percentage_stacked',
|
||||
} as const;
|
||||
|
||||
export const YScaleTypes = {
|
||||
TIME: 'time',
|
||||
LINEAR: 'linear',
|
||||
LOG: 'log',
|
||||
SQRT: 'sqrt',
|
||||
} as const;
|
||||
|
||||
export const XScaleTypes = {
|
||||
TIME: 'time',
|
||||
LINEAR: 'linear',
|
||||
ORDINAL: 'ordinal',
|
||||
} as const;
|
||||
|
||||
export const XYCurveTypes = {
|
||||
LINEAR: 'LINEAR',
|
||||
CURVE_MONOTONE_X: 'CURVE_MONOTONE_X',
|
||||
} as const;
|
||||
|
||||
export const ValueLabelModes = {
|
||||
HIDE: 'hide',
|
||||
INSIDE: 'inside',
|
||||
OUTSIDE: 'outside',
|
||||
} as const;
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 type { ExpressionFunctionDefinition } from '../../../../expressions/common';
|
||||
import { LayerTypes, ANNOTATION_LAYER } from '../constants';
|
||||
import { AnnotationLayerArgs, AnnotationLayerConfigResult } from '../types';
|
||||
|
||||
export function annotationLayerConfigFunction(): ExpressionFunctionDefinition<
|
||||
typeof ANNOTATION_LAYER,
|
||||
null,
|
||||
AnnotationLayerArgs,
|
||||
AnnotationLayerConfigResult
|
||||
> {
|
||||
return {
|
||||
name: ANNOTATION_LAYER,
|
||||
aliases: [],
|
||||
type: ANNOTATION_LAYER,
|
||||
inputTypes: ['null'],
|
||||
help: 'Annotation layer in lens',
|
||||
args: {
|
||||
layerId: {
|
||||
types: ['string'],
|
||||
help: '',
|
||||
},
|
||||
hide: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: 'Show details',
|
||||
},
|
||||
annotations: {
|
||||
types: ['manual_event_annotation'],
|
||||
help: '',
|
||||
multi: true,
|
||||
},
|
||||
},
|
||||
fn: (input, args) => {
|
||||
return {
|
||||
type: ANNOTATION_LAYER,
|
||||
...args,
|
||||
layerType: LayerTypes.ANNOTATIONS,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../expressions/common';
|
||||
import { AxisExtentConfig, AxisExtentConfigResult } from '../types';
|
||||
import { AxisExtentModes, AXIS_EXTENT_CONFIG } from '../constants';
|
||||
|
||||
export const axisExtentConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof AXIS_EXTENT_CONFIG,
|
||||
null,
|
||||
AxisExtentConfig,
|
||||
AxisExtentConfigResult
|
||||
> = {
|
||||
name: AXIS_EXTENT_CONFIG,
|
||||
aliases: [],
|
||||
type: AXIS_EXTENT_CONFIG,
|
||||
help: i18n.translate('expressionXY.axisExtentConfig.help', {
|
||||
defaultMessage: `Configure the xy chart's axis extents`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
mode: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(AxisExtentModes)],
|
||||
help: i18n.translate('expressionXY.axisExtentConfig.extentMode.help', {
|
||||
defaultMessage: 'The extent mode',
|
||||
}),
|
||||
},
|
||||
lowerBound: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionXY.axisExtentConfig.lowerBound.help', {
|
||||
defaultMessage: 'Lower bound',
|
||||
}),
|
||||
},
|
||||
upperBound: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionXY.axisExtentConfig.upperBound.help', {
|
||||
defaultMessage: 'Upper bound',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: AXIS_EXTENT_CONFIG,
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../expressions/common';
|
||||
import { AXIS_TITLES_VISIBILITY_CONFIG } from '../constants';
|
||||
import { AxesSettingsConfig, AxisTitlesVisibilityConfigResult } from '../types';
|
||||
|
||||
export const axisTitlesVisibilityConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof AXIS_TITLES_VISIBILITY_CONFIG,
|
||||
null,
|
||||
AxesSettingsConfig,
|
||||
AxisTitlesVisibilityConfigResult
|
||||
> = {
|
||||
name: AXIS_TITLES_VISIBILITY_CONFIG,
|
||||
aliases: [],
|
||||
type: AXIS_TITLES_VISIBILITY_CONFIG,
|
||||
help: i18n.translate('expressionXY.axisTitlesVisibilityConfig.help', {
|
||||
defaultMessage: `Configure the xy chart's axis titles appearance`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
x: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionXY.axisTitlesVisibilityConfig.x.help', {
|
||||
defaultMessage: 'Specifies whether or not the title of the x-axis are visible.',
|
||||
}),
|
||||
},
|
||||
yLeft: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionXY.axisTitlesVisibilityConfig.yLeft.help', {
|
||||
defaultMessage: 'Specifies whether or not the title of the left y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
yRight: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionXY.axisTitlesVisibilityConfig.yRight.help', {
|
||||
defaultMessage: 'Specifies whether or not the title of the right y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn(inputn, args) {
|
||||
return {
|
||||
type: AXIS_TITLES_VISIBILITY_CONFIG,
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { DataLayerArgs } from '../types';
|
||||
import { dataLayerConfigFunction } from '../expression_functions';
|
||||
import { createMockExecutionContext } from '../../../../expressions/common/mocks';
|
||||
import { mockPaletteOutput } from '../__mocks__';
|
||||
import { LayerTypes } from '../constants';
|
||||
|
||||
describe('dataLayerConfig', () => {
|
||||
test('produces the correct arguments', () => {
|
||||
const args: DataLayerArgs = {
|
||||
layerId: 'first',
|
||||
seriesType: 'line',
|
||||
xAccessor: 'c',
|
||||
accessors: ['a', 'b'],
|
||||
splitAccessor: 'd',
|
||||
xScaleType: 'linear',
|
||||
yScaleType: 'linear',
|
||||
isHistogram: false,
|
||||
palette: mockPaletteOutput,
|
||||
};
|
||||
|
||||
const result = dataLayerConfigFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({ type: 'dataLayer', layerType: LayerTypes.DATA, ...args });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../expressions/common';
|
||||
import { DataLayerArgs, DataLayerConfigResult } from '../types';
|
||||
import {
|
||||
DATA_LAYER,
|
||||
LayerTypes,
|
||||
SeriesTypes,
|
||||
XScaleTypes,
|
||||
YScaleTypes,
|
||||
Y_CONFIG,
|
||||
} from '../constants';
|
||||
|
||||
export const dataLayerConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof DATA_LAYER,
|
||||
null,
|
||||
DataLayerArgs,
|
||||
DataLayerConfigResult
|
||||
> = {
|
||||
name: DATA_LAYER,
|
||||
aliases: [],
|
||||
type: DATA_LAYER,
|
||||
help: i18n.translate('expressionXY.dataLayer.help', {
|
||||
defaultMessage: `Configure a layer in the xy chart`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
hide: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: i18n.translate('expressionXY.dataLayer.hide.help', {
|
||||
defaultMessage: 'Show / hide axis',
|
||||
}),
|
||||
},
|
||||
layerId: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.dataLayer.layerId.help', {
|
||||
defaultMessage: 'Layer ID',
|
||||
}),
|
||||
},
|
||||
xAccessor: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.dataLayer.xAccessor.help', {
|
||||
defaultMessage: 'X-axis',
|
||||
}),
|
||||
},
|
||||
seriesType: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(SeriesTypes)],
|
||||
help: i18n.translate('expressionXY.dataLayer.seriesType.help', {
|
||||
defaultMessage: 'The type of chart to display.',
|
||||
}),
|
||||
},
|
||||
xScaleType: {
|
||||
options: [...Object.values(XScaleTypes)],
|
||||
help: i18n.translate('expressionXY.dataLayer.xScaleType.help', {
|
||||
defaultMessage: 'The scale type of the x axis',
|
||||
}),
|
||||
default: XScaleTypes.ORDINAL,
|
||||
},
|
||||
isHistogram: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: i18n.translate('expressionXY.dataLayer.isHistogram.help', {
|
||||
defaultMessage: 'Whether to layout the chart as a histogram',
|
||||
}),
|
||||
},
|
||||
yScaleType: {
|
||||
options: [...Object.values(YScaleTypes)],
|
||||
help: i18n.translate('expressionXY.dataLayer.yScaleType.help', {
|
||||
defaultMessage: 'The scale type of the y axes',
|
||||
}),
|
||||
default: YScaleTypes.LINEAR,
|
||||
},
|
||||
splitAccessor: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.dataLayer.splitAccessor.help', {
|
||||
defaultMessage: 'The column to split by',
|
||||
}),
|
||||
},
|
||||
accessors: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.dataLayer.accessors.help', {
|
||||
defaultMessage: 'The columns to display on the y axis.',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
yConfig: {
|
||||
types: [Y_CONFIG],
|
||||
help: i18n.translate('expressionXY.dataLayer.yConfig.help', {
|
||||
defaultMessage: 'Additional configuration for y axes',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
columnToLabel: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.dataLayer.columnToLabel.help', {
|
||||
defaultMessage: 'JSON key-value pairs of column ID to label',
|
||||
}),
|
||||
},
|
||||
palette: {
|
||||
default: `{theme "palette" default={system_palette name="default"} }`,
|
||||
help: i18n.translate('expressionXY.dataLayer.palette.help', {
|
||||
defaultMessage: 'Palette',
|
||||
}),
|
||||
types: ['palette'],
|
||||
},
|
||||
},
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: DATA_LAYER,
|
||||
...args,
|
||||
layerType: LayerTypes.DATA,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { AxesSettingsConfig } from '../types';
|
||||
import { gridlinesConfigFunction } from '../expression_functions';
|
||||
import { createMockExecutionContext } from '../../../../../plugins/expressions/common/mocks';
|
||||
|
||||
describe('gridlinesConfig', () => {
|
||||
test('produces the correct arguments', () => {
|
||||
const args: AxesSettingsConfig = { x: true, yLeft: false, yRight: false };
|
||||
const result = gridlinesConfigFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({ type: 'gridlinesConfig', ...args });
|
||||
});
|
||||
});
|
|
@ -1,50 +1,52 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
|
||||
import type { AxesSettingsConfig } from './axis_config';
|
||||
import { GRID_LINES_CONFIG } from '../constants';
|
||||
import { AxesSettingsConfig, GridlinesConfigResult } from '../types';
|
||||
|
||||
export type GridlinesConfigResult = AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' };
|
||||
|
||||
export const gridlinesConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_gridlinesConfig',
|
||||
export const gridlinesConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof GRID_LINES_CONFIG,
|
||||
null,
|
||||
AxesSettingsConfig,
|
||||
GridlinesConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_gridlinesConfig',
|
||||
name: GRID_LINES_CONFIG,
|
||||
aliases: [],
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
help: `Configure the xy chart's gridlines appearance`,
|
||||
type: GRID_LINES_CONFIG,
|
||||
help: i18n.translate('expressionXY.gridlinesConfig.help', {
|
||||
defaultMessage: `Configure the xy chart's gridlines appearance`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
x: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.xAxisGridlines.help', {
|
||||
help: i18n.translate('expressionXY.gridlinesConfig.x.help', {
|
||||
defaultMessage: 'Specifies whether or not the gridlines of the x-axis are visible.',
|
||||
}),
|
||||
},
|
||||
yLeft: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yLeftAxisgridlines.help', {
|
||||
help: i18n.translate('expressionXY.gridlinesConfig.yLeft.help', {
|
||||
defaultMessage: 'Specifies whether or not the gridlines of the left y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
yRight: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yRightAxisgridlines.help', {
|
||||
help: i18n.translate('expressionXY.gridlinesConfig.yRight.help', {
|
||||
defaultMessage: 'Specifies whether or not the gridlines of the right y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: AxesSettingsConfig) {
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
type: GRID_LINES_CONFIG,
|
||||
...args,
|
||||
};
|
||||
},
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './xy_vis';
|
||||
export * from './legend_config';
|
||||
export * from './annotation_layer_config';
|
||||
export * from './y_axis_config';
|
||||
export * from './data_layer_config';
|
||||
export * from './grid_lines_config';
|
||||
export * from './axis_extent_config';
|
||||
export * from './tick_labels_config';
|
||||
export * from './labels_orientation_config';
|
||||
export * from './reference_line_layer_config';
|
||||
export * from './axis_titles_visibility_config';
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { LabelsOrientationConfig } from '../types';
|
||||
import { labelsOrientationConfigFunction } from '../expression_functions';
|
||||
import { createMockExecutionContext } from '../../../../../plugins/expressions/common/mocks';
|
||||
|
||||
describe('labelsOrientationConfig', () => {
|
||||
test('produces the correct arguments', () => {
|
||||
const args: LabelsOrientationConfig = { x: 0, yLeft: -90, yRight: -45 };
|
||||
const result = labelsOrientationConfigFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({ type: 'labelsOrientationConfig', ...args });
|
||||
});
|
||||
});
|
|
@ -1,59 +1,55 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
|
||||
import { LABELS_ORIENTATION_CONFIG } from '../constants';
|
||||
import { LabelsOrientationConfig, LabelsOrientationConfigResult } from '../types';
|
||||
|
||||
export interface LabelsOrientationConfig {
|
||||
x: number;
|
||||
yLeft: number;
|
||||
yRight: number;
|
||||
}
|
||||
|
||||
export type LabelsOrientationConfigResult = LabelsOrientationConfig & {
|
||||
type: 'lens_xy_labelsOrientationConfig';
|
||||
};
|
||||
|
||||
export const labelsOrientationConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_labelsOrientationConfig',
|
||||
export const labelsOrientationConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof LABELS_ORIENTATION_CONFIG,
|
||||
null,
|
||||
LabelsOrientationConfig,
|
||||
LabelsOrientationConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_labelsOrientationConfig',
|
||||
name: LABELS_ORIENTATION_CONFIG,
|
||||
aliases: [],
|
||||
type: 'lens_xy_labelsOrientationConfig',
|
||||
help: `Configure the xy chart's tick labels orientation`,
|
||||
type: LABELS_ORIENTATION_CONFIG,
|
||||
help: i18n.translate('expressionXY.labelsOrientationConfig.help', {
|
||||
defaultMessage: `Configure the xy chart's tick labels orientation`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
x: {
|
||||
types: ['number'],
|
||||
options: [0, -90, -45],
|
||||
help: i18n.translate('xpack.lens.xyChart.xAxisLabelsOrientation.help', {
|
||||
help: i18n.translate('expressionXY.labelsOrientationConfig.x.help', {
|
||||
defaultMessage: 'Specifies the labels orientation of the x-axis.',
|
||||
}),
|
||||
},
|
||||
yLeft: {
|
||||
types: ['number'],
|
||||
options: [0, -90, -45],
|
||||
help: i18n.translate('xpack.lens.xyChart.yLeftAxisLabelsOrientation.help', {
|
||||
help: i18n.translate('expressionXY.labelsOrientationConfig.yLeft.help', {
|
||||
defaultMessage: 'Specifies the labels orientation of the left y-axis.',
|
||||
}),
|
||||
},
|
||||
yRight: {
|
||||
types: ['number'],
|
||||
options: [0, -90, -45],
|
||||
help: i18n.translate('xpack.lens.xyChart.yRightAxisLabelsOrientation.help', {
|
||||
help: i18n.translate('expressionXY.labelsOrientationConfig.yRight.help', {
|
||||
defaultMessage: 'Specifies the labels orientation of the right y-axis.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: LabelsOrientationConfig) {
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: 'lens_xy_labelsOrientationConfig',
|
||||
type: LABELS_ORIENTATION_CONFIG,
|
||||
...args,
|
||||
};
|
||||
},
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { Position } from '@elastic/charts';
|
||||
import { createMockExecutionContext } from '../../../../expressions/common/mocks';
|
||||
import { LegendConfig } from '../types';
|
||||
import { legendConfigFunction } from './legend_config';
|
||||
|
||||
describe('legendConfigFunction', () => {
|
||||
test('produces the correct arguments', () => {
|
||||
const args: LegendConfig = { isVisible: true, position: Position.Left };
|
||||
const result = legendConfigFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({ type: 'legendConfig', ...args });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
|
||||
import { LEGEND_CONFIG } from '../constants';
|
||||
import { LegendConfig, LegendConfigResult } from '../types';
|
||||
|
||||
export const legendConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof LEGEND_CONFIG,
|
||||
null,
|
||||
LegendConfig,
|
||||
LegendConfigResult
|
||||
> = {
|
||||
name: LEGEND_CONFIG,
|
||||
aliases: [],
|
||||
type: LEGEND_CONFIG,
|
||||
help: i18n.translate('expressionXY.legendConfig.help', {
|
||||
defaultMessage: `Configure the xy chart's legend`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
isVisible: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionXY.legendConfig.isVisible.help', {
|
||||
defaultMessage: 'Specifies whether or not the legend is visible.',
|
||||
}),
|
||||
},
|
||||
position: {
|
||||
types: ['string'],
|
||||
options: [Position.Top, Position.Right, Position.Bottom, Position.Left],
|
||||
help: i18n.translate('expressionXY.legendConfig.position.help', {
|
||||
defaultMessage: 'Specifies the legend position.',
|
||||
}),
|
||||
},
|
||||
showSingleSeries: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionXY.legendConfig.showSingleSeries.help', {
|
||||
defaultMessage: 'Specifies whether a legend with just a single entry should be shown',
|
||||
}),
|
||||
},
|
||||
isInside: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionXY.legendConfig.isInside.help', {
|
||||
defaultMessage: 'Specifies whether a legend is inside the chart',
|
||||
}),
|
||||
},
|
||||
horizontalAlignment: {
|
||||
types: ['string'],
|
||||
options: [HorizontalAlignment.Right, HorizontalAlignment.Left],
|
||||
help: i18n.translate('expressionXY.legendConfig.horizontalAlignment.help', {
|
||||
defaultMessage:
|
||||
'Specifies the horizontal alignment of the legend when it is displayed inside chart.',
|
||||
}),
|
||||
},
|
||||
verticalAlignment: {
|
||||
types: ['string'],
|
||||
options: [VerticalAlignment.Top, VerticalAlignment.Bottom],
|
||||
help: i18n.translate('expressionXY.legendConfig.verticalAlignment.help', {
|
||||
defaultMessage:
|
||||
'Specifies the vertical alignment of the legend when it is displayed inside chart.',
|
||||
}),
|
||||
},
|
||||
floatingColumns: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionXY.legendConfig.floatingColumns.help', {
|
||||
defaultMessage: 'Specifies the number of columns when legend is displayed inside chart.',
|
||||
}),
|
||||
},
|
||||
maxLines: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionXY.legendConfig.maxLines.help', {
|
||||
defaultMessage: 'Specifies the number of lines per legend item.',
|
||||
}),
|
||||
},
|
||||
shouldTruncate: {
|
||||
types: ['boolean'],
|
||||
default: true,
|
||||
help: i18n.translate('expressionXY.legendConfig.shouldTruncate.help', {
|
||||
defaultMessage: 'Specifies whether the legend items will be truncated or not',
|
||||
}),
|
||||
},
|
||||
legendSize: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionXY.legendConfig.legendSize.help', {
|
||||
defaultMessage: 'Specifies the legend size in pixels.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: LEGEND_CONFIG,
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../expressions/common';
|
||||
import { LayerTypes, REFERENCE_LINE_LAYER, Y_CONFIG } from '../constants';
|
||||
import { ReferenceLineLayerArgs, ReferenceLineLayerConfigResult } from '../types';
|
||||
|
||||
export const referenceLineLayerConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof REFERENCE_LINE_LAYER,
|
||||
null,
|
||||
ReferenceLineLayerArgs,
|
||||
ReferenceLineLayerConfigResult
|
||||
> = {
|
||||
name: REFERENCE_LINE_LAYER,
|
||||
aliases: [],
|
||||
type: REFERENCE_LINE_LAYER,
|
||||
help: i18n.translate('expressionXY.referenceLineLayer.help', {
|
||||
defaultMessage: `Configure a reference line in the xy chart`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
layerId: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.referenceLineLayer.layerId.help', {
|
||||
defaultMessage: `Layer ID`,
|
||||
}),
|
||||
},
|
||||
accessors: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.referenceLineLayer.accessors.help', {
|
||||
defaultMessage: 'The columns to display on the y axis.',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
yConfig: {
|
||||
types: [Y_CONFIG],
|
||||
help: i18n.translate('expressionXY.referenceLineLayer.yConfig.help', {
|
||||
defaultMessage: 'Additional configuration for y axes',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
columnToLabel: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.referenceLineLayer.columnToLabel.help', {
|
||||
defaultMessage: 'JSON key-value pairs of column ID to label',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: REFERENCE_LINE_LAYER,
|
||||
...args,
|
||||
layerType: LayerTypes.REFERENCELINE,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { AxesSettingsConfig } from '../types';
|
||||
import { tickLabelsConfigFunction } from '../expression_functions';
|
||||
import { createMockExecutionContext } from '../../../../../plugins/expressions/common/mocks';
|
||||
|
||||
describe('tickLabelsConfig', () => {
|
||||
test('produces the correct arguments', () => {
|
||||
const args: AxesSettingsConfig = { x: true, yLeft: false, yRight: false };
|
||||
const result = tickLabelsConfigFunction.fn(null, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({ type: 'tickLabelsConfig', ...args });
|
||||
});
|
||||
});
|
|
@ -1,50 +1,52 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
|
||||
import type { AxesSettingsConfig } from './axis_config';
|
||||
import { TICK_LABELS_CONFIG } from '../constants';
|
||||
import { AxesSettingsConfig, TickLabelsConfigResult } from '../types';
|
||||
|
||||
export type TickLabelsConfigResult = AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' };
|
||||
|
||||
export const tickLabelsConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_tickLabelsConfig',
|
||||
export const tickLabelsConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof TICK_LABELS_CONFIG,
|
||||
null,
|
||||
AxesSettingsConfig,
|
||||
TickLabelsConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_tickLabelsConfig',
|
||||
name: TICK_LABELS_CONFIG,
|
||||
aliases: [],
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
help: `Configure the xy chart's tick labels appearance`,
|
||||
type: TICK_LABELS_CONFIG,
|
||||
help: i18n.translate('expressionXY.tickLabelsConfig.help', {
|
||||
defaultMessage: `Configure the xy chart's tick labels appearance`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
x: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.xAxisTickLabels.help', {
|
||||
help: i18n.translate('expressionXY.tickLabelsConfig.x.help', {
|
||||
defaultMessage: 'Specifies whether or not the tick labels of the x-axis are visible.',
|
||||
}),
|
||||
},
|
||||
yLeft: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yLeftAxisTickLabels.help', {
|
||||
help: i18n.translate('expressionXY.tickLabelsConfig.yLeft.help', {
|
||||
defaultMessage: 'Specifies whether or not the tick labels of the left y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
yRight: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yRightAxisTickLabels.help', {
|
||||
help: i18n.translate('expressionXY.tickLabelsConfig.yRight.help', {
|
||||
defaultMessage: 'Specifies whether or not the tick labels of the right y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: AxesSettingsConfig) {
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
type: TICK_LABELS_CONFIG,
|
||||
...args,
|
||||
};
|
||||
},
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { xyVisFunction } from '../expression_functions';
|
||||
import { createMockExecutionContext } from '../../../../../plugins/expressions/common/mocks';
|
||||
import { sampleArgs } from '../__mocks__';
|
||||
import { XY_VIS } from '../constants';
|
||||
|
||||
describe('xyVis', () => {
|
||||
test('it renders with the specified data and args', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
const result = xyVisFunction.fn(data, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({ type: 'render', as: XY_VIS, value: { data, args } });
|
||||
});
|
||||
});
|
|
@ -1,58 +1,64 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
|
||||
import type { ExpressionValueSearchContext } from '../../../../../../src/plugins/data/common';
|
||||
import type { LensMultiTable } from '../../types';
|
||||
import type { XYArgs } from './xy_args';
|
||||
import { fittingFunctionDefinitions } from './fitting_function';
|
||||
import { prepareLogTable } from '../../../../../../src/plugins/visualizations/common/utils';
|
||||
import { endValueDefinitions } from './end_value';
|
||||
|
||||
export interface XYChartProps {
|
||||
data: LensMultiTable;
|
||||
args: XYArgs;
|
||||
}
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../expressions';
|
||||
import { LensMultiTable, XYArgs, XYRender } from '../types';
|
||||
import { prepareLogTable } from '../../../../../../src/plugins/visualizations/common/utils';
|
||||
import {
|
||||
XY_VIS,
|
||||
DATA_LAYER,
|
||||
MULTITABLE,
|
||||
XYCurveTypes,
|
||||
LEGEND_CONFIG,
|
||||
ValueLabelModes,
|
||||
FittingFunctions,
|
||||
GRID_LINES_CONFIG,
|
||||
XY_VIS_RENDERER,
|
||||
AXIS_EXTENT_CONFIG,
|
||||
TICK_LABELS_CONFIG,
|
||||
REFERENCE_LINE_LAYER,
|
||||
LABELS_ORIENTATION_CONFIG,
|
||||
AXIS_TITLES_VISIBILITY_CONFIG,
|
||||
EndValues,
|
||||
ANNOTATION_LAYER,
|
||||
LayerTypes,
|
||||
} from '../constants';
|
||||
|
||||
const strings = {
|
||||
getMetricHelp: () =>
|
||||
i18n.translate('xpack.lens.xy.logDatatable.metric', {
|
||||
i18n.translate('expressionXY.xyVis.logDatatable.metric', {
|
||||
defaultMessage: 'Vertical axis',
|
||||
}),
|
||||
getXAxisHelp: () =>
|
||||
i18n.translate('xpack.lens.xy.logDatatable.x', {
|
||||
i18n.translate('expressionXY.xyVis.logDatatable.x', {
|
||||
defaultMessage: 'Horizontal axis',
|
||||
}),
|
||||
getBreakdownHelp: () =>
|
||||
i18n.translate('xpack.lens.xy.logDatatable.breakDown', {
|
||||
i18n.translate('expressionXY.xyVis.logDatatable.breakDown', {
|
||||
defaultMessage: 'Break down by',
|
||||
}),
|
||||
getReferenceLineHelp: () =>
|
||||
i18n.translate('xpack.lens.xy.logDatatable.breakDown', {
|
||||
i18n.translate('expressionXY.xyVis.logDatatable.breakDown', {
|
||||
defaultMessage: 'Break down by',
|
||||
}),
|
||||
};
|
||||
|
||||
export interface XYRender {
|
||||
type: 'render';
|
||||
as: 'lens_xy_chart_renderer';
|
||||
value: XYChartProps;
|
||||
}
|
||||
|
||||
export const xyChart: ExpressionFunctionDefinition<
|
||||
'lens_xy_chart',
|
||||
LensMultiTable | ExpressionValueSearchContext | null,
|
||||
export const xyVisFunction: ExpressionFunctionDefinition<
|
||||
typeof XY_VIS,
|
||||
LensMultiTable,
|
||||
XYArgs,
|
||||
XYRender
|
||||
> = {
|
||||
name: 'lens_xy_chart',
|
||||
name: XY_VIS,
|
||||
type: 'render',
|
||||
inputTypes: ['lens_multitable', 'kibana_context', 'null'],
|
||||
help: i18n.translate('xpack.lens.xyChart.help', {
|
||||
inputTypes: [MULTITABLE],
|
||||
help: i18n.translate('expressionXY.xyVis.help', {
|
||||
defaultMessage: 'An X/Y chart',
|
||||
}),
|
||||
args: {
|
||||
|
@ -66,51 +72,53 @@ export const xyChart: ExpressionFunctionDefinition<
|
|||
},
|
||||
xTitle: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('xpack.lens.xyChart.xTitle.help', {
|
||||
help: i18n.translate('expressionXY.xyVis.xTitle.help', {
|
||||
defaultMessage: 'X axis title',
|
||||
}),
|
||||
},
|
||||
yTitle: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yLeftTitle.help', {
|
||||
help: i18n.translate('expressionXY.xyVis.yLeftTitle.help', {
|
||||
defaultMessage: 'Y left axis title',
|
||||
}),
|
||||
},
|
||||
yRightTitle: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yRightTitle.help', {
|
||||
help: i18n.translate('expressionXY.xyVis.yRightTitle.help', {
|
||||
defaultMessage: 'Y right axis title',
|
||||
}),
|
||||
},
|
||||
yLeftExtent: {
|
||||
types: ['lens_xy_axisExtentConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yLeftExtent.help', {
|
||||
types: [AXIS_EXTENT_CONFIG],
|
||||
help: i18n.translate('expressionXY.xyVis.yLeftExtent.help', {
|
||||
defaultMessage: 'Y left axis extents',
|
||||
}),
|
||||
},
|
||||
yRightExtent: {
|
||||
types: ['lens_xy_axisExtentConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yRightExtent.help', {
|
||||
types: [AXIS_EXTENT_CONFIG],
|
||||
help: i18n.translate('expressionXY.xyVis.yRightExtent.help', {
|
||||
defaultMessage: 'Y right axis extents',
|
||||
}),
|
||||
},
|
||||
legend: {
|
||||
types: ['lens_xy_legendConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.legend.help', {
|
||||
types: [LEGEND_CONFIG],
|
||||
help: i18n.translate('expressionXY.xyVis.legend.help', {
|
||||
defaultMessage: 'Configure the chart legend.',
|
||||
}),
|
||||
},
|
||||
fittingFunction: {
|
||||
types: ['string'],
|
||||
options: [...fittingFunctionDefinitions.map(({ id }) => id)],
|
||||
help: i18n.translate('xpack.lens.xyChart.fittingFunction.help', {
|
||||
options: [...Object.values(FittingFunctions)],
|
||||
help: i18n.translate('expressionXY.xyVis.fittingFunction.help', {
|
||||
defaultMessage: 'Define how missing values are treated',
|
||||
}),
|
||||
},
|
||||
endValue: {
|
||||
types: ['string'],
|
||||
options: [...endValueDefinitions.map(({ id }) => id)],
|
||||
help: '',
|
||||
options: [...Object.values(EndValues)],
|
||||
help: i18n.translate('expressionXY.xyVis.endValue.help', {
|
||||
defaultMessage: 'End value',
|
||||
}),
|
||||
},
|
||||
emphasizeFitting: {
|
||||
types: ['boolean'],
|
||||
|
@ -119,85 +127,92 @@ export const xyChart: ExpressionFunctionDefinition<
|
|||
},
|
||||
valueLabels: {
|
||||
types: ['string'],
|
||||
options: ['hide', 'inside'],
|
||||
help: '',
|
||||
options: [...Object.values(ValueLabelModes)],
|
||||
help: i18n.translate('expressionXY.xyVis.valueLabels.help', {
|
||||
defaultMessage: 'Value labels mode',
|
||||
}),
|
||||
},
|
||||
tickLabelsVisibilitySettings: {
|
||||
types: ['lens_xy_tickLabelsConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', {
|
||||
types: [TICK_LABELS_CONFIG],
|
||||
help: i18n.translate('expressionXY.xyVis.tickLabelsVisibilitySettings.help', {
|
||||
defaultMessage: 'Show x and y axes tick labels',
|
||||
}),
|
||||
},
|
||||
labelsOrientation: {
|
||||
types: ['lens_xy_labelsOrientationConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.labelsOrientation.help', {
|
||||
types: [LABELS_ORIENTATION_CONFIG],
|
||||
help: i18n.translate('expressionXY.xyVis.labelsOrientation.help', {
|
||||
defaultMessage: 'Defines the rotation of the axis labels',
|
||||
}),
|
||||
},
|
||||
gridlinesVisibilitySettings: {
|
||||
types: ['lens_xy_gridlinesConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.gridlinesSettings.help', {
|
||||
types: [GRID_LINES_CONFIG],
|
||||
help: i18n.translate('expressionXY.xyVis.gridlinesVisibilitySettings.help', {
|
||||
defaultMessage: 'Show x and y axes gridlines',
|
||||
}),
|
||||
},
|
||||
axisTitlesVisibilitySettings: {
|
||||
types: ['lens_xy_axisTitlesVisibilityConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.axisTitlesSettings.help', {
|
||||
types: [AXIS_TITLES_VISIBILITY_CONFIG],
|
||||
help: i18n.translate('expressionXY.xyVis.axisTitlesVisibilitySettings.help', {
|
||||
defaultMessage: 'Show x and y axes titles',
|
||||
}),
|
||||
},
|
||||
layers: {
|
||||
types: [
|
||||
'lens_xy_data_layer',
|
||||
'lens_xy_referenceLine_layer',
|
||||
'lens_xy_annotation_layer',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
] as any,
|
||||
help: 'Layers of visual series',
|
||||
types: [DATA_LAYER, REFERENCE_LINE_LAYER, ANNOTATION_LAYER],
|
||||
help: i18n.translate('expressionXY.xyVis.layers.help', {
|
||||
defaultMessage: 'Layers of visual series',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
curveType: {
|
||||
types: ['string'],
|
||||
options: ['LINEAR', 'CURVE_MONOTONE_X'],
|
||||
help: i18n.translate('xpack.lens.xyChart.curveType.help', {
|
||||
options: [...Object.values(XYCurveTypes)],
|
||||
help: i18n.translate('expressionXY.xyVis.curveType.help', {
|
||||
defaultMessage: 'Define how curve type is rendered for a line chart',
|
||||
}),
|
||||
},
|
||||
fillOpacity: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('xpack.lens.xyChart.fillOpacity.help', {
|
||||
help: i18n.translate('expressionXY.xyVis.fillOpacity.help', {
|
||||
defaultMessage: 'Define the area chart fill opacity',
|
||||
}),
|
||||
},
|
||||
hideEndzones: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: i18n.translate('xpack.lens.xyChart.hideEndzones.help', {
|
||||
help: i18n.translate('expressionXY.xyVis.hideEndzones.help', {
|
||||
defaultMessage: 'Hide endzone markers for partial data',
|
||||
}),
|
||||
},
|
||||
valuesInLegend: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: i18n.translate('xpack.lens.xyChart.valuesInLegend.help', {
|
||||
help: i18n.translate('expressionXY.xyVis.valuesInLegend.help', {
|
||||
defaultMessage: 'Show values in legend',
|
||||
}),
|
||||
},
|
||||
ariaLabel: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('xpack.lens.xyChart.ariaLabel.help', {
|
||||
help: i18n.translate('expressionXY.xyVis.ariaLabel.help', {
|
||||
defaultMessage: 'Specifies the aria label of the xy chart',
|
||||
}),
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
fn(data: LensMultiTable, args: XYArgs, handlers) {
|
||||
fn(data, args, handlers) {
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
args.layers.forEach((layer) => {
|
||||
if (layer.layerType === 'annotations') {
|
||||
if (layer.layerType === LayerTypes.ANNOTATIONS) {
|
||||
return;
|
||||
}
|
||||
const { layerId, accessors, xAccessor, splitAccessor, layerType } = layer;
|
||||
|
||||
let xAccessor;
|
||||
let splitAccessor;
|
||||
if (layer.layerType === LayerTypes.DATA) {
|
||||
xAccessor = layer.xAccessor;
|
||||
splitAccessor = layer.splitAccessor;
|
||||
}
|
||||
|
||||
const { layerId, accessors, layerType } = layer;
|
||||
const logTable = prepareLogTable(
|
||||
data.tables[layerId],
|
||||
[
|
||||
|
@ -214,9 +229,10 @@ export const xyChart: ExpressionFunctionDefinition<
|
|||
handlers.inspectorAdapters.tables.logDatatable(layerId, logTable);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: 'lens_xy_chart_renderer',
|
||||
as: XY_VIS_RENDERER,
|
||||
value: {
|
||||
data,
|
||||
args: {
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../expressions/common';
|
||||
import { FillStyles, IconPositions, LineStyles, YAxisModes, Y_CONFIG } from '../constants';
|
||||
import { YConfig, YConfigResult } from '../types';
|
||||
|
||||
export const yAxisConfigFunction: ExpressionFunctionDefinition<
|
||||
typeof Y_CONFIG,
|
||||
null,
|
||||
YConfig,
|
||||
YConfigResult
|
||||
> = {
|
||||
name: Y_CONFIG,
|
||||
aliases: [],
|
||||
type: Y_CONFIG,
|
||||
help: i18n.translate('expressionXY.yConfig.help', {
|
||||
defaultMessage: `Configure the behavior of a xy chart's y axis metric`,
|
||||
}),
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
forAccessor: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.yConfig.forAccessor.help', {
|
||||
defaultMessage: 'The accessor this configuration is for',
|
||||
}),
|
||||
},
|
||||
axisMode: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(YAxisModes)],
|
||||
help: i18n.translate('expressionXY.yConfig.axisMode.help', {
|
||||
defaultMessage: 'The axis mode of the metric',
|
||||
}),
|
||||
},
|
||||
color: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.yConfig.color.help', {
|
||||
defaultMessage: 'The color of the series',
|
||||
}),
|
||||
},
|
||||
lineStyle: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(LineStyles)],
|
||||
help: i18n.translate('expressionXY.yConfig.lineStyle.help', {
|
||||
defaultMessage: 'The style of the reference line',
|
||||
}),
|
||||
},
|
||||
lineWidth: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionXY.yConfig.lineWidth.help', {
|
||||
defaultMessage: 'The width of the reference line',
|
||||
}),
|
||||
},
|
||||
icon: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionXY.yConfig.icon.help', {
|
||||
defaultMessage: 'An optional icon used for reference lines',
|
||||
}),
|
||||
},
|
||||
iconPosition: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(IconPositions)],
|
||||
help: i18n.translate('expressionXY.yConfig.iconPosition.help', {
|
||||
defaultMessage: 'The placement of the icon for the reference line',
|
||||
}),
|
||||
},
|
||||
textVisibility: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionXY.yConfig.textVisibility.help', {
|
||||
defaultMessage: 'Visibility of the label on the reference line',
|
||||
}),
|
||||
},
|
||||
fill: {
|
||||
types: ['string'],
|
||||
options: [...Object.values(FillStyles)],
|
||||
help: i18n.translate('expressionXY.yConfig.fill.help', {
|
||||
defaultMessage: 'Fill',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn(input, args) {
|
||||
return {
|
||||
type: Y_CONFIG,
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
66
src/plugins/chart_expressions/expression_xy/common/index.ts
Executable file
66
src/plugins/chart_expressions/expression_xy/common/index.ts
Executable file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const PLUGIN_ID = 'expressionXy';
|
||||
export const PLUGIN_NAME = 'expressionXy';
|
||||
|
||||
export {
|
||||
xyVisFunction,
|
||||
yAxisConfigFunction,
|
||||
legendConfigFunction,
|
||||
gridlinesConfigFunction,
|
||||
dataLayerConfigFunction,
|
||||
axisExtentConfigFunction,
|
||||
tickLabelsConfigFunction,
|
||||
annotationLayerConfigFunction,
|
||||
labelsOrientationConfigFunction,
|
||||
referenceLineLayerConfigFunction,
|
||||
axisTitlesVisibilityConfigFunction,
|
||||
} from './expression_functions';
|
||||
|
||||
export type {
|
||||
XYArgs,
|
||||
YConfig,
|
||||
EndValue,
|
||||
XYRender,
|
||||
LayerType,
|
||||
YAxisMode,
|
||||
LineStyle,
|
||||
FillStyle,
|
||||
SeriesType,
|
||||
YScaleType,
|
||||
XScaleType,
|
||||
AxisConfig,
|
||||
ValidLayer,
|
||||
XYLayerArgs,
|
||||
XYCurveType,
|
||||
XYChartProps,
|
||||
LegendConfig,
|
||||
IconPosition,
|
||||
YConfigResult,
|
||||
DataLayerArgs,
|
||||
LensMultiTable,
|
||||
ValueLabelMode,
|
||||
AxisExtentMode,
|
||||
FittingFunction,
|
||||
AxisExtentConfig,
|
||||
LegendConfigResult,
|
||||
AxesSettingsConfig,
|
||||
AnnotationLayerArgs,
|
||||
XYLayerConfigResult,
|
||||
GridlinesConfigResult,
|
||||
DataLayerConfigResult,
|
||||
TickLabelsConfigResult,
|
||||
AxisExtentConfigResult,
|
||||
ReferenceLineLayerArgs,
|
||||
LabelsOrientationConfig,
|
||||
AnnotationLayerConfigResult,
|
||||
LabelsOrientationConfigResult,
|
||||
ReferenceLineLayerConfigResult,
|
||||
AxisTitlesVisibilityConfigResult,
|
||||
} from './types';
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* 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 { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts';
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import { Datatable } from '../../../../expressions';
|
||||
import { PaletteOutput } from '../../../../charts/common';
|
||||
import { EventAnnotationOutput } from '../../../../event_annotation/common';
|
||||
import {
|
||||
AxisExtentModes,
|
||||
FillStyles,
|
||||
FittingFunctions,
|
||||
IconPositions,
|
||||
LayerTypes,
|
||||
MULTITABLE,
|
||||
LineStyles,
|
||||
SeriesTypes,
|
||||
ValueLabelModes,
|
||||
XScaleTypes,
|
||||
XYCurveTypes,
|
||||
YAxisModes,
|
||||
YScaleTypes,
|
||||
REFERENCE_LINE_LAYER,
|
||||
Y_CONFIG,
|
||||
AXIS_TITLES_VISIBILITY_CONFIG,
|
||||
LABELS_ORIENTATION_CONFIG,
|
||||
TICK_LABELS_CONFIG,
|
||||
GRID_LINES_CONFIG,
|
||||
LEGEND_CONFIG,
|
||||
DATA_LAYER,
|
||||
AXIS_EXTENT_CONFIG,
|
||||
ANNOTATION_LAYER,
|
||||
EndValues,
|
||||
} from '../constants';
|
||||
|
||||
export type EndValue = $Values<typeof EndValues>;
|
||||
export type LayerType = $Values<typeof LayerTypes>;
|
||||
export type YAxisMode = $Values<typeof YAxisModes>;
|
||||
export type LineStyle = $Values<typeof LineStyles>;
|
||||
export type FillStyle = $Values<typeof FillStyles>;
|
||||
export type SeriesType = $Values<typeof SeriesTypes>;
|
||||
export type YScaleType = $Values<typeof YScaleTypes>;
|
||||
export type XScaleType = $Values<typeof XScaleTypes>;
|
||||
export type XYCurveType = $Values<typeof XYCurveTypes>;
|
||||
export type IconPosition = $Values<typeof IconPositions>;
|
||||
export type ValueLabelMode = $Values<typeof ValueLabelModes>;
|
||||
export type AxisExtentMode = $Values<typeof AxisExtentModes>;
|
||||
export type FittingFunction = $Values<typeof FittingFunctions>;
|
||||
|
||||
export interface AxesSettingsConfig {
|
||||
x: boolean;
|
||||
yLeft: boolean;
|
||||
yRight: boolean;
|
||||
}
|
||||
|
||||
export interface AxisExtentConfig {
|
||||
mode: AxisExtentMode;
|
||||
lowerBound?: number;
|
||||
upperBound?: number;
|
||||
}
|
||||
|
||||
export interface AxisConfig {
|
||||
title: string;
|
||||
hide?: boolean;
|
||||
}
|
||||
|
||||
export interface YConfig {
|
||||
forAccessor: string;
|
||||
axisMode?: YAxisMode;
|
||||
color?: string;
|
||||
icon?: string;
|
||||
lineWidth?: number;
|
||||
lineStyle?: LineStyle;
|
||||
fill?: FillStyle;
|
||||
iconPosition?: IconPosition;
|
||||
textVisibility?: boolean;
|
||||
}
|
||||
|
||||
export interface ValidLayer extends DataLayerConfigResult {
|
||||
xAccessor: NonNullable<DataLayerConfigResult['xAccessor']>;
|
||||
}
|
||||
|
||||
export interface DataLayerArgs {
|
||||
layerId: string;
|
||||
accessors: string[];
|
||||
seriesType: SeriesType;
|
||||
xAccessor?: string;
|
||||
hide?: boolean;
|
||||
splitAccessor?: string;
|
||||
columnToLabel?: string; // Actually a JSON key-value pair
|
||||
yScaleType: YScaleType;
|
||||
xScaleType: XScaleType;
|
||||
isHistogram: boolean;
|
||||
// palette will always be set on the expression
|
||||
palette: PaletteOutput;
|
||||
yConfig?: YConfigResult[];
|
||||
}
|
||||
|
||||
export interface LegendConfig {
|
||||
/**
|
||||
* Flag whether the legend should be shown. If there is just a single series, it will be hidden
|
||||
*/
|
||||
isVisible: boolean;
|
||||
/**
|
||||
* Position of the legend relative to the chart
|
||||
*/
|
||||
position: Position;
|
||||
/**
|
||||
* Flag whether the legend should be shown even with just a single series
|
||||
*/
|
||||
showSingleSeries?: boolean;
|
||||
/**
|
||||
* Flag whether the legend is inside the chart
|
||||
*/
|
||||
isInside?: boolean;
|
||||
/**
|
||||
* Horizontal Alignment of the legend when it is set inside chart
|
||||
*/
|
||||
horizontalAlignment?: HorizontalAlignment;
|
||||
/**
|
||||
* Vertical Alignment of the legend when it is set inside chart
|
||||
*/
|
||||
verticalAlignment?: VerticalAlignment;
|
||||
/**
|
||||
* Number of columns when legend is set inside chart
|
||||
*/
|
||||
floatingColumns?: number;
|
||||
/**
|
||||
* Maximum number of lines per legend item
|
||||
*/
|
||||
maxLines?: number;
|
||||
|
||||
/**
|
||||
* Flag whether the legend items are truncated or not
|
||||
*/
|
||||
shouldTruncate?: boolean;
|
||||
|
||||
/**
|
||||
* Exact legend width (vertical) or height (horizontal)
|
||||
* Limited to max of 70% of the chart container dimension Vertical legends limited to min of 30% of computed width
|
||||
*/
|
||||
legendSize?: number;
|
||||
}
|
||||
|
||||
export interface LabelsOrientationConfig {
|
||||
x: number;
|
||||
yLeft: number;
|
||||
yRight: number;
|
||||
}
|
||||
|
||||
// Arguments to XY chart expression, with computed properties
|
||||
export interface XYArgs {
|
||||
title?: string;
|
||||
description?: string;
|
||||
xTitle: string;
|
||||
yTitle: string;
|
||||
yRightTitle: string;
|
||||
yLeftExtent: AxisExtentConfigResult;
|
||||
yRightExtent: AxisExtentConfigResult;
|
||||
legend: LegendConfigResult;
|
||||
valueLabels: ValueLabelMode;
|
||||
layers: XYLayerConfigResult[];
|
||||
endValue?: EndValue;
|
||||
emphasizeFitting?: boolean;
|
||||
fittingFunction?: FittingFunction;
|
||||
axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult;
|
||||
tickLabelsVisibilitySettings?: TickLabelsConfigResult;
|
||||
gridlinesVisibilitySettings?: GridlinesConfigResult;
|
||||
labelsOrientation?: LabelsOrientationConfigResult;
|
||||
curveType?: XYCurveType;
|
||||
fillOpacity?: number;
|
||||
hideEndzones?: boolean;
|
||||
valuesInLegend?: boolean;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export interface AnnotationLayerArgs {
|
||||
annotations: EventAnnotationOutput[];
|
||||
layerId: string;
|
||||
hide?: boolean;
|
||||
}
|
||||
|
||||
export type AnnotationLayerConfigResult = AnnotationLayerArgs & {
|
||||
type: typeof ANNOTATION_LAYER;
|
||||
layerType: typeof LayerTypes.ANNOTATIONS;
|
||||
};
|
||||
|
||||
export interface ReferenceLineLayerArgs {
|
||||
layerId: string;
|
||||
accessors: string[];
|
||||
columnToLabel?: string;
|
||||
yConfig?: YConfigResult[];
|
||||
}
|
||||
|
||||
export type XYLayerArgs = DataLayerArgs | ReferenceLineLayerArgs | AnnotationLayerArgs;
|
||||
|
||||
export type XYLayerConfigResult =
|
||||
| DataLayerConfigResult
|
||||
| ReferenceLineLayerConfigResult
|
||||
| AnnotationLayerConfigResult;
|
||||
|
||||
export interface LensMultiTable {
|
||||
type: typeof MULTITABLE;
|
||||
tables: Record<string, Datatable>;
|
||||
dateRange?: {
|
||||
fromDate: Date;
|
||||
toDate: Date;
|
||||
};
|
||||
}
|
||||
|
||||
export type ReferenceLineLayerConfigResult = ReferenceLineLayerArgs & {
|
||||
type: typeof REFERENCE_LINE_LAYER;
|
||||
layerType: typeof LayerTypes.REFERENCELINE;
|
||||
};
|
||||
|
||||
export type DataLayerConfigResult = DataLayerArgs & {
|
||||
type: typeof DATA_LAYER;
|
||||
layerType: typeof LayerTypes.DATA;
|
||||
};
|
||||
|
||||
export type YConfigResult = YConfig & { type: typeof Y_CONFIG };
|
||||
|
||||
export type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & {
|
||||
type: typeof AXIS_TITLES_VISIBILITY_CONFIG;
|
||||
};
|
||||
|
||||
export type LabelsOrientationConfigResult = LabelsOrientationConfig & {
|
||||
type: typeof LABELS_ORIENTATION_CONFIG;
|
||||
};
|
||||
|
||||
export type LegendConfigResult = LegendConfig & { type: typeof LEGEND_CONFIG };
|
||||
export type AxisExtentConfigResult = AxisExtentConfig & { type: typeof AXIS_EXTENT_CONFIG };
|
||||
export type GridlinesConfigResult = AxesSettingsConfig & { type: typeof GRID_LINES_CONFIG };
|
||||
export type TickLabelsConfigResult = AxesSettingsConfig & { type: typeof TICK_LABELS_CONFIG };
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { XY_VIS_RENDERER } from '../constants';
|
||||
import { LensMultiTable, XYArgs } from './expression_functions';
|
||||
|
||||
export interface XYChartProps {
|
||||
data: LensMultiTable;
|
||||
args: XYArgs;
|
||||
}
|
||||
|
||||
export interface XYRender {
|
||||
type: 'render';
|
||||
as: typeof XY_VIS_RENDERER;
|
||||
value: XYChartProps;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './expression_functions';
|
||||
export * from './expression_renderers';
|
19
src/plugins/chart_expressions/expression_xy/jest.config.js
Normal file
19
src/plugins/chart_expressions/expression_xy/jest.config.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../',
|
||||
roots: ['<rootDir>/src/plugins/chart_expressions/expression_xy'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/src/plugins/chart_expressions/expression_xy',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/src/plugins/chart_expressions/expression_xy/{common,public,server}/**/*.{ts,tsx}',
|
||||
],
|
||||
};
|
15
src/plugins/chart_expressions/expression_xy/kibana.json
Executable file
15
src/plugins/chart_expressions/expression_xy/kibana.json
Executable file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "expressionXY",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
"githubTeam": "kibana-vis-editors"
|
||||
},
|
||||
"description": "Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart.",
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["expressions", "charts", "data", "fieldFormats", "uiActions", "eventAnnotation", "visualizations"],
|
||||
"requiredBundles": ["kibanaReact"],
|
||||
"optionalPlugins": []
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* 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 { chartPluginMock } from '../../../../../plugins/charts/public/mocks';
|
||||
import { DataLayerConfigResult, LensMultiTable, XYArgs } from '../../common';
|
||||
import { LayerTypes } from '../../common/constants';
|
||||
import { mockPaletteOutput, sampleArgs } from '../../common/__mocks__';
|
||||
|
||||
const chartSetupContract = chartPluginMock.createSetupContract();
|
||||
const chartStartContract = chartPluginMock.createStartContract();
|
||||
|
||||
export const chartsThemeService = chartSetupContract.theme;
|
||||
export const chartsActiveCursorService = chartStartContract.activeCursor;
|
||||
|
||||
export const paletteService = chartPluginMock.createPaletteRegistry();
|
||||
|
||||
export const dateHistogramData: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
timeLayer: {
|
||||
type: 'datatable',
|
||||
rows: [
|
||||
{
|
||||
xAccessorId: 1585758120000,
|
||||
splitAccessorId: "Men's Clothing",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585758360000,
|
||||
splitAccessorId: "Women's Accessories",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585758360000,
|
||||
splitAccessorId: "Women's Clothing",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585759380000,
|
||||
splitAccessorId: "Men's Clothing",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585759380000,
|
||||
splitAccessorId: "Men's Shoes",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585759380000,
|
||||
splitAccessorId: "Women's Clothing",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585760700000,
|
||||
splitAccessorId: "Men's Clothing",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585760760000,
|
||||
splitAccessorId: "Men's Clothing",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585760760000,
|
||||
splitAccessorId: "Men's Shoes",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585761120000,
|
||||
splitAccessorId: "Men's Shoes",
|
||||
yAccessorId: 1,
|
||||
},
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
id: 'xAccessorId',
|
||||
name: 'order_date per minute',
|
||||
meta: {
|
||||
type: 'date',
|
||||
field: 'order_date',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
type: 'date_histogram',
|
||||
appliedTimeRange: {
|
||||
from: '2020-04-01T16:14:16.246Z',
|
||||
to: '2020-04-01T17:15:41.263Z',
|
||||
},
|
||||
params: {
|
||||
field: 'order_date',
|
||||
timeRange: { from: '2020-04-01T16:14:16.246Z', to: '2020-04-01T17:15:41.263Z' },
|
||||
useNormalizedEsInterval: true,
|
||||
scaleMetricValues: false,
|
||||
interval: '1m',
|
||||
drop_partials: false,
|
||||
min_doc_count: 0,
|
||||
extended_bounds: {},
|
||||
},
|
||||
},
|
||||
params: { id: 'date', params: { pattern: 'HH:mm' } },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'splitAccessorId',
|
||||
name: 'Top values of category.keyword',
|
||||
meta: {
|
||||
type: 'string',
|
||||
field: 'category.keyword',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
type: 'terms',
|
||||
params: {
|
||||
field: 'category.keyword',
|
||||
orderBy: 'yAccessorId',
|
||||
order: 'desc',
|
||||
size: 3,
|
||||
otherBucket: false,
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucket: false,
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
params: {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'string',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
parsedUrl: {
|
||||
origin: 'http://localhost:5601',
|
||||
pathname: '/jiy/app/kibana',
|
||||
basePath: '/jiy',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'yAccessorId',
|
||||
name: 'Count of records',
|
||||
meta: {
|
||||
type: 'number',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
params: {},
|
||||
},
|
||||
params: { id: 'number' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
dateRange: {
|
||||
fromDate: new Date('2020-04-01T16:14:16.246Z'),
|
||||
toDate: new Date('2020-04-01T17:15:41.263Z'),
|
||||
},
|
||||
};
|
||||
|
||||
export const dateHistogramLayer: DataLayerConfigResult = {
|
||||
type: 'dataLayer',
|
||||
layerId: 'timeLayer',
|
||||
layerType: LayerTypes.DATA,
|
||||
hide: false,
|
||||
xAccessor: 'xAccessorId',
|
||||
yScaleType: 'linear',
|
||||
xScaleType: 'time',
|
||||
isHistogram: true,
|
||||
splitAccessor: 'splitAccessorId',
|
||||
seriesType: 'bar_stacked',
|
||||
accessors: ['yAccessorId'],
|
||||
palette: mockPaletteOutput,
|
||||
};
|
||||
|
||||
export function sampleArgsWithReferenceLine(value: number = 150) {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
return {
|
||||
data: {
|
||||
...data,
|
||||
tables: {
|
||||
...data.tables,
|
||||
referenceLine: {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: 'referenceLine-a',
|
||||
meta: { params: { id: 'number' }, type: 'number' },
|
||||
name: 'Static value',
|
||||
},
|
||||
],
|
||||
rows: [{ 'referenceLine-a': value }],
|
||||
},
|
||||
},
|
||||
} as LensMultiTable,
|
||||
args: {
|
||||
...args,
|
||||
layers: [
|
||||
...args.layers,
|
||||
{
|
||||
layerType: LayerTypes.REFERENCELINE,
|
||||
accessors: ['referenceLine-a'],
|
||||
layerId: 'referenceLine',
|
||||
seriesType: 'line',
|
||||
xScaleType: 'linear',
|
||||
yScaleType: 'linear',
|
||||
palette: mockPaletteOutput,
|
||||
isHistogram: false,
|
||||
hide: true,
|
||||
yConfig: [{ axisMode: 'left', forAccessor: 'referenceLine-a', type: 'yConfig' }],
|
||||
},
|
||||
],
|
||||
} as XYArgs,
|
||||
};
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`xy_expression XYChart component annotations should render basic annotation 1`] = `
|
||||
exports[`XYChart component annotations should render basic annotation 1`] = `
|
||||
<LineAnnotation
|
||||
dataValues={
|
||||
Array [
|
||||
|
@ -49,7 +49,7 @@ exports[`xy_expression XYChart component annotations should render basic annotat
|
|||
/>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component annotations should render grouped annotations preserving the shared styles 1`] = `
|
||||
exports[`XYChart component annotations should render grouped annotations preserving the shared styles 1`] = `
|
||||
<LineAnnotation
|
||||
customTooltipDetails={[Function]}
|
||||
dataValues={
|
||||
|
@ -108,7 +108,7 @@ exports[`xy_expression XYChart component annotations should render grouped annot
|
|||
/>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component annotations should render grouped annotations with default styles 1`] = `
|
||||
exports[`XYChart component annotations should render grouped annotations with default styles 1`] = `
|
||||
<LineAnnotation
|
||||
customTooltipDetails={[Function]}
|
||||
dataValues={
|
||||
|
@ -164,7 +164,7 @@ exports[`xy_expression XYChart component annotations should render grouped annot
|
|||
/>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component annotations should render simplified annotation when hide is true 1`] = `
|
||||
exports[`XYChart component annotations should render simplified annotation when hide is true 1`] = `
|
||||
<LineAnnotation
|
||||
dataValues={
|
||||
Array [
|
||||
|
@ -213,7 +213,7 @@ exports[`xy_expression XYChart component annotations should render simplified an
|
|||
/>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component it renders area 1`] = `
|
||||
exports[`XYChart component it renders area 1`] = `
|
||||
<Chart
|
||||
renderer="canvas"
|
||||
>
|
||||
|
@ -447,7 +447,7 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
</Chart>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component it renders bar 1`] = `
|
||||
exports[`XYChart component it renders bar 1`] = `
|
||||
<Chart
|
||||
renderer="canvas"
|
||||
>
|
||||
|
@ -693,7 +693,7 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
|||
</Chart>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
||||
exports[`XYChart component it renders horizontal bar 1`] = `
|
||||
<Chart
|
||||
renderer="canvas"
|
||||
>
|
||||
|
@ -939,7 +939,7 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
|||
</Chart>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component it renders line 1`] = `
|
||||
exports[`XYChart component it renders line 1`] = `
|
||||
<Chart
|
||||
renderer="canvas"
|
||||
>
|
||||
|
@ -1173,7 +1173,7 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
</Chart>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component it renders stacked area 1`] = `
|
||||
exports[`XYChart component it renders stacked area 1`] = `
|
||||
<Chart
|
||||
renderer="canvas"
|
||||
>
|
||||
|
@ -1415,7 +1415,7 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
|
|||
</Chart>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
||||
exports[`XYChart component it renders stacked bar 1`] = `
|
||||
<Chart
|
||||
renderer="canvas"
|
||||
>
|
||||
|
@ -1669,7 +1669,7 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
|||
</Chart>
|
||||
`;
|
||||
|
||||
exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] = `
|
||||
exports[`XYChart component it renders stacked horizontal bar 1`] = `
|
||||
<Chart
|
||||
renderer="canvas"
|
||||
>
|
|
@ -0,0 +1,18 @@
|
|||
.xyAnnotationNumberIcon {
|
||||
border-radius: $euiSize;
|
||||
min-width: $euiSize;
|
||||
height: $euiSize;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
.xyAnnotationNumberIcon__text {
|
||||
font-weight: 500;
|
||||
font-size: 9px;
|
||||
letter-spacing: -.5px;
|
||||
line-height: 11px;
|
||||
}
|
||||
|
||||
.xyAnnotationIcon_rotate90 {
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center;
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 './expression.scss';
|
||||
import './annotations.scss';
|
||||
import './reference_lines.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { snakeCase } from 'lodash';
|
||||
import {
|
||||
|
@ -14,20 +17,20 @@ import {
|
|||
LineAnnotation,
|
||||
Position,
|
||||
} from '@elastic/charts';
|
||||
import type { FieldFormat } from 'src/plugins/field_formats/common';
|
||||
import type { EventAnnotationArgs } from 'src/plugins/event_annotation/common';
|
||||
import moment from 'moment';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiIconProps, EuiText } from '@elastic/eui';
|
||||
import classnames from 'classnames';
|
||||
import type { EventAnnotationArgs } from '../../../../event_annotation/common';
|
||||
import type { FieldFormat } from '../../../../field_formats/common';
|
||||
import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public';
|
||||
import type { AnnotationLayerArgs } from '../../../common/expressions';
|
||||
import { hasIcon } from '../xy_config_panel/shared/icon_select';
|
||||
import {
|
||||
mapVerticalToHorizontalPlacement,
|
||||
LINES_MARKER_SIZE,
|
||||
MarkerBody,
|
||||
Marker,
|
||||
AnnotationIcon,
|
||||
} from '../annotations_helpers';
|
||||
import type {
|
||||
AnnotationLayerArgs,
|
||||
AnnotationLayerConfigResult,
|
||||
IconPosition,
|
||||
YAxisMode,
|
||||
} from '../../common/types';
|
||||
import { annotationsIconSet, hasIcon, isNumericalString } from '../helpers';
|
||||
import { mapVerticalToHorizontalPlacement, LINES_MARKER_SIZE } from '../helpers';
|
||||
|
||||
const getRoundedTimestamp = (timestamp: number, firstTimestamp?: number, minInterval?: number) => {
|
||||
if (!firstTimestamp || !minInterval) {
|
||||
|
@ -124,7 +127,7 @@ const getCommonStyles = (configArr: EventAnnotationArgs[]) => {
|
|||
};
|
||||
|
||||
export const getAnnotationsGroupedByInterval = (
|
||||
layers: AnnotationLayerArgs[],
|
||||
layers: AnnotationLayerConfigResult[],
|
||||
minInterval?: number,
|
||||
firstTimestamp?: number,
|
||||
formatter?: FieldFormat
|
||||
|
@ -187,7 +190,7 @@ export const Annotations = ({
|
|||
isHorizontal: !isHorizontal,
|
||||
hasReducedPadding,
|
||||
label: annotation.label,
|
||||
rotateClassName: isHorizontal ? 'lnsXyAnnotationIcon_rotate90' : undefined,
|
||||
rotateClassName: isHorizontal ? 'xyAnnotationIcon_rotate90' : undefined,
|
||||
}}
|
||||
/>
|
||||
) : undefined
|
||||
|
@ -232,3 +235,123 @@ export const Annotations = ({
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export function MarkerBody({
|
||||
label,
|
||||
isHorizontal,
|
||||
}: {
|
||||
label: string | undefined;
|
||||
isHorizontal: boolean;
|
||||
}) {
|
||||
if (!label) {
|
||||
return null;
|
||||
}
|
||||
if (isHorizontal) {
|
||||
return (
|
||||
<div className="eui-textTruncate" style={{ maxWidth: LINES_MARKER_SIZE * 3 }}>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="xyDecorationRotatedWrapper"
|
||||
style={{
|
||||
width: LINES_MARKER_SIZE,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="eui-textTruncate xyDecorationRotatedWrapper__label"
|
||||
style={{
|
||||
maxWidth: LINES_MARKER_SIZE * 3,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NumberIcon({ number }: { number: number }) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
className="xyAnnotationNumberIcon"
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiText color="ghost" className="xyAnnotationNumberIcon__text">
|
||||
{number < 10 ? number : `9+`}
|
||||
</EuiText>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export const AnnotationIcon = ({
|
||||
type,
|
||||
rotateClassName = '',
|
||||
isHorizontal,
|
||||
renderedInChart,
|
||||
...rest
|
||||
}: {
|
||||
type: string;
|
||||
rotateClassName?: string;
|
||||
isHorizontal?: boolean;
|
||||
renderedInChart?: boolean;
|
||||
} & EuiIconProps) => {
|
||||
if (isNumericalString(type)) {
|
||||
return <NumberIcon number={Number(type)} />;
|
||||
}
|
||||
const iconConfig = annotationsIconSet.find((i) => i.value === type);
|
||||
if (!iconConfig) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<EuiIcon
|
||||
{...rest}
|
||||
type={iconConfig.icon || type}
|
||||
className={classnames(
|
||||
{ [rotateClassName]: iconConfig.shouldRotate },
|
||||
{
|
||||
lensAnnotationIconFill: renderedInChart && iconConfig.canFill,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface MarkerConfig {
|
||||
axisMode?: YAxisMode;
|
||||
icon?: string;
|
||||
textVisibility?: boolean;
|
||||
iconPosition?: IconPosition;
|
||||
}
|
||||
|
||||
export function Marker({
|
||||
config,
|
||||
isHorizontal,
|
||||
hasReducedPadding,
|
||||
label,
|
||||
rotateClassName,
|
||||
}: {
|
||||
config: MarkerConfig;
|
||||
isHorizontal: boolean;
|
||||
hasReducedPadding: boolean;
|
||||
label?: string;
|
||||
rotateClassName?: string;
|
||||
}) {
|
||||
if (hasIcon(config.icon)) {
|
||||
return (
|
||||
<AnnotationIcon type={config.icon} rotateClassName={rotateClassName} renderedInChart={true} />
|
||||
);
|
||||
}
|
||||
|
||||
// if there's some text, check whether to show it as marker, or just show some padding for the icon
|
||||
if (config.textVisibility) {
|
||||
if (hasReducedPadding) {
|
||||
return <MarkerBody label={label} isHorizontal={isHorizontal} />;
|
||||
}
|
||||
return <EuiIcon type="empty" />;
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './legend_action_popover';
|
||||
export * from './reference_lines';
|
||||
export * from './legend_action';
|
||||
export * from './x_domain';
|
||||
export * from './xy_chart';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 React from 'react';
|
||||
|
@ -11,14 +12,15 @@ import { EuiPopover } from '@elastic/eui';
|
|||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { ComponentType, ReactWrapper } from 'enzyme';
|
||||
import type { LensMultiTable } from '../../common';
|
||||
import { layerTypes } from '../../common';
|
||||
import type { DataLayerArgs } from '../../common/expressions';
|
||||
import { getLegendAction } from './get_legend_action';
|
||||
import { LegendActionPopover } from '../shared_components';
|
||||
import { LayerTypes } from '../../common/constants';
|
||||
import type { DataLayerArgs } from '../../common';
|
||||
import { getLegendAction } from './legend_action';
|
||||
import { LegendActionPopover } from './legend_action_popover';
|
||||
import { mockPaletteOutput } from '../../common/__mocks__';
|
||||
|
||||
const sampleLayer = {
|
||||
layerId: 'first',
|
||||
layerType: layerTypes.DATA,
|
||||
layerType: LayerTypes.DATA,
|
||||
seriesType: 'line',
|
||||
xAccessor: 'c',
|
||||
accessors: ['a', 'b'],
|
||||
|
@ -27,6 +29,7 @@ const sampleLayer = {
|
|||
xScaleType: 'ordinal',
|
||||
yScaleType: 'linear',
|
||||
isHistogram: false,
|
||||
palette: mockPaletteOutput,
|
||||
} as DataLayerArgs;
|
||||
|
||||
const tables = {
|
|
@ -1,21 +1,22 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 React from 'react';
|
||||
import type { LegendAction, XYChartSeriesIdentifier } from '@elastic/charts';
|
||||
import type { LensFilterEvent } from '../types';
|
||||
import type { LensMultiTable, FormatFactory } from '../../common';
|
||||
import type { DataLayerArgs } from '../../common/expressions';
|
||||
import { LegendActionPopover } from '../shared_components';
|
||||
import type { FilterEvent } from '../types';
|
||||
import type { LensMultiTable, DataLayerArgs } from '../../common';
|
||||
import type { FormatFactory } from '../types';
|
||||
import { LegendActionPopover } from './legend_action_popover';
|
||||
|
||||
export const getLegendAction = (
|
||||
filteredLayers: DataLayerArgs[],
|
||||
tables: LensMultiTable['tables'],
|
||||
onFilter: (data: LensFilterEvent['data']) => void,
|
||||
onFilter: (data: FilterEvent['data']) => void,
|
||||
formatFactory: FormatFactory,
|
||||
layersAlreadyFormatted: Record<string, boolean>
|
||||
): LegendAction =>
|
||||
|
@ -55,7 +56,7 @@ export const getLegendAction = (
|
|||
},
|
||||
];
|
||||
|
||||
const context: LensFilterEvent['data'] = {
|
||||
const context: FilterEvent['data'] = {
|
||||
data,
|
||||
};
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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 React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiContextMenuPanelDescriptor, EuiIcon, EuiPopover, EuiContextMenu } from '@elastic/eui';
|
||||
import { useLegendAction } from '@elastic/charts';
|
||||
import type { FilterEvent } from '../types';
|
||||
|
||||
export interface LegendActionPopoverProps {
|
||||
/**
|
||||
* Determines the panels label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Callback on filter value
|
||||
*/
|
||||
onFilter: (data: FilterEvent['data']) => void;
|
||||
/**
|
||||
* Determines the filter event data
|
||||
*/
|
||||
context: FilterEvent['data'];
|
||||
}
|
||||
|
||||
export const LegendActionPopover: React.FunctionComponent<LegendActionPopoverProps> = ({
|
||||
label,
|
||||
onFilter,
|
||||
context,
|
||||
}) => {
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const [ref, onClose] = useLegendAction<HTMLDivElement>();
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 'main',
|
||||
title: label,
|
||||
items: [
|
||||
{
|
||||
name: i18n.translate('expressionXY.legend.filterForValueButtonAriaLabel', {
|
||||
defaultMessage: 'Filter for value',
|
||||
}),
|
||||
'data-test-subj': `legend-${label}-filterIn`,
|
||||
icon: <EuiIcon type="plusInCircle" size="m" />,
|
||||
onClick: () => {
|
||||
setPopoverOpen(false);
|
||||
onFilter(context);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: i18n.translate('expressionXY.legend.filterOutValueButtonAriaLabel', {
|
||||
defaultMessage: 'Filter out value',
|
||||
}),
|
||||
'data-test-subj': `legend-${label}-filterOut`,
|
||||
icon: <EuiIcon type="minusInCircle" size="m" />,
|
||||
onClick: () => {
|
||||
setPopoverOpen(false);
|
||||
onFilter({ ...context, negate: true });
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const Button = (
|
||||
<div
|
||||
tabIndex={0}
|
||||
ref={ref}
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
marginLeft: 4,
|
||||
marginRight: 4,
|
||||
}}
|
||||
data-test-subj={`legend-${label}`}
|
||||
onKeyPress={() => setPopoverOpen(!popoverOpen)}
|
||||
onClick={() => setPopoverOpen(!popoverOpen)}
|
||||
>
|
||||
<EuiIcon size="s" type="boxesVertical" />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<EuiPopover
|
||||
id="contextMenuNormal"
|
||||
button={Button}
|
||||
isOpen={popoverOpen}
|
||||
closePopover={() => {
|
||||
setPopoverOpen(false);
|
||||
onClose();
|
||||
}}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="upLeft"
|
||||
title={i18n.translate('expressionXY.legend.filterOptionsLegend', {
|
||||
defaultMessage: '{legendDataLabel}, filter options',
|
||||
values: { legendDataLabel: label },
|
||||
})}
|
||||
>
|
||||
<EuiContextMenu initialPanelId="main" panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -1,9 +1,9 @@
|
|||
.lnsXyDecorationRotatedWrapper {
|
||||
.xyDecorationRotatedWrapper {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
|
||||
.lnsXyDecorationRotatedWrapper__label {
|
||||
.xyDecorationRotatedWrapper__label {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
transform: translate(0, 100%) rotate(-90deg);
|
|
@ -1,23 +1,18 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { LineAnnotation, RectAnnotation } from '@elastic/charts';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { chartPluginMock } from 'src/plugins/charts/public/mocks';
|
||||
import { FieldFormat } from 'src/plugins/field_formats/common';
|
||||
import { FieldFormat } from '../../../../field_formats/common';
|
||||
import { LensMultiTable } from '../../common';
|
||||
import { ReferenceLineLayerArgs, YConfig } from '../../common/expressions';
|
||||
import {
|
||||
ReferenceLineAnnotations,
|
||||
ReferenceLineAnnotationsProps,
|
||||
} from './expression_reference_lines';
|
||||
|
||||
const paletteService = chartPluginMock.createPaletteRegistry();
|
||||
import { ReferenceLineLayerArgs, YConfig } from '../../common/types';
|
||||
import { ReferenceLineAnnotations, ReferenceLineAnnotationsProps } from './reference_lines';
|
||||
|
||||
const row: Record<string, number> = {
|
||||
xAccessorFirstId: 1,
|
||||
|
@ -54,7 +49,6 @@ function createLayers(yConfigs: ReferenceLineLayerArgs['yConfig']): ReferenceLin
|
|||
return [
|
||||
{
|
||||
layerId: 'firstLayer',
|
||||
layerType: 'referenceLine',
|
||||
accessors: (yConfigs || []).map(({ forAccessor }) => forAccessor),
|
||||
yConfig: yConfigs,
|
||||
},
|
||||
|
@ -90,8 +84,6 @@ describe('ReferenceLineAnnotations', () => {
|
|||
|
||||
defaultProps = {
|
||||
formatters,
|
||||
paletteService,
|
||||
syncColors: false,
|
||||
isHorizontal: false,
|
||||
axesMap: { left: true, right: false },
|
||||
paddingMap: {},
|
||||
|
@ -117,6 +109,7 @@ describe('ReferenceLineAnnotations', () => {
|
|||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'yConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
|
@ -154,6 +147,7 @@ describe('ReferenceLineAnnotations', () => {
|
|||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'yConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
|
@ -195,12 +189,14 @@ describe('ReferenceLineAnnotations', () => {
|
|||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
type: 'yConfig',
|
||||
fill,
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
type: 'yConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
|
@ -243,12 +239,14 @@ describe('ReferenceLineAnnotations', () => {
|
|||
forAccessor: `${layerPrefix}FirstId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'yConfig',
|
||||
fill,
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode: 'bottom',
|
||||
lineStyle: 'solid',
|
||||
type: 'yConfig',
|
||||
fill,
|
||||
},
|
||||
])}
|
||||
|
@ -291,12 +289,14 @@ describe('ReferenceLineAnnotations', () => {
|
|||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill: 'above',
|
||||
type: 'yConfig',
|
||||
},
|
||||
{
|
||||
forAccessor: `${layerPrefix}SecondId`,
|
||||
axisMode,
|
||||
lineStyle: 'solid',
|
||||
fill: 'below',
|
||||
type: 'yConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
||||
|
@ -339,12 +339,14 @@ describe('ReferenceLineAnnotations', () => {
|
|||
axisMode: 'left',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'yConfig',
|
||||
},
|
||||
{
|
||||
forAccessor: `yAccessorRightSecondId`,
|
||||
axisMode: 'right',
|
||||
lineStyle: 'solid',
|
||||
fill,
|
||||
type: 'yConfig',
|
||||
},
|
||||
])}
|
||||
/>
|
|
@ -0,0 +1,366 @@
|
|||
/*
|
||||
* 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 './reference_lines.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { groupBy } from 'lodash';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { RectAnnotation, AnnotationDomainType, LineAnnotation, Position } from '@elastic/charts';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import type { FieldFormat } from '../../../../field_formats/common';
|
||||
import type { IconPosition, ReferenceLineLayerArgs, YAxisMode } from '../../common/types';
|
||||
import type { LensMultiTable } from '../../common/types';
|
||||
import { hasIcon } from '../helpers';
|
||||
|
||||
export const REFERENCE_LINE_MARKER_SIZE = 20;
|
||||
|
||||
export const computeChartMargins = (
|
||||
referenceLinePaddings: Partial<Record<Position, number>>,
|
||||
labelVisibility: Partial<Record<'x' | 'yLeft' | 'yRight', boolean>>,
|
||||
titleVisibility: Partial<Record<'x' | 'yLeft' | 'yRight', boolean>>,
|
||||
axesMap: Record<'left' | 'right', unknown>,
|
||||
isHorizontal: boolean
|
||||
) => {
|
||||
const result: Partial<Record<Position, number>> = {};
|
||||
if (!labelVisibility?.x && !titleVisibility?.x && referenceLinePaddings.bottom) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('bottom') : 'bottom';
|
||||
result[placement] = referenceLinePaddings.bottom;
|
||||
}
|
||||
if (
|
||||
referenceLinePaddings.left &&
|
||||
(isHorizontal || (!labelVisibility?.yLeft && !titleVisibility?.yLeft))
|
||||
) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('left') : 'left';
|
||||
result[placement] = referenceLinePaddings.left;
|
||||
}
|
||||
if (
|
||||
referenceLinePaddings.right &&
|
||||
(isHorizontal || !axesMap.right || (!labelVisibility?.yRight && !titleVisibility?.yRight))
|
||||
) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('right') : 'right';
|
||||
result[placement] = referenceLinePaddings.right;
|
||||
}
|
||||
// there's no top axis, so just check if a margin has been computed
|
||||
if (referenceLinePaddings.top) {
|
||||
const placement = isHorizontal ? mapVerticalToHorizontalPlacement('top') : 'top';
|
||||
result[placement] = referenceLinePaddings.top;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Note: it does not take into consideration whether the reference line is in view or not
|
||||
export const getReferenceLineRequiredPaddings = (
|
||||
referenceLineLayers: ReferenceLineLayerArgs[],
|
||||
axesMap: Record<'left' | 'right', unknown>
|
||||
) => {
|
||||
// collect all paddings for the 4 axis: if any text is detected double it.
|
||||
const paddings: Partial<Record<Position, number>> = {};
|
||||
const icons: Partial<Record<Position, number>> = {};
|
||||
referenceLineLayers.forEach((layer) => {
|
||||
layer.yConfig?.forEach(({ axisMode, icon, iconPosition, textVisibility }) => {
|
||||
if (axisMode && (hasIcon(icon) || textVisibility)) {
|
||||
const placement = getBaseIconPlacement(iconPosition, axisMode, axesMap);
|
||||
paddings[placement] = Math.max(
|
||||
paddings[placement] || 0,
|
||||
REFERENCE_LINE_MARKER_SIZE * (textVisibility ? 2 : 1) // double the padding size if there's text
|
||||
);
|
||||
icons[placement] = (icons[placement] || 0) + (hasIcon(icon) ? 1 : 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
// post-process the padding based on the icon presence:
|
||||
// if no icon is present for the placement, just reduce the padding
|
||||
(Object.keys(paddings) as Position[]).forEach((placement) => {
|
||||
if (!icons[placement]) {
|
||||
paddings[placement] = REFERENCE_LINE_MARKER_SIZE;
|
||||
}
|
||||
});
|
||||
|
||||
return paddings;
|
||||
};
|
||||
|
||||
function mapVerticalToHorizontalPlacement(placement: Position) {
|
||||
switch (placement) {
|
||||
case Position.Top:
|
||||
return Position.Right;
|
||||
case Position.Bottom:
|
||||
return Position.Left;
|
||||
case Position.Left:
|
||||
return Position.Bottom;
|
||||
case Position.Right:
|
||||
return Position.Top;
|
||||
}
|
||||
}
|
||||
|
||||
// if there's just one axis, put it on the other one
|
||||
// otherwise use the same axis
|
||||
// this function assume the chart is vertical
|
||||
function getBaseIconPlacement(
|
||||
iconPosition: IconPosition | undefined,
|
||||
axisMode: YAxisMode | undefined,
|
||||
axesMap: Record<string, unknown>
|
||||
) {
|
||||
if (iconPosition === 'auto') {
|
||||
if (axisMode === 'bottom') {
|
||||
return Position.Top;
|
||||
}
|
||||
if (axisMode === 'left') {
|
||||
return axesMap.right ? Position.Left : Position.Right;
|
||||
}
|
||||
return axesMap.left ? Position.Right : Position.Left;
|
||||
}
|
||||
|
||||
if (iconPosition === 'left') {
|
||||
return Position.Left;
|
||||
}
|
||||
if (iconPosition === 'right') {
|
||||
return Position.Right;
|
||||
}
|
||||
if (iconPosition === 'below') {
|
||||
return Position.Bottom;
|
||||
}
|
||||
return Position.Top;
|
||||
}
|
||||
|
||||
function getMarkerBody(label: string | undefined, isHorizontal: boolean) {
|
||||
if (!label) {
|
||||
return;
|
||||
}
|
||||
if (isHorizontal) {
|
||||
return (
|
||||
<div className="eui-textTruncate" style={{ maxWidth: REFERENCE_LINE_MARKER_SIZE * 3 }}>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="xyDecorationRotatedWrapper"
|
||||
style={{
|
||||
width: REFERENCE_LINE_MARKER_SIZE,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="eui-textTruncate xyDecorationRotatedWrapper__label"
|
||||
style={{
|
||||
maxWidth: REFERENCE_LINE_MARKER_SIZE * 3,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface MarkerConfig {
|
||||
axisMode?: YAxisMode;
|
||||
icon?: string;
|
||||
textVisibility?: boolean;
|
||||
}
|
||||
|
||||
function getMarkerToShow(
|
||||
markerConfig: MarkerConfig,
|
||||
label: string | undefined,
|
||||
isHorizontal: boolean,
|
||||
hasReducedPadding: boolean
|
||||
) {
|
||||
// show an icon if present
|
||||
if (hasIcon(markerConfig.icon)) {
|
||||
return <EuiIcon type={markerConfig.icon} />;
|
||||
}
|
||||
// if there's some text, check whether to show it as marker, or just show some padding for the icon
|
||||
if (markerConfig.textVisibility) {
|
||||
if (hasReducedPadding) {
|
||||
return getMarkerBody(
|
||||
label,
|
||||
(!isHorizontal && markerConfig.axisMode === 'bottom') ||
|
||||
(isHorizontal && markerConfig.axisMode !== 'bottom')
|
||||
);
|
||||
}
|
||||
return <EuiIcon type="empty" />;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReferenceLineAnnotationsProps {
|
||||
layers: ReferenceLineLayerArgs[];
|
||||
data: LensMultiTable;
|
||||
formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>;
|
||||
axesMap: Record<'left' | 'right', boolean>;
|
||||
isHorizontal: boolean;
|
||||
paddingMap: Partial<Record<Position, number>>;
|
||||
}
|
||||
|
||||
export const ReferenceLineAnnotations = ({
|
||||
layers,
|
||||
data,
|
||||
formatters,
|
||||
axesMap,
|
||||
isHorizontal,
|
||||
paddingMap,
|
||||
}: ReferenceLineAnnotationsProps) => {
|
||||
return (
|
||||
<>
|
||||
{layers.flatMap((layer) => {
|
||||
if (!layer.yConfig) {
|
||||
return [];
|
||||
}
|
||||
const { columnToLabel, yConfig: yConfigs, layerId } = layer;
|
||||
const columnToLabelMap: Record<string, string> = columnToLabel
|
||||
? JSON.parse(columnToLabel)
|
||||
: {};
|
||||
const table = data.tables[layerId];
|
||||
|
||||
const row = table.rows[0];
|
||||
|
||||
const yConfigByValue = yConfigs.sort(
|
||||
({ forAccessor: idA }, { forAccessor: idB }) => row[idA] - row[idB]
|
||||
);
|
||||
|
||||
const groupedByDirection = groupBy(yConfigByValue, 'fill');
|
||||
if (groupedByDirection.below) {
|
||||
groupedByDirection.below.reverse();
|
||||
}
|
||||
|
||||
return yConfigByValue.flatMap((yConfig, i) => {
|
||||
// Find the formatter for the given axis
|
||||
const groupId =
|
||||
yConfig.axisMode === 'bottom'
|
||||
? undefined
|
||||
: yConfig.axisMode === 'right'
|
||||
? 'right'
|
||||
: 'left';
|
||||
|
||||
const formatter = formatters[groupId || 'bottom'];
|
||||
|
||||
const defaultColor = euiLightVars.euiColorDarkShade;
|
||||
|
||||
// get the position for vertical chart
|
||||
const markerPositionVertical = getBaseIconPlacement(
|
||||
yConfig.iconPosition,
|
||||
yConfig.axisMode,
|
||||
axesMap
|
||||
);
|
||||
// the padding map is built for vertical chart
|
||||
const hasReducedPadding =
|
||||
paddingMap[markerPositionVertical] === REFERENCE_LINE_MARKER_SIZE;
|
||||
|
||||
const props = {
|
||||
groupId,
|
||||
marker: getMarkerToShow(
|
||||
yConfig,
|
||||
columnToLabelMap[yConfig.forAccessor],
|
||||
isHorizontal,
|
||||
hasReducedPadding
|
||||
),
|
||||
markerBody: getMarkerBody(
|
||||
yConfig.textVisibility && !hasReducedPadding
|
||||
? columnToLabelMap[yConfig.forAccessor]
|
||||
: undefined,
|
||||
(!isHorizontal && yConfig.axisMode === 'bottom') ||
|
||||
(isHorizontal && yConfig.axisMode !== 'bottom')
|
||||
),
|
||||
// rotate the position if required
|
||||
markerPosition: isHorizontal
|
||||
? mapVerticalToHorizontalPlacement(markerPositionVertical)
|
||||
: markerPositionVertical,
|
||||
};
|
||||
const annotations = [];
|
||||
|
||||
const dashStyle =
|
||||
yConfig.lineStyle === 'dashed'
|
||||
? [(yConfig.lineWidth || 1) * 3, yConfig.lineWidth || 1]
|
||||
: yConfig.lineStyle === 'dotted'
|
||||
? [yConfig.lineWidth || 1, yConfig.lineWidth || 1]
|
||||
: undefined;
|
||||
|
||||
const sharedStyle = {
|
||||
strokeWidth: yConfig.lineWidth || 1,
|
||||
stroke: yConfig.color || defaultColor,
|
||||
dash: dashStyle,
|
||||
};
|
||||
|
||||
annotations.push(
|
||||
<LineAnnotation
|
||||
{...props}
|
||||
id={`${layerId}-${yConfig.forAccessor}-line`}
|
||||
key={`${layerId}-${yConfig.forAccessor}-line`}
|
||||
dataValues={table.rows.map(() => ({
|
||||
dataValue: row[yConfig.forAccessor],
|
||||
header: columnToLabelMap[yConfig.forAccessor],
|
||||
details: formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor],
|
||||
}))}
|
||||
domainType={
|
||||
yConfig.axisMode === 'bottom'
|
||||
? AnnotationDomainType.XDomain
|
||||
: AnnotationDomainType.YDomain
|
||||
}
|
||||
style={{
|
||||
line: {
|
||||
...sharedStyle,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (yConfig.fill && yConfig.fill !== 'none') {
|
||||
const isFillAbove = yConfig.fill === 'above';
|
||||
const indexFromSameType = groupedByDirection[yConfig.fill].findIndex(
|
||||
({ forAccessor }) => forAccessor === yConfig.forAccessor
|
||||
);
|
||||
const shouldCheckNextReferenceLine =
|
||||
indexFromSameType < groupedByDirection[yConfig.fill].length - 1;
|
||||
annotations.push(
|
||||
<RectAnnotation
|
||||
{...props}
|
||||
id={`${layerId}-${yConfig.forAccessor}-rect`}
|
||||
key={`${layerId}-${yConfig.forAccessor}-rect`}
|
||||
dataValues={table.rows.map(() => {
|
||||
const nextValue = shouldCheckNextReferenceLine
|
||||
? row[groupedByDirection[yConfig.fill!][indexFromSameType + 1].forAccessor]
|
||||
: undefined;
|
||||
if (yConfig.axisMode === 'bottom') {
|
||||
return {
|
||||
coordinates: {
|
||||
x0: isFillAbove ? row[yConfig.forAccessor] : nextValue,
|
||||
y0: undefined,
|
||||
x1: isFillAbove ? nextValue : row[yConfig.forAccessor],
|
||||
y1: undefined,
|
||||
},
|
||||
header: columnToLabelMap[yConfig.forAccessor],
|
||||
details:
|
||||
formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor],
|
||||
};
|
||||
}
|
||||
return {
|
||||
coordinates: {
|
||||
x0: undefined,
|
||||
y0: isFillAbove ? row[yConfig.forAccessor] : nextValue,
|
||||
x1: undefined,
|
||||
y1: isFillAbove ? nextValue : row[yConfig.forAccessor],
|
||||
},
|
||||
header: columnToLabelMap[yConfig.forAccessor],
|
||||
details:
|
||||
formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor],
|
||||
};
|
||||
})}
|
||||
style={{
|
||||
...sharedStyle,
|
||||
fill: yConfig.color || defaultColor,
|
||||
opacity: 0.1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return annotations;
|
||||
});
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,17 +1,17 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { uniq } from 'lodash';
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { Endzones } from '../../../../../src/plugins/charts/public';
|
||||
import type { LensMultiTable } from '../../common';
|
||||
import type { DataLayerArgs } from '../../common/expressions';
|
||||
import { search } from '../../../../../src/plugins/data/public';
|
||||
import { Endzones } from '../../../../../plugins/charts/public';
|
||||
import type { LensMultiTable, DataLayerArgs } from '../../common';
|
||||
import { search } from '../../../../../plugins/data/public';
|
||||
|
||||
export interface XDomain {
|
||||
min?: number;
|
|
@ -0,0 +1,7 @@
|
|||
.xyChart__empty {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,12 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 './expression.scss';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {
|
||||
Chart,
|
||||
Settings,
|
||||
|
@ -38,57 +36,44 @@ import {
|
|||
LineSeriesProps,
|
||||
ColorVariant,
|
||||
} from '@elastic/charts';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import type {
|
||||
ExpressionRenderDefinition,
|
||||
Datatable,
|
||||
DatatableRow,
|
||||
DatatableColumn,
|
||||
} from 'src/plugins/expressions/public';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { RenderMode } from 'src/plugins/expressions';
|
||||
import { ThemeServiceStart } from 'kibana/public';
|
||||
import { FieldFormat } from 'src/plugins/field_formats/common';
|
||||
import { EventAnnotationServiceType } from '../../../../../src/plugins/event_annotation/public';
|
||||
import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public';
|
||||
import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import type { ILensInterpreterRenderHandlers, LensFilterEvent, LensBrushEvent } from '../types';
|
||||
import type { LensMultiTable, FormatFactory } from '../../common';
|
||||
import type {
|
||||
DataLayerArgs,
|
||||
SeriesType,
|
||||
XYChartProps,
|
||||
XYLayerArgs,
|
||||
} from '../../common/expressions';
|
||||
import { visualizationTypes } from './types';
|
||||
import { VisualizationContainer } from '../visualization_container';
|
||||
import { isHorizontalChart, getSeriesColor } from './state_helpers';
|
||||
import { search } from '../../../../../src/plugins/data/public';
|
||||
import type { Datatable, DatatableRow, DatatableColumn } from '../../../../expressions/public';
|
||||
import { RenderMode } from '../../../../expressions/common';
|
||||
import { FieldFormat } from '../../../../field_formats/common';
|
||||
import { EmptyPlaceholder } from '../../../../../plugins/charts/public';
|
||||
import type { FilterEvent, BrushEvent, FormatFactory } from '../types';
|
||||
import type { SeriesType, XYChartProps } from '../../common/types';
|
||||
import { isHorizontalChart, getSeriesColor, getAnnotationsLayers, getDataLayers } from '../helpers';
|
||||
import { EventAnnotationServiceType } from '../../../../event_annotation/public';
|
||||
import {
|
||||
ChartsPluginSetup,
|
||||
ChartsPluginStart,
|
||||
PaletteRegistry,
|
||||
SeriesLayer,
|
||||
useActiveCursor,
|
||||
} from '../../../../../src/plugins/charts/public';
|
||||
import { MULTILAYER_TIME_AXIS_STYLE } from '../../../../../src/plugins/charts/common';
|
||||
import { getFitOptions } from './fitting_functions';
|
||||
import { getAxesConfiguration, GroupsConfiguration, validateExtent } from './axes_configuration';
|
||||
import { getColorAssignments } from './color_assignment';
|
||||
import { getXDomain, XyEndzones } from './x_domain';
|
||||
import { getLegendAction } from './get_legend_action';
|
||||
import { ReferenceLineAnnotations } from './expression_reference_lines';
|
||||
|
||||
import { computeChartMargins, getLinesCausedPaddings } from './annotations_helpers';
|
||||
|
||||
import { Annotations, getAnnotationsGroupedByInterval } from './annotations/expression';
|
||||
import { computeOverallDataDomain } from './reference_line_helpers';
|
||||
} from '../../../../../plugins/charts/public';
|
||||
import { MULTILAYER_TIME_AXIS_STYLE } from '../../../../../plugins/charts/common';
|
||||
import {
|
||||
getFilteredLayers,
|
||||
getReferenceLayers,
|
||||
getDataLayersArgs,
|
||||
getAnnotationsLayersArgs,
|
||||
} from './visualization_helpers';
|
||||
isDataLayer,
|
||||
getFitOptions,
|
||||
getAxesConfiguration,
|
||||
GroupsConfiguration,
|
||||
validateExtent,
|
||||
computeOverallDataDomain,
|
||||
getColorAssignments,
|
||||
getLinesCausedPaddings,
|
||||
} from '../helpers';
|
||||
import { getXDomain, XyEndzones } from './x_domain';
|
||||
import { getLegendAction } from './legend_action';
|
||||
import { ReferenceLineAnnotations, computeChartMargins } from './reference_lines';
|
||||
import { visualizationDefinitions } from '../definitions';
|
||||
import { XYLayerConfigResult } from '../../common/types';
|
||||
import { Annotations, getAnnotationsGroupedByInterval } from './annotations';
|
||||
|
||||
import './xy_chart.scss';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -110,96 +95,15 @@ export type XYChartRenderProps = XYChartProps & {
|
|||
useLegacyTimeAxis: boolean;
|
||||
minInterval: number | undefined;
|
||||
interactive?: boolean;
|
||||
onClickValue: (data: LensFilterEvent['data']) => void;
|
||||
onSelectRange: (data: LensBrushEvent['data']) => void;
|
||||
onClickValue: (data: FilterEvent['data']) => void;
|
||||
onSelectRange: (data: BrushEvent['data']) => void;
|
||||
renderMode: RenderMode;
|
||||
syncColors: boolean;
|
||||
eventAnnotationService: EventAnnotationServiceType;
|
||||
};
|
||||
|
||||
export function calculateMinInterval({ args: { layers }, data }: XYChartProps) {
|
||||
const filteredLayers = getFilteredLayers(layers, data);
|
||||
if (filteredLayers.length === 0) return;
|
||||
const isTimeViz = filteredLayers.every((l) => l.xScaleType === 'time');
|
||||
const xColumn = data.tables[filteredLayers[0].layerId].columns.find(
|
||||
(column) => column.id === filteredLayers[0].xAccessor
|
||||
);
|
||||
|
||||
if (!xColumn) return;
|
||||
if (!isTimeViz) {
|
||||
const histogramInterval = search.aggs.getNumberHistogramIntervalByDatatableColumn(xColumn);
|
||||
if (typeof histogramInterval === 'number') {
|
||||
return histogramInterval;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
const dateInterval = search.aggs.getDateHistogramMetaDataByDatatableColumn(xColumn)?.interval;
|
||||
if (!dateInterval) return;
|
||||
const intervalDuration = search.aggs.parseInterval(dateInterval);
|
||||
if (!intervalDuration) return;
|
||||
return intervalDuration.as('milliseconds');
|
||||
}
|
||||
|
||||
const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object';
|
||||
|
||||
export const getXyChartRenderer = (dependencies: {
|
||||
formatFactory: FormatFactory;
|
||||
chartsThemeService: ChartsPluginStart['theme'];
|
||||
chartsActiveCursorService: ChartsPluginStart['activeCursor'];
|
||||
paletteService: PaletteRegistry;
|
||||
timeZone: string;
|
||||
useLegacyTimeAxis: boolean;
|
||||
kibanaTheme: ThemeServiceStart;
|
||||
eventAnnotationService: EventAnnotationServiceType;
|
||||
}): ExpressionRenderDefinition<XYChartProps> => ({
|
||||
name: 'lens_xy_chart_renderer',
|
||||
displayName: 'XY chart',
|
||||
help: i18n.translate('xpack.lens.xyChart.renderer.help', {
|
||||
defaultMessage: 'X/Y chart renderer',
|
||||
}),
|
||||
validate: () => undefined,
|
||||
reuseDomNode: true,
|
||||
render: async (
|
||||
domNode: Element,
|
||||
config: XYChartProps,
|
||||
handlers: ILensInterpreterRenderHandlers
|
||||
) => {
|
||||
handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode));
|
||||
const onClickValue = (data: LensFilterEvent['data']) => {
|
||||
handlers.event({ name: 'filter', data });
|
||||
};
|
||||
const onSelectRange = (data: LensBrushEvent['data']) => {
|
||||
handlers.event({ name: 'brush', data });
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme$={dependencies.kibanaTheme.theme$}>
|
||||
<I18nProvider>
|
||||
<XYChartReportable
|
||||
{...config}
|
||||
formatFactory={dependencies.formatFactory}
|
||||
chartsActiveCursorService={dependencies.chartsActiveCursorService}
|
||||
chartsThemeService={dependencies.chartsThemeService}
|
||||
paletteService={dependencies.paletteService}
|
||||
eventAnnotationService={dependencies.eventAnnotationService}
|
||||
timeZone={dependencies.timeZone}
|
||||
useLegacyTimeAxis={dependencies.useLegacyTimeAxis}
|
||||
minInterval={calculateMinInterval(config)}
|
||||
interactive={handlers.isInteractive()}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
renderMode={handlers.getRenderMode()}
|
||||
syncColors={handlers.isSyncColorsEnabled()}
|
||||
/>
|
||||
</I18nProvider>
|
||||
</KibanaThemeProvider>,
|
||||
domNode,
|
||||
() => handlers.done()
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function getValueLabelsStyling(isHorizontal: boolean): {
|
||||
displayValue: RecursivePartial<DisplayValueStyle>;
|
||||
} {
|
||||
|
@ -222,18 +126,10 @@ function getValueLabelsStyling(isHorizontal: boolean): {
|
|||
}
|
||||
|
||||
function getIconForSeriesType(seriesType: SeriesType): IconType {
|
||||
return visualizationTypes.find((c) => c.id === seriesType)!.icon || 'empty';
|
||||
return visualizationDefinitions.find((c) => c.id === seriesType)!.icon || 'empty';
|
||||
}
|
||||
|
||||
const MemoizedChart = React.memo(XYChart);
|
||||
|
||||
export function XYChartReportable(props: XYChartRenderProps) {
|
||||
return (
|
||||
<VisualizationContainer className="lnsXyExpression__container">
|
||||
<MemoizedChart {...props} />
|
||||
</VisualizationContainer>
|
||||
);
|
||||
}
|
||||
export const XYChartReportable = React.memo(XYChart);
|
||||
|
||||
export function XYChart({
|
||||
data,
|
||||
|
@ -268,26 +164,28 @@ export function XYChart({
|
|||
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
|
||||
const darkMode = chartsThemeService.useDarkMode();
|
||||
const filteredLayers = getFilteredLayers(layers, data);
|
||||
const layersById = filteredLayers.reduce((memo, layer) => {
|
||||
memo[layer.layerId] = layer;
|
||||
return memo;
|
||||
}, {} as Record<string, DataLayerArgs>);
|
||||
const layersById = filteredLayers.reduce<Record<string, XYLayerConfigResult>>(
|
||||
(hashMap, layer) => {
|
||||
hashMap[layer.layerId] = layer;
|
||||
return hashMap;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const handleCursorUpdate = useActiveCursor(chartsActiveCursorService, chartRef, {
|
||||
datatables: Object.values(data.tables),
|
||||
});
|
||||
|
||||
if (filteredLayers.length === 0) {
|
||||
const icon: IconType = getIconForSeriesType(
|
||||
getDataLayersArgs(layers)?.[0]?.seriesType || 'bar'
|
||||
);
|
||||
return <EmptyPlaceholder icon={icon} />;
|
||||
const icon: IconType = getIconForSeriesType(getDataLayers(layers)?.[0]?.seriesType || 'bar');
|
||||
return <EmptyPlaceholder className="xyChart__empty" icon={icon} />;
|
||||
}
|
||||
|
||||
// use formatting hint of first x axis column to format ticks
|
||||
const xAxisColumn = data.tables[filteredLayers[0].layerId].columns.find(
|
||||
({ id }) => id === filteredLayers[0].xAccessor
|
||||
);
|
||||
|
||||
const xAxisFormatter = formatFactory(xAxisColumn && xAxisColumn.meta?.params);
|
||||
const layersAlreadyFormatted: Record<string, boolean> = {};
|
||||
|
||||
|
@ -368,7 +266,7 @@ export function XYChart({
|
|||
};
|
||||
|
||||
const referenceLineLayers = getReferenceLayers(layers);
|
||||
const annotationsLayers = getAnnotationsLayersArgs(layers);
|
||||
const annotationsLayers = getAnnotationsLayers(layers);
|
||||
const firstTable = data.tables[filteredLayers[0].layerId];
|
||||
|
||||
const xColumnId = firstTable.columns.find((col) => col.id === filteredLayers[0].xAccessor)?.id;
|
||||
|
@ -427,8 +325,12 @@ export function XYChart({
|
|||
const extent = axis.groupId === 'left' ? yLeftExtent : yRightExtent;
|
||||
const hasBarOrArea = Boolean(
|
||||
axis.series.some((series) => {
|
||||
const seriesType = layersById[series.layer]?.seriesType;
|
||||
return seriesType?.includes('bar') || seriesType?.includes('area');
|
||||
const layer = layersById[series.layer];
|
||||
if (!(layer && isDataLayer(layer))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return layer.seriesType.includes('bar') || layer.seriesType.includes('area');
|
||||
})
|
||||
);
|
||||
const fit = !hasBarOrArea && extent.mode === 'dataBounds';
|
||||
|
@ -489,7 +391,7 @@ export function XYChart({
|
|||
const valueLabelsStyling =
|
||||
shouldShowValueLabels && valueLabels !== 'hide' && getValueLabelsStyling(shouldRotate);
|
||||
|
||||
const colorAssignments = getColorAssignments(getDataLayersArgs(args.layers), data, formatFactory);
|
||||
const colorAssignments = getColorAssignments(getDataLayers(args.layers), data, formatFactory);
|
||||
|
||||
const clickHandler: ElementClickListener = ([[geometry, series]]) => {
|
||||
// for xyChart series is always XYChartSeriesIdentifier and geometry is always type of GeometryValue
|
||||
|
@ -548,7 +450,7 @@ export function XYChart({
|
|||
value: pointValue,
|
||||
});
|
||||
}
|
||||
const context: LensFilterEvent['data'] = {
|
||||
const context: FilterEvent['data'] = {
|
||||
data: points.map((point) => ({
|
||||
row: point.row,
|
||||
column: point.column,
|
||||
|
@ -572,7 +474,7 @@ export function XYChart({
|
|||
|
||||
const xAxisColumnIndex = table.columns.findIndex((el) => el.id === filteredLayers[0].xAccessor);
|
||||
|
||||
const context: LensBrushEvent['data'] = {
|
||||
const context: BrushEvent['data'] = {
|
||||
range: [min, max],
|
||||
table,
|
||||
column: xAxisColumnIndex,
|
||||
|
@ -827,7 +729,7 @@ export function XYChart({
|
|||
|
||||
if (!xAccessor) {
|
||||
rows.forEach((row) => {
|
||||
row.unifiedX = i18n.translate('xpack.lens.xyChart.emptyXLabel', {
|
||||
row.unifiedX = i18n.translate('expressionXY.xyChart.emptyXLabel', {
|
||||
defaultMessage: '(empty)',
|
||||
});
|
||||
});
|
||||
|
@ -1021,8 +923,6 @@ export function XYChart({
|
|||
<ReferenceLineAnnotations
|
||||
layers={referenceLineLayers}
|
||||
data={data}
|
||||
syncColors={syncColors}
|
||||
paletteService={paletteService}
|
||||
formatters={{
|
||||
left: yAxesMap.left?.formatter,
|
||||
right: yAxesMap.right?.formatter,
|
||||
|
@ -1051,23 +951,6 @@ export function XYChart({
|
|||
);
|
||||
}
|
||||
|
||||
function getFilteredLayers(layers: XYLayerArgs[], data: LensMultiTable) {
|
||||
return getDataLayersArgs(layers).filter((layer) => {
|
||||
const { layerId, xAccessor, accessors, splitAccessor } = layer;
|
||||
return !(
|
||||
!accessors.length ||
|
||||
!data.tables[layerId] ||
|
||||
data.tables[layerId].rows.length === 0 ||
|
||||
(xAccessor &&
|
||||
data.tables[layerId].rows.every((row) => typeof row[xAccessor] === 'undefined')) ||
|
||||
// stacked percentage bars have no xAccessors but splitAccessor with undefined values in them when empty
|
||||
(!xAccessor &&
|
||||
splitAccessor &&
|
||||
data.tables[layerId].rows.every((row) => typeof row[splitAccessor] === 'undefined'))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function assertNever(x: never): never {
|
||||
throw new Error('Unexpected series type: ' + x);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { visualizationDefinitions } from './visualizations';
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { SeriesTypes } from '../../common/constants';
|
||||
import {
|
||||
BarIcon,
|
||||
LineIcon,
|
||||
AreaIcon,
|
||||
BarStackedIcon,
|
||||
AreaStackedIcon,
|
||||
BarHorizontalIcon,
|
||||
BarPercentageIcon,
|
||||
AreaPercentageIcon,
|
||||
BarHorizontalStackedIcon,
|
||||
BarHorizontalPercentageIcon,
|
||||
} from '../icons';
|
||||
|
||||
export const visualizationDefinitions = [
|
||||
{ id: SeriesTypes.BAR, icon: BarIcon },
|
||||
{ id: SeriesTypes.BAR_STACKED, icon: BarStackedIcon },
|
||||
{ id: SeriesTypes.BAR_HORIZONTAL, icon: BarHorizontalIcon },
|
||||
{ id: SeriesTypes.BAR_PERCENTAGE_STACKED, icon: BarPercentageIcon },
|
||||
{ id: SeriesTypes.BAR_HORIZONTAL_STACKED, icon: BarHorizontalStackedIcon },
|
||||
{ id: SeriesTypes.BAR_HORIZONTAL_PERCENTAGE_STACKED, icon: BarHorizontalPercentageIcon },
|
||||
{ id: SeriesTypes.LINE, icon: LineIcon },
|
||||
{ id: SeriesTypes.AREA, icon: AreaIcon },
|
||||
{ id: SeriesTypes.AREA_STACKED, icon: AreaStackedIcon },
|
||||
{ id: SeriesTypes.AREA_PERCENTAGE_STACKED, icon: AreaPercentageIcon },
|
||||
];
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { getXyChartRenderer } from './xy_chart_renderer';
|
||||
export type { GetStartDepsFn } from './xy_chart_renderer';
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { ThemeServiceStart } from 'kibana/public';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ChartsPluginStart, PaletteRegistry } from '../../../../charts/public';
|
||||
import { EventAnnotationServiceType } from '../../../../event_annotation/public';
|
||||
import { ExpressionRenderDefinition } from '../../../../expressions';
|
||||
import { FormatFactory } from '../../../../field_formats/common';
|
||||
import { KibanaThemeProvider } from '../../../../kibana_react/public';
|
||||
import { XYChartProps } from '../../common';
|
||||
import { calculateMinInterval } from '../helpers';
|
||||
import { BrushEvent, FilterEvent } from '../types';
|
||||
|
||||
export type GetStartDepsFn = () => Promise<{
|
||||
formatFactory: FormatFactory;
|
||||
theme: ChartsPluginStart['theme'];
|
||||
activeCursor: ChartsPluginStart['activeCursor'];
|
||||
paletteService: PaletteRegistry;
|
||||
timeZone: string;
|
||||
useLegacyTimeAxis: boolean;
|
||||
kibanaTheme: ThemeServiceStart;
|
||||
eventAnnotationService: EventAnnotationServiceType;
|
||||
}>;
|
||||
|
||||
interface XyChartRendererDeps {
|
||||
getStartDeps: GetStartDepsFn;
|
||||
}
|
||||
|
||||
export const getXyChartRenderer = ({
|
||||
getStartDeps,
|
||||
}: XyChartRendererDeps): ExpressionRenderDefinition<XYChartProps> => ({
|
||||
name: 'xyVis',
|
||||
displayName: 'XY chart',
|
||||
help: i18n.translate('expressionXY.xyVis.renderer.help', {
|
||||
defaultMessage: 'X/Y chart renderer',
|
||||
}),
|
||||
validate: () => undefined,
|
||||
reuseDomNode: true,
|
||||
render: async (domNode: Element, config: XYChartProps, handlers) => {
|
||||
handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode));
|
||||
const onClickValue = (data: FilterEvent['data']) => {
|
||||
handlers.event({ name: 'filter', data });
|
||||
};
|
||||
const onSelectRange = (data: BrushEvent['data']) => {
|
||||
handlers.event({ name: 'brush', data });
|
||||
};
|
||||
const deps = await getStartDeps();
|
||||
|
||||
const { XYChartReportable } = await import('../components/xy_chart');
|
||||
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme$={deps.kibanaTheme.theme$}>
|
||||
<I18nProvider>
|
||||
<div
|
||||
style={{ width: '100%', height: '100%', overflowX: 'hidden' }}
|
||||
data-test-subj="xyVisChart"
|
||||
>
|
||||
<XYChartReportable
|
||||
{...config}
|
||||
formatFactory={deps.formatFactory}
|
||||
chartsActiveCursorService={deps.activeCursor}
|
||||
chartsThemeService={deps.theme}
|
||||
paletteService={deps.paletteService}
|
||||
timeZone={deps.timeZone}
|
||||
eventAnnotationService={deps.eventAnnotationService}
|
||||
useLegacyTimeAxis={deps.useLegacyTimeAxis}
|
||||
minInterval={calculateMinInterval(config)}
|
||||
interactive={handlers.isInteractive()}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
renderMode={handlers.getRenderMode()}
|
||||
syncColors={handlers.isSyncColorsEnabled()}
|
||||
/>
|
||||
</div>{' '}
|
||||
</I18nProvider>
|
||||
</KibanaThemeProvider>,
|
||||
domNode,
|
||||
() => handlers.done()
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,18 +1,13 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 './expression_reference_lines.scss';
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText } from '@elastic/eui';
|
||||
import { Position } from '@elastic/charts';
|
||||
import classnames from 'classnames';
|
||||
import type { IconPosition, YAxisMode, YConfig } from '../../common/expressions';
|
||||
import { hasIcon } from './xy_config_panel/shared/icon_select';
|
||||
import { annotationsIconSet } from './annotations/config_panel/icon_set';
|
||||
import type { IconPosition, YAxisMode, YConfig } from '../../common/types';
|
||||
import { hasIcon } from './icon';
|
||||
|
||||
export const LINES_MARKER_SIZE = 20;
|
||||
|
||||
|
@ -130,124 +125,4 @@ export function getBaseIconPlacement(
|
|||
return Position.Top;
|
||||
}
|
||||
|
||||
export function MarkerBody({
|
||||
label,
|
||||
isHorizontal,
|
||||
}: {
|
||||
label: string | undefined;
|
||||
isHorizontal: boolean;
|
||||
}) {
|
||||
if (!label) {
|
||||
return null;
|
||||
}
|
||||
if (isHorizontal) {
|
||||
return (
|
||||
<div className="eui-textTruncate" style={{ maxWidth: LINES_MARKER_SIZE * 3 }}>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="lnsXyDecorationRotatedWrapper"
|
||||
style={{
|
||||
width: LINES_MARKER_SIZE,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="eui-textTruncate lnsXyDecorationRotatedWrapper__label"
|
||||
style={{
|
||||
maxWidth: LINES_MARKER_SIZE * 3,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isNumericalString = (value: string) => !isNaN(Number(value));
|
||||
|
||||
function NumberIcon({ number }: { number: number }) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
justifyContent="spaceAround"
|
||||
className="lnsXyAnnotationNumberIcon"
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
>
|
||||
<EuiText color="ghost" className="lnsXyAnnotationNumberIcon__text">
|
||||
{number < 10 ? number : `9+`}
|
||||
</EuiText>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
interface MarkerConfig {
|
||||
axisMode?: YAxisMode;
|
||||
icon?: string;
|
||||
textVisibility?: boolean;
|
||||
iconPosition?: IconPosition;
|
||||
}
|
||||
|
||||
export const AnnotationIcon = ({
|
||||
type,
|
||||
rotateClassName = '',
|
||||
isHorizontal,
|
||||
renderedInChart,
|
||||
...rest
|
||||
}: {
|
||||
type: string;
|
||||
rotateClassName?: string;
|
||||
isHorizontal?: boolean;
|
||||
renderedInChart?: boolean;
|
||||
} & EuiIconProps) => {
|
||||
if (isNumericalString(type)) {
|
||||
return <NumberIcon number={Number(type)} />;
|
||||
}
|
||||
const iconConfig = annotationsIconSet.find((i) => i.value === type);
|
||||
if (!iconConfig) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<EuiIcon
|
||||
{...rest}
|
||||
type={iconConfig.icon || type}
|
||||
className={classnames(
|
||||
{ [rotateClassName]: iconConfig.shouldRotate },
|
||||
{
|
||||
lensAnnotationIconFill: renderedInChart && iconConfig.canFill,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export function Marker({
|
||||
config,
|
||||
isHorizontal,
|
||||
hasReducedPadding,
|
||||
label,
|
||||
rotateClassName,
|
||||
}: {
|
||||
config: MarkerConfig;
|
||||
isHorizontal: boolean;
|
||||
hasReducedPadding: boolean;
|
||||
label?: string;
|
||||
rotateClassName?: string;
|
||||
}) {
|
||||
if (hasIcon(config.icon)) {
|
||||
return (
|
||||
<AnnotationIcon type={config.icon} rotateClassName={rotateClassName} renderedInChart={true} />
|
||||
);
|
||||
}
|
||||
|
||||
// if there's some text, check whether to show it as marker, or just show some padding for the icon
|
||||
if (config.textVisibility) {
|
||||
if (hasReducedPadding) {
|
||||
return <MarkerBody label={label} isHorizontal={isHorizontal} />;
|
||||
}
|
||||
return <EuiIcon type="empty" />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
export const isNumericalString = (value: string) => !isNaN(Number(value));
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { TriangleIcon, CircleIcon } from '../icons';
|
||||
|
||||
export const annotationsIconSet = [
|
||||
{
|
||||
value: 'asterisk',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.asteriskIconLabel', {
|
||||
defaultMessage: 'Asterisk',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'alert',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.alertIconLabel', {
|
||||
defaultMessage: 'Alert',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'bell',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.bellIconLabel', {
|
||||
defaultMessage: 'Bell',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'bolt',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.boltIconLabel', {
|
||||
defaultMessage: 'Bolt',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'bug',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.bugIconLabel', {
|
||||
defaultMessage: 'Bug',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'circle',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.circleIconLabel', {
|
||||
defaultMessage: 'Circle',
|
||||
}),
|
||||
icon: CircleIcon,
|
||||
canFill: true,
|
||||
},
|
||||
|
||||
{
|
||||
value: 'editorComment',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.commentIconLabel', {
|
||||
defaultMessage: 'Comment',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'flag',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.flagIconLabel', {
|
||||
defaultMessage: 'Flag',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'heart',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.heartLabel', {
|
||||
defaultMessage: 'Heart',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'mapMarker',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.mapMarkerLabel', {
|
||||
defaultMessage: 'Map Marker',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'pinFilled',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.mapPinLabel', {
|
||||
defaultMessage: 'Map Pin',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'starEmpty',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.starLabel', { defaultMessage: 'Star' }),
|
||||
},
|
||||
{
|
||||
value: 'tag',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.tagIconLabel', {
|
||||
defaultMessage: 'Tag',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'triangle',
|
||||
label: i18n.translate('expressionXY.xyChart.iconSelect.triangleIconLabel', {
|
||||
defaultMessage: 'Triangle',
|
||||
}),
|
||||
icon: TriangleIcon,
|
||||
shouldRotate: true,
|
||||
canFill: true,
|
||||
},
|
||||
];
|
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
* 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 { DataLayerConfigResult } from '../../common';
|
||||
import { LayerTypes } from '../../common/constants';
|
||||
import { Datatable } from '../../../../../plugins/expressions/public';
|
||||
import { getAxesConfiguration } from './axes_configuration';
|
||||
|
||||
describe('axes_configuration', () => {
|
||||
const tables: Record<string, Datatable> = {
|
||||
first: {
|
||||
type: 'datatable',
|
||||
rows: [
|
||||
{
|
||||
xAccessorId: 1585758120000,
|
||||
splitAccessorId: "Men's Clothing",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585758360000,
|
||||
splitAccessorId: "Women's Accessories",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585758360000,
|
||||
splitAccessorId: "Women's Clothing",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585759380000,
|
||||
splitAccessorId: "Men's Clothing",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585759380000,
|
||||
splitAccessorId: "Men's Shoes",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585759380000,
|
||||
splitAccessorId: "Women's Clothing",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585760700000,
|
||||
splitAccessorId: "Men's Clothing",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585760760000,
|
||||
splitAccessorId: "Men's Clothing",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585760760000,
|
||||
splitAccessorId: "Men's Shoes",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
{
|
||||
xAccessorId: 1585761120000,
|
||||
splitAccessorId: "Men's Shoes",
|
||||
yAccessorId: 1,
|
||||
yAccessorId2: 1,
|
||||
yAccessorId3: 1,
|
||||
yAccessorId4: 4,
|
||||
},
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
id: 'xAccessorId',
|
||||
name: 'order_date per minute',
|
||||
meta: {
|
||||
type: 'date',
|
||||
field: 'order_date',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
type: 'date_histogram',
|
||||
params: {
|
||||
field: 'order_date',
|
||||
timeRange: { from: '2020-04-01T16:14:16.246Z', to: '2020-04-01T17:15:41.263Z' },
|
||||
useNormalizedEsInterval: true,
|
||||
scaleMetricValues: false,
|
||||
interval: '1m',
|
||||
drop_partials: false,
|
||||
min_doc_count: 0,
|
||||
extended_bounds: {},
|
||||
},
|
||||
},
|
||||
params: { params: { id: 'date', params: { pattern: 'HH:mm' } } },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'splitAccessorId',
|
||||
name: 'Top values of category.keyword',
|
||||
meta: {
|
||||
type: 'string',
|
||||
field: 'category.keyword',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
type: 'terms',
|
||||
params: {
|
||||
field: 'category.keyword',
|
||||
orderBy: 'yAccessorId',
|
||||
order: 'desc',
|
||||
size: 3,
|
||||
otherBucket: false,
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucket: false,
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
params: {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'string',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
parsedUrl: {
|
||||
origin: 'http://localhost:5601',
|
||||
pathname: '/jiy/app/kibana',
|
||||
basePath: '/jiy',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'yAccessorId',
|
||||
name: 'Count of records',
|
||||
meta: {
|
||||
type: 'number',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
type: 'count',
|
||||
},
|
||||
params: { id: 'number' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'yAccessorId2',
|
||||
name: 'Other column',
|
||||
meta: {
|
||||
type: 'number',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
type: 'average',
|
||||
},
|
||||
params: { id: 'bytes' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'yAccessorId3',
|
||||
name: 'Other column',
|
||||
meta: {
|
||||
type: 'number',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
type: 'average',
|
||||
},
|
||||
params: { id: 'currency' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'yAccessorId4',
|
||||
name: 'Other column',
|
||||
meta: {
|
||||
type: 'number',
|
||||
source: 'esaggs',
|
||||
index: 'indexPatternId',
|
||||
sourceParams: {
|
||||
indexPatternId: 'indexPatternId',
|
||||
type: 'average',
|
||||
},
|
||||
params: { id: 'currency' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const sampleLayer: DataLayerConfigResult = {
|
||||
type: 'dataLayer',
|
||||
layerId: 'first',
|
||||
layerType: LayerTypes.DATA,
|
||||
seriesType: 'line',
|
||||
xAccessor: 'c',
|
||||
accessors: ['yAccessorId'],
|
||||
splitAccessor: 'd',
|
||||
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
|
||||
xScaleType: 'ordinal',
|
||||
yScaleType: 'linear',
|
||||
isHistogram: false,
|
||||
palette: { type: 'palette', name: 'default' },
|
||||
};
|
||||
|
||||
it('should map auto series to left axis', () => {
|
||||
const formatFactory = jest.fn();
|
||||
const groups = getAxesConfiguration([sampleLayer], false, tables, formatFactory);
|
||||
expect(groups.length).toEqual(1);
|
||||
expect(groups[0].position).toEqual('left');
|
||||
expect(groups[0].series[0].accessor).toEqual('yAccessorId');
|
||||
expect(groups[0].series[0].layer).toEqual('first');
|
||||
});
|
||||
|
||||
it('should map auto series to right axis if formatters do not match', () => {
|
||||
const formatFactory = jest.fn();
|
||||
const twoSeriesLayer = { ...sampleLayer, accessors: ['yAccessorId', 'yAccessorId2'] };
|
||||
const groups = getAxesConfiguration([twoSeriesLayer], false, tables, formatFactory);
|
||||
expect(groups.length).toEqual(2);
|
||||
expect(groups[0].position).toEqual('left');
|
||||
expect(groups[1].position).toEqual('right');
|
||||
expect(groups[0].series[0].accessor).toEqual('yAccessorId');
|
||||
expect(groups[1].series[0].accessor).toEqual('yAccessorId2');
|
||||
});
|
||||
|
||||
it('should map auto series to left if left and right are already filled with non-matching series', () => {
|
||||
const formatFactory = jest.fn();
|
||||
const threeSeriesLayer = {
|
||||
...sampleLayer,
|
||||
accessors: ['yAccessorId', 'yAccessorId2', 'yAccessorId3'],
|
||||
};
|
||||
const groups = getAxesConfiguration([threeSeriesLayer], false, tables, formatFactory);
|
||||
expect(groups.length).toEqual(2);
|
||||
expect(groups[0].position).toEqual('left');
|
||||
expect(groups[1].position).toEqual('right');
|
||||
expect(groups[0].series[0].accessor).toEqual('yAccessorId');
|
||||
expect(groups[0].series[1].accessor).toEqual('yAccessorId3');
|
||||
expect(groups[1].series[0].accessor).toEqual('yAccessorId2');
|
||||
});
|
||||
|
||||
it('should map right series to right axis', () => {
|
||||
const formatFactory = jest.fn();
|
||||
const groups = getAxesConfiguration(
|
||||
[
|
||||
{
|
||||
...sampleLayer,
|
||||
yConfig: [{ type: 'yConfig', forAccessor: 'yAccessorId', axisMode: 'right' }],
|
||||
},
|
||||
],
|
||||
false,
|
||||
tables,
|
||||
formatFactory
|
||||
);
|
||||
expect(groups.length).toEqual(1);
|
||||
expect(groups[0].position).toEqual('right');
|
||||
expect(groups[0].series[0].accessor).toEqual('yAccessorId');
|
||||
expect(groups[0].series[0].layer).toEqual('first');
|
||||
});
|
||||
|
||||
it('should map series with matching formatters to same axis', () => {
|
||||
const formatFactory = jest.fn();
|
||||
const groups = getAxesConfiguration(
|
||||
[
|
||||
{
|
||||
...sampleLayer,
|
||||
accessors: ['yAccessorId', 'yAccessorId3', 'yAccessorId4'],
|
||||
yConfig: [{ type: 'yConfig', forAccessor: 'yAccessorId', axisMode: 'right' }],
|
||||
},
|
||||
],
|
||||
false,
|
||||
tables,
|
||||
formatFactory
|
||||
);
|
||||
expect(groups.length).toEqual(2);
|
||||
expect(groups[0].position).toEqual('left');
|
||||
expect(groups[0].series[0].accessor).toEqual('yAccessorId3');
|
||||
expect(groups[0].series[1].accessor).toEqual('yAccessorId4');
|
||||
expect(groups[1].position).toEqual('right');
|
||||
expect(groups[1].series[0].accessor).toEqual('yAccessorId');
|
||||
expect(formatFactory).toHaveBeenCalledWith({ id: 'number' });
|
||||
expect(formatFactory).toHaveBeenCalledWith({ id: 'currency' });
|
||||
});
|
||||
|
||||
it('should create one formatter per series group', () => {
|
||||
const formatFactory = jest.fn();
|
||||
getAxesConfiguration(
|
||||
[
|
||||
{
|
||||
...sampleLayer,
|
||||
accessors: ['yAccessorId', 'yAccessorId3', 'yAccessorId4'],
|
||||
yConfig: [{ type: 'yConfig', forAccessor: 'yAccessorId', axisMode: 'right' }],
|
||||
},
|
||||
],
|
||||
false,
|
||||
tables,
|
||||
formatFactory
|
||||
);
|
||||
expect(formatFactory).toHaveBeenCalledTimes(2);
|
||||
expect(formatFactory).toHaveBeenCalledWith({ id: 'number' });
|
||||
expect(formatFactory).toHaveBeenCalledWith({ id: 'currency' });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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 { FormatFactory } from '../types';
|
||||
import { AxisExtentConfig, DataLayerConfigResult } from '../../common';
|
||||
import { Datatable } from '../../../../../plugins/expressions/public';
|
||||
import type {
|
||||
IFieldFormat,
|
||||
SerializedFieldFormat,
|
||||
} from '../../../../../plugins/field_formats/common';
|
||||
|
||||
interface FormattedMetric {
|
||||
layer: string;
|
||||
accessor: string;
|
||||
fieldFormat: SerializedFieldFormat;
|
||||
}
|
||||
|
||||
export type GroupsConfiguration = Array<{
|
||||
groupId: string;
|
||||
position: 'left' | 'right' | 'bottom' | 'top';
|
||||
formatter?: IFieldFormat;
|
||||
series: Array<{ layer: string; accessor: string }>;
|
||||
}>;
|
||||
|
||||
export function isFormatterCompatible(
|
||||
formatter1: SerializedFieldFormat,
|
||||
formatter2: SerializedFieldFormat
|
||||
) {
|
||||
return formatter1.id === formatter2.id;
|
||||
}
|
||||
|
||||
export function groupAxesByType(
|
||||
layers: DataLayerConfigResult[],
|
||||
tables?: Record<string, Datatable>
|
||||
) {
|
||||
const series: {
|
||||
auto: FormattedMetric[];
|
||||
left: FormattedMetric[];
|
||||
right: FormattedMetric[];
|
||||
bottom: FormattedMetric[];
|
||||
} = {
|
||||
auto: [],
|
||||
left: [],
|
||||
right: [],
|
||||
bottom: [],
|
||||
};
|
||||
|
||||
layers?.forEach((layer) => {
|
||||
const table = tables?.[layer.layerId];
|
||||
layer.accessors.forEach((accessor) => {
|
||||
const mode =
|
||||
layer.yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode ||
|
||||
'auto';
|
||||
let formatter: SerializedFieldFormat = table?.columns.find((column) => column.id === accessor)
|
||||
?.meta?.params || { id: 'number' };
|
||||
if (layer.seriesType.includes('percentage') && formatter.id !== 'percent') {
|
||||
formatter = {
|
||||
id: 'percent',
|
||||
params: {
|
||||
pattern: '0.[00]%',
|
||||
},
|
||||
};
|
||||
}
|
||||
series[mode].push({
|
||||
layer: layer.layerId,
|
||||
accessor,
|
||||
fieldFormat: formatter,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
series.auto.forEach((currentSeries) => {
|
||||
if (
|
||||
series.left.length === 0 ||
|
||||
(tables &&
|
||||
series.left.every((leftSeries) =>
|
||||
isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
|
||||
))
|
||||
) {
|
||||
series.left.push(currentSeries);
|
||||
} else if (
|
||||
series.right.length === 0 ||
|
||||
(tables &&
|
||||
series.left.every((leftSeries) =>
|
||||
isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
|
||||
))
|
||||
) {
|
||||
series.right.push(currentSeries);
|
||||
} else if (series.right.length >= series.left.length) {
|
||||
series.left.push(currentSeries);
|
||||
} else {
|
||||
series.right.push(currentSeries);
|
||||
}
|
||||
});
|
||||
return series;
|
||||
}
|
||||
|
||||
export function getAxesConfiguration(
|
||||
layers: DataLayerConfigResult[],
|
||||
shouldRotate: boolean,
|
||||
tables?: Record<string, Datatable>,
|
||||
formatFactory?: FormatFactory
|
||||
): GroupsConfiguration {
|
||||
const series = groupAxesByType(layers, tables);
|
||||
|
||||
const axisGroups: GroupsConfiguration = [];
|
||||
|
||||
if (series.left.length > 0) {
|
||||
axisGroups.push({
|
||||
groupId: 'left',
|
||||
position: shouldRotate ? 'bottom' : 'left',
|
||||
formatter: formatFactory?.(series.left[0].fieldFormat),
|
||||
series: series.left.map(({ fieldFormat, ...currentSeries }) => currentSeries),
|
||||
});
|
||||
}
|
||||
|
||||
if (series.right.length > 0) {
|
||||
axisGroups.push({
|
||||
groupId: 'right',
|
||||
position: shouldRotate ? 'top' : 'right',
|
||||
formatter: formatFactory?.(series.right[0].fieldFormat),
|
||||
series: series.right.map(({ fieldFormat, ...currentSeries }) => currentSeries),
|
||||
});
|
||||
}
|
||||
|
||||
return axisGroups;
|
||||
}
|
||||
|
||||
export function validateExtent(hasBarOrArea: boolean, extent?: AxisExtentConfig) {
|
||||
const inclusiveZeroError =
|
||||
extent &&
|
||||
hasBarOrArea &&
|
||||
((extent.lowerBound !== undefined && extent.lowerBound > 0) ||
|
||||
(extent.upperBound !== undefined && extent.upperBound) < 0);
|
||||
const boundaryError =
|
||||
extent &&
|
||||
extent.lowerBound !== undefined &&
|
||||
extent.upperBound !== undefined &&
|
||||
extent.upperBound <= extent.lowerBound;
|
||||
return { inclusiveZeroError, boundaryError };
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* 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 { getColorAssignments } from './color_assignment';
|
||||
import type { DataLayerConfigResult, LensMultiTable } from '../../common';
|
||||
import type { FormatFactory } from '../types';
|
||||
import { LayerTypes } from '../../common/constants';
|
||||
|
||||
describe('color_assignment', () => {
|
||||
const layers: DataLayerConfigResult[] = [
|
||||
{
|
||||
type: 'dataLayer',
|
||||
yScaleType: 'linear',
|
||||
xScaleType: 'linear',
|
||||
isHistogram: true,
|
||||
seriesType: 'bar',
|
||||
palette: { type: 'palette', name: 'palette1' },
|
||||
layerId: '1',
|
||||
layerType: LayerTypes.DATA,
|
||||
splitAccessor: 'split1',
|
||||
accessors: ['y1', 'y2'],
|
||||
},
|
||||
{
|
||||
type: 'dataLayer',
|
||||
yScaleType: 'linear',
|
||||
xScaleType: 'linear',
|
||||
isHistogram: true,
|
||||
seriesType: 'bar',
|
||||
palette: { type: 'palette', name: 'palette2' },
|
||||
layerId: '2',
|
||||
layerType: LayerTypes.DATA,
|
||||
splitAccessor: 'split2',
|
||||
accessors: ['y3', 'y4'],
|
||||
},
|
||||
];
|
||||
|
||||
const data: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
'1': {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'split1', name: '', meta: { type: 'number' } },
|
||||
{ id: 'y1', name: '', meta: { type: 'number' } },
|
||||
{ id: 'y2', name: '', meta: { type: 'number' } },
|
||||
],
|
||||
rows: [
|
||||
{ split1: 1 },
|
||||
{ split1: 2 },
|
||||
{ split1: 3 },
|
||||
{ split1: 1 },
|
||||
{ split1: 2 },
|
||||
{ split1: 3 },
|
||||
],
|
||||
},
|
||||
'2': {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'split2', name: '', meta: { type: 'number' } },
|
||||
{ id: 'y1', name: '', meta: { type: 'number' } },
|
||||
{ id: 'y2', name: '', meta: { type: 'number' } },
|
||||
],
|
||||
rows: [
|
||||
{ split2: 1 },
|
||||
{ split2: 2 },
|
||||
{ split2: 3 },
|
||||
{ split2: 1 },
|
||||
{ split2: 2 },
|
||||
{ split2: 3 },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const formatFactory = (() =>
|
||||
({
|
||||
convert(x: unknown) {
|
||||
return x;
|
||||
},
|
||||
} as unknown)) as FormatFactory;
|
||||
|
||||
describe('totalSeriesCount', () => {
|
||||
it('should calculate total number of series per palette', () => {
|
||||
const assignments = getColorAssignments(layers, data, formatFactory);
|
||||
// two y accessors, with 3 splitted series
|
||||
expect(assignments.palette1.totalSeriesCount).toEqual(2 * 3);
|
||||
expect(assignments.palette2.totalSeriesCount).toEqual(2 * 3);
|
||||
});
|
||||
|
||||
it('should calculate total number of series spanning multible layers', () => {
|
||||
const assignments = getColorAssignments(
|
||||
[layers[0], { ...layers[1], palette: layers[0].palette }],
|
||||
data,
|
||||
formatFactory
|
||||
);
|
||||
// two y accessors, with 3 splitted series, two times
|
||||
expect(assignments.palette1.totalSeriesCount).toEqual(2 * 3 + 2 * 3);
|
||||
expect(assignments.palette2).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should calculate total number of series for non split series', () => {
|
||||
const assignments = getColorAssignments(
|
||||
[layers[0], { ...layers[1], palette: layers[0].palette, splitAccessor: undefined }],
|
||||
data,
|
||||
formatFactory
|
||||
);
|
||||
// two y accessors, with 3 splitted series for the first layer, 2 non splitted y accessors for the second layer
|
||||
expect(assignments.palette1.totalSeriesCount).toEqual(2 * 3 + 2);
|
||||
expect(assignments.palette2).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should format non-primitive values and count them correctly', () => {
|
||||
const complexObject = { aProp: 123 };
|
||||
const formatMock = jest.fn((x) => 'formatted');
|
||||
const assignments = getColorAssignments(
|
||||
layers,
|
||||
{
|
||||
...data,
|
||||
tables: {
|
||||
...data.tables,
|
||||
'1': { ...data.tables['1'], rows: [{ split1: complexObject }, { split1: 'abc' }] },
|
||||
},
|
||||
},
|
||||
(() =>
|
||||
({
|
||||
convert: formatMock,
|
||||
} as unknown)) as FormatFactory
|
||||
);
|
||||
expect(assignments.palette1.totalSeriesCount).toEqual(2 * 2);
|
||||
expect(assignments.palette2.totalSeriesCount).toEqual(2 * 3);
|
||||
expect(formatMock).toHaveBeenCalledWith(complexObject);
|
||||
});
|
||||
|
||||
it('should handle missing tables', () => {
|
||||
const assignments = getColorAssignments(layers, { ...data, tables: {} }, formatFactory);
|
||||
// if there is no data, just assume a single split
|
||||
expect(assignments.palette1.totalSeriesCount).toEqual(2);
|
||||
});
|
||||
|
||||
it('should handle missing columns', () => {
|
||||
const assignments = getColorAssignments(
|
||||
layers,
|
||||
{
|
||||
...data,
|
||||
tables: {
|
||||
...data.tables,
|
||||
'1': {
|
||||
...data.tables['1'],
|
||||
columns: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
formatFactory
|
||||
);
|
||||
// if the split column is missing, just assume a single split
|
||||
expect(assignments.palette1.totalSeriesCount).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRank', () => {
|
||||
it('should return the correct rank for a series key', () => {
|
||||
const assignments = getColorAssignments(layers, data, formatFactory);
|
||||
// 3 series in front of 2/y2 - 1/y1, 1/y2 and 2/y1
|
||||
expect(assignments.palette1.getRank(layers[0], '2', 'y2')).toEqual(3);
|
||||
// 1 series in front of 1/y4 - 1/y3
|
||||
expect(assignments.palette2.getRank(layers[1], '1', 'y4')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return the correct rank for a series key spanning multiple layers', () => {
|
||||
const newLayers = [layers[0], { ...layers[1], palette: layers[0].palette }];
|
||||
const assignments = getColorAssignments(newLayers, data, formatFactory);
|
||||
// 3 series in front of 2/y2 - 1/y1, 1/y2 and 2/y1
|
||||
expect(assignments.palette1.getRank(newLayers[0], '2', 'y2')).toEqual(3);
|
||||
// 2 series in front for the current layer (1/y3, 1/y4), plus all 6 series from the first layer
|
||||
expect(assignments.palette1.getRank(newLayers[1], '2', 'y3')).toEqual(8);
|
||||
});
|
||||
|
||||
it('should return the correct rank for a series without a split', () => {
|
||||
const newLayers = [
|
||||
layers[0],
|
||||
{ ...layers[1], palette: layers[0].palette, splitAccessor: undefined },
|
||||
];
|
||||
const assignments = getColorAssignments(newLayers, data, formatFactory);
|
||||
// 3 series in front of 2/y2 - 1/y1, 1/y2 and 2/y1
|
||||
expect(assignments.palette1.getRank(newLayers[0], '2', 'y2')).toEqual(3);
|
||||
// 1 series in front for the current layer (y3), plus all 6 series from the first layer
|
||||
expect(assignments.palette1.getRank(newLayers[1], 'Metric y4', 'y4')).toEqual(7);
|
||||
});
|
||||
|
||||
it('should return the correct rank for a series with a non-primitive value', () => {
|
||||
const assignments = getColorAssignments(
|
||||
layers,
|
||||
{
|
||||
...data,
|
||||
tables: {
|
||||
...data.tables,
|
||||
'1': { ...data.tables['1'], rows: [{ split1: 'abc' }, { split1: { aProp: 123 } }] },
|
||||
},
|
||||
},
|
||||
(() =>
|
||||
({
|
||||
convert: () => 'formatted',
|
||||
} as unknown)) as FormatFactory
|
||||
);
|
||||
// 3 series in front of (complex object)/y1 - abc/y1, abc/y2
|
||||
expect(assignments.palette1.getRank(layers[0], 'formatted', 'y1')).toEqual(2);
|
||||
});
|
||||
|
||||
it('should handle missing tables', () => {
|
||||
const assignments = getColorAssignments(layers, { ...data, tables: {} }, formatFactory);
|
||||
// if there is no data, assume it is the first splitted series. One series in front - 0/y1
|
||||
expect(assignments.palette1.getRank(layers[0], '2', 'y2')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should handle missing columns', () => {
|
||||
const assignments = getColorAssignments(
|
||||
layers,
|
||||
{
|
||||
...data,
|
||||
tables: {
|
||||
...data.tables,
|
||||
'1': {
|
||||
...data.tables['1'],
|
||||
columns: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
formatFactory
|
||||
);
|
||||
// if the split column is missing, assume it is the first splitted series. One series in front - 0/y1
|
||||
expect(assignments.palette1.getRank(layers[0], '2', 'y2')).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 { uniq, mapValues } from 'lodash';
|
||||
import { euiLightVars } from '@kbn/ui-theme';
|
||||
import type { Datatable } from '../../../../expressions';
|
||||
import { FormatFactory } from '../types';
|
||||
import { isDataLayer } from './visualization';
|
||||
import { DataLayerConfigResult, XYLayerConfigResult } from '../../common';
|
||||
|
||||
const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object';
|
||||
|
||||
export const defaultReferenceLineColor = euiLightVars.euiColorDarkShade;
|
||||
|
||||
export type ColorAssignments = Record<
|
||||
string,
|
||||
{
|
||||
totalSeriesCount: number;
|
||||
getRank(sortedLayer: DataLayerConfigResult, seriesKey: string, yAccessor: string): number;
|
||||
}
|
||||
>;
|
||||
|
||||
export function getColorAssignments(
|
||||
layers: XYLayerConfigResult[],
|
||||
data: { tables: Record<string, Datatable> },
|
||||
formatFactory: FormatFactory
|
||||
): ColorAssignments {
|
||||
const layersPerPalette: Record<string, DataLayerConfigResult[]> = {};
|
||||
|
||||
layers
|
||||
.filter((layer): layer is DataLayerConfigResult => isDataLayer(layer))
|
||||
.forEach((layer) => {
|
||||
const palette = layer.palette?.name || 'default';
|
||||
if (!layersPerPalette[palette]) {
|
||||
layersPerPalette[palette] = [];
|
||||
}
|
||||
layersPerPalette[palette].push(layer);
|
||||
});
|
||||
|
||||
return mapValues(layersPerPalette, (paletteLayers) => {
|
||||
const seriesPerLayer = paletteLayers.map((layer, layerIndex) => {
|
||||
if (!layer.splitAccessor) {
|
||||
return { numberOfSeries: layer.accessors.length, splits: [] };
|
||||
}
|
||||
const splitAccessor = layer.splitAccessor;
|
||||
const column = data.tables[layer.layerId]?.columns.find(({ id }) => id === splitAccessor);
|
||||
const columnFormatter = column && formatFactory(column.meta.params);
|
||||
const splits =
|
||||
!column || !data.tables[layer.layerId]
|
||||
? []
|
||||
: uniq(
|
||||
data.tables[layer.layerId].rows.map((row) => {
|
||||
let value = row[splitAccessor];
|
||||
if (value && !isPrimitive(value)) {
|
||||
value = columnFormatter?.convert(value) ?? value;
|
||||
} else {
|
||||
value = String(value);
|
||||
}
|
||||
return value;
|
||||
})
|
||||
);
|
||||
return { numberOfSeries: (splits.length || 1) * layer.accessors.length, splits };
|
||||
});
|
||||
const totalSeriesCount = seriesPerLayer.reduce(
|
||||
(sum, perLayer) => sum + perLayer.numberOfSeries,
|
||||
0
|
||||
);
|
||||
return {
|
||||
totalSeriesCount,
|
||||
getRank(sortedLayer: DataLayerConfigResult, seriesKey: string, yAccessor: string) {
|
||||
const layerIndex = paletteLayers.findIndex((l) => sortedLayer.layerId === l.layerId);
|
||||
const currentSeriesPerLayer = seriesPerLayer[layerIndex];
|
||||
const splitRank = currentSeriesPerLayer.splits.indexOf(seriesKey);
|
||||
return (
|
||||
(layerIndex === 0
|
||||
? 0
|
||||
: seriesPerLayer
|
||||
.slice(0, layerIndex)
|
||||
.reduce((sum, perLayer) => sum + perLayer.numberOfSeries, 0)) +
|
||||
(sortedLayer.splitAccessor && splitRank !== -1
|
||||
? splitRank * sortedLayer.accessors.length
|
||||
: 0) +
|
||||
sortedLayer.accessors.indexOf(yAccessor)
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 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 { Fit } from '@elastic/charts';
|
||||
import { EndValue, FittingFunction } from '../../common/expressions';
|
||||
import { EndValue, FittingFunction } from '../../common';
|
||||
|
||||
export function getFitEnum(fittingFunction?: FittingFunction | EndValue) {
|
||||
if (fittingFunction) {
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export function hasIcon(icon: string | undefined): icon is string {
|
||||
return icon != null && icon !== 'empty';
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './interval';
|
||||
export * from './layers';
|
||||
export * from './state';
|
||||
export * from './visualization';
|
||||
export * from './fitting_functions';
|
||||
export * from './axes_configuration';
|
||||
export * from './reference_lines';
|
||||
export * from './icon';
|
||||
export * from './color_assignment';
|
||||
export * from './annotations_icon_set';
|
||||
export * from './annotations';
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 { DataLayerConfigResult, XYChartProps } from '../../common';
|
||||
import { sampleArgs } from '../../common/__mocks__';
|
||||
import { calculateMinInterval } from './interval';
|
||||
|
||||
describe('calculateMinInterval', () => {
|
||||
let xyProps: XYChartProps;
|
||||
|
||||
beforeEach(() => {
|
||||
xyProps = sampleArgs();
|
||||
(xyProps.args.layers[0] as DataLayerConfigResult).xScaleType = 'time';
|
||||
});
|
||||
it('should use first valid layer and determine interval', async () => {
|
||||
xyProps.data.tables.first.columns[2].meta.source = 'esaggs';
|
||||
xyProps.data.tables.first.columns[2].meta.sourceParams = {
|
||||
type: 'date_histogram',
|
||||
params: {
|
||||
used_interval: '5m',
|
||||
},
|
||||
};
|
||||
const result = await calculateMinInterval(xyProps);
|
||||
expect(result).toEqual(5 * 60 * 1000);
|
||||
});
|
||||
|
||||
it('should return interval of number histogram if available on first x axis columns', async () => {
|
||||
(xyProps.args.layers[0] as DataLayerConfigResult).xScaleType = 'linear';
|
||||
xyProps.data.tables.first.columns[2].meta = {
|
||||
source: 'esaggs',
|
||||
type: 'number',
|
||||
field: 'someField',
|
||||
sourceParams: {
|
||||
type: 'histogram',
|
||||
params: {
|
||||
interval: 'auto',
|
||||
used_interval: 5,
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await calculateMinInterval(xyProps);
|
||||
expect(result).toEqual(5);
|
||||
});
|
||||
|
||||
it('should return undefined if data table is empty', async () => {
|
||||
xyProps.data.tables.first.rows = [];
|
||||
xyProps.data.tables.first.columns[2].meta.source = 'esaggs';
|
||||
xyProps.data.tables.first.columns[2].meta.sourceParams = {
|
||||
type: 'date_histogram',
|
||||
params: {
|
||||
used_interval: '5m',
|
||||
},
|
||||
};
|
||||
const result = await calculateMinInterval(xyProps);
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined if interval can not be checked', async () => {
|
||||
const result = await calculateMinInterval(xyProps);
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined if date column is not found', async () => {
|
||||
xyProps.data.tables.first.columns.splice(2, 1);
|
||||
const result = await calculateMinInterval(xyProps);
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined if x axis is not a date', async () => {
|
||||
(xyProps.args.layers[0] as DataLayerConfigResult).xScaleType = 'ordinal';
|
||||
xyProps.data.tables.first.columns.splice(2, 1);
|
||||
const result = await calculateMinInterval(xyProps);
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { search } from '../../../../data/public';
|
||||
import { XYChartProps } from '../../common';
|
||||
import { getFilteredLayers } from './layers';
|
||||
import { isDataLayer } from './visualization';
|
||||
|
||||
export function calculateMinInterval({ args: { layers }, data }: XYChartProps) {
|
||||
const filteredLayers = getFilteredLayers(layers, data);
|
||||
if (filteredLayers.length === 0) return;
|
||||
const isTimeViz = filteredLayers.every((l) => isDataLayer(l) && l.xScaleType === 'time');
|
||||
const xColumn = data.tables[filteredLayers[0].layerId].columns.find(
|
||||
(column) => isDataLayer(filteredLayers[0]) && column.id === filteredLayers[0].xAccessor
|
||||
);
|
||||
|
||||
if (!xColumn) return;
|
||||
if (!isTimeViz) {
|
||||
const histogramInterval = search.aggs.getNumberHistogramIntervalByDatatableColumn(xColumn);
|
||||
if (typeof histogramInterval === 'number') {
|
||||
return histogramInterval;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
const dateInterval = search.aggs.getDateHistogramMetaDataByDatatableColumn(xColumn)?.interval;
|
||||
if (!dateInterval) return;
|
||||
const intervalDuration = search.aggs.parseInterval(dateInterval);
|
||||
if (!intervalDuration) return;
|
||||
return intervalDuration.as('milliseconds');
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { LensMultiTable } from '../../common';
|
||||
import { DataLayerConfigResult, XYLayerConfigResult } from '../../common/types';
|
||||
import { getDataLayers } from './visualization';
|
||||
|
||||
export function getFilteredLayers(layers: XYLayerConfigResult[], data: LensMultiTable) {
|
||||
return getDataLayers(layers).filter<DataLayerConfigResult>(
|
||||
(layer): layer is DataLayerConfigResult => {
|
||||
const { layerId, xAccessor, accessors, splitAccessor } = layer;
|
||||
return !(
|
||||
!accessors.length ||
|
||||
!data.tables[layerId] ||
|
||||
data.tables[layerId].rows.length === 0 ||
|
||||
(xAccessor &&
|
||||
data.tables[layerId].rows.every((row) => typeof row[xAccessor] === 'undefined')) ||
|
||||
// stacked percentage bars have no xAccessors but splitAccessor with undefined values in them when empty
|
||||
(!xAccessor &&
|
||||
splitAccessor &&
|
||||
data.tables[layerId].rows.every((row) => typeof row[splitAccessor] === 'undefined'))
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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 { partition } from 'lodash';
|
||||
import { Datatable } from '../../../../expressions';
|
||||
import type { DataLayerConfigResult } from '../../common';
|
||||
import { isStackedChart } from './state';
|
||||
|
||||
export function computeOverallDataDomain(
|
||||
dataLayers: DataLayerConfigResult[],
|
||||
accessorIds: string[],
|
||||
activeData: Record<string, Datatable>,
|
||||
allowStacking: boolean = true
|
||||
) {
|
||||
const accessorMap = new Set(accessorIds);
|
||||
let min: number | undefined;
|
||||
let max: number | undefined;
|
||||
const [stacked, unstacked] = partition(
|
||||
dataLayers,
|
||||
({ seriesType }) => isStackedChart(seriesType) && allowStacking
|
||||
);
|
||||
for (const { layerId, accessors } of unstacked) {
|
||||
const table = activeData[layerId];
|
||||
if (table) {
|
||||
for (const accessor of accessors) {
|
||||
if (accessorMap.has(accessor)) {
|
||||
for (const row of table.rows) {
|
||||
const value = row[accessor];
|
||||
if (typeof value === 'number') {
|
||||
// when not stacked, do not keep the 0
|
||||
max = max != null ? Math.max(value, max) : value;
|
||||
min = min != null ? Math.min(value, min) : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// stacked can span multiple layers, so compute an overall max/min by bucket
|
||||
const stackedResults: Record<string, number> = {};
|
||||
for (const { layerId, accessors, xAccessor } of stacked) {
|
||||
const table = activeData[layerId];
|
||||
if (table) {
|
||||
for (const accessor of accessors) {
|
||||
if (accessorMap.has(accessor)) {
|
||||
for (const row of table.rows) {
|
||||
const value = row[accessor];
|
||||
// start with a shared bucket
|
||||
let bucket = 'shared';
|
||||
// but if there's an xAccessor use it as new bucket system
|
||||
if (xAccessor) {
|
||||
bucket = row[xAccessor];
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
stackedResults[bucket] = stackedResults[bucket] ?? 0;
|
||||
stackedResults[bucket] += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of Object.values(stackedResults)) {
|
||||
// for stacked extents keep 0 in view
|
||||
max = Math.max(value, max || 0, 0);
|
||||
min = Math.min(value, min || 0, 0);
|
||||
}
|
||||
|
||||
return { min, max };
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 type { SeriesType, XYLayerConfigResult, YConfig } from '../../common';
|
||||
import { getDataLayers, isAnnotationsLayer, isDataLayer } from './visualization';
|
||||
|
||||
export function isHorizontalSeries(seriesType: SeriesType) {
|
||||
return (
|
||||
seriesType === 'bar_horizontal' ||
|
||||
seriesType === 'bar_horizontal_stacked' ||
|
||||
seriesType === 'bar_horizontal_percentage_stacked'
|
||||
);
|
||||
}
|
||||
|
||||
export function isStackedChart(seriesType: SeriesType) {
|
||||
return seriesType.includes('stacked');
|
||||
}
|
||||
|
||||
export function isHorizontalChart(layers: XYLayerConfigResult[]) {
|
||||
return getDataLayers(layers).every((l) => isHorizontalSeries(l.seriesType));
|
||||
}
|
||||
|
||||
export const getSeriesColor = (layer: XYLayerConfigResult, accessor: string) => {
|
||||
if ((isDataLayer(layer) && layer.splitAccessor) || isAnnotationsLayer(layer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null
|
||||
);
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 {
|
||||
DataLayerConfigResult,
|
||||
ReferenceLineLayerConfigResult,
|
||||
XYLayerConfigResult,
|
||||
AnnotationLayerConfigResult,
|
||||
} from '../../common/types';
|
||||
import { LayerTypes } from '../../common/constants';
|
||||
|
||||
export const isDataLayer = (layer: XYLayerConfigResult): layer is DataLayerConfigResult =>
|
||||
layer.layerType === LayerTypes.DATA || !layer.layerType;
|
||||
|
||||
export const getDataLayers = (layers: XYLayerConfigResult[]) =>
|
||||
(layers || []).filter((layer): layer is DataLayerConfigResult => isDataLayer(layer));
|
||||
|
||||
export const isReferenceLayer = (
|
||||
layer: XYLayerConfigResult
|
||||
): layer is ReferenceLineLayerConfigResult => layer.layerType === LayerTypes.REFERENCELINE;
|
||||
|
||||
export const getReferenceLayers = (layers: XYLayerConfigResult[]) =>
|
||||
(layers || []).filter((layer): layer is ReferenceLineLayerConfigResult =>
|
||||
isReferenceLayer(layer)
|
||||
);
|
||||
|
||||
const isAnnotationLayerCommon = (
|
||||
layer: XYLayerConfigResult
|
||||
): layer is AnnotationLayerConfigResult => layer.layerType === LayerTypes.ANNOTATIONS;
|
||||
|
||||
export const isAnnotationsLayer = (
|
||||
layer: XYLayerConfigResult
|
||||
): layer is AnnotationLayerConfigResult => isAnnotationLayerCommon(layer);
|
||||
|
||||
export const getAnnotationsLayers = (
|
||||
layers: XYLayerConfigResult[]
|
||||
): AnnotationLayerConfigResult[] =>
|
||||
(layers || []).filter((layer): layer is AnnotationLayerConfigResult => isAnnotationsLayer(layer));
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const AreaIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M30 6v15a1 1 0 01-1 1H1a1 1 0 01-1-1v-2c1 0 3.5-4 6-4s5 3 6 3 3.23-6.994 5.865-6.997C20.5 11 23 11 24 11s3-5 6-5z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
<path
|
||||
d="M6 1c3 0 5 6 6 6s3.5-3 6-3c1.667 0 2.944 2.333 3.833 6.999l.309.001c-1.013 0-2.27 0-3.593.002h-.684C15.231 11.007 13 18 12 18s-3.5-3-6-3-5 4-6 4V7c1-1.5 3-6 6-6z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const AreaPercentageIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M0 13v8a1 1 0 001 1h28a1 1 0 001-1V9.25c-1.251-.929-2.45-1.734-3.493-2.313a11.028 11.028 0 00-1.478-.703C24.592 6.072 24.25 6 24 6c-.262 0-.63.212-1.126.77-.472.53-.952 1.249-1.458 2.007l-.013.02c-.49.736-1.006 1.51-1.53 2.098C19.37 11.462 18.739 12 18 12c-1.062 0-2.112-.263-3.092-.508l-.03-.007C13.869 11.232 12.929 11 12 11c-.337 0-.729.171-1.2.525-.466.35-.94.822-1.446 1.329l-.015.015c-.49.489-1.01 1.01-1.539 1.406-.529.396-1.137.725-1.8.725-.657 0-1.57-.212-2.48-.424l-.058-.014C2.275 14.287 1.032 14 0 14v-1z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
<path
|
||||
d="M29 0a1 1 0 011 1v6.012c-1.06-.764-2.085-1.437-3.007-1.95a11.93 11.93 0 00-1.616-.765C24.887 4.115 24.418 4 24 4c-.738 0-1.369.538-1.874 1.105-.523.589-1.039 1.362-1.529 2.098l-.013.02c-.506.758-.985 1.476-1.458 2.007-.495.558-.864.77-1.126.77-.928 0-1.867-.232-2.879-.485l-.029-.007C14.112 9.263 13.062 9 12 9c-.663 0-1.271.328-1.8.725-.528.396-1.05.917-1.538 1.406l-.015.015c-.507.507-.98.98-1.447 1.329-.471.354-.863.525-1.2.525-.528 0-1.328-.183-2.311-.412l-.034-.007C2.507 12.314 1.159 12 .001 12V1a1 1 0 011-1h28z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const AreaStackedIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={31}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M0 15.213l.484.091.813.146.762.129C3.779 15.859 5.09 16 6 16c.986 0 1.712-.25 3.166-.966l.281-.14C10.802 14.217 11.381 14 12 14c.507 0 .988.146 1.89.571l1.209.592c1.28.617 1.977.837 2.901.837 1.028 0 1.75-.349 3.119-1.344l.89-.659C23.034 13.252 23.535 13 24 13c.581 0 1.232.185 2.598.718l1.1.436.568.217c.72.27 1.256.438 1.736.532L30 21a1 1 0 01-1 1H1a1 1 0 01-1-1v-5.787z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
<path
|
||||
d="M24 1c1.334 0 3.334 1 6 3v8.842l-.324-.098c-.346-.11-.759-.262-1.273-.462l-1.101-.436-.568-.217-.536-.193C25.277 11.118 24.676 11 24 11c-1.028 0-1.75.349-3.119 1.344l-.89.659c-1.024.745-1.524.997-1.99.997-.508 0-.989-.146-1.89-.571l-1.21-.592c-1.28-.617-1.977-.837-2.9-.837-.987 0-1.713.25-3.167.966l-.281.14C7.198 13.783 6.619 14 6 14l-.334-.007c-1.182-.045-3.08-.317-5.665-.815V9c2 0 4.666 1 6 1 2 0 4-4 6-4s4 1 6 1 4-6 6-6z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const BarIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M5 7a1 1 0 011 1v13a1 1 0 01-1 1H1a1 1 0 01-1-1V8a1 1 0 011-1h4zm16-7a1 1 0 011 1v20a1 1 0 01-1 1h-4a1 1 0 01-1-1V1a1 1 0 011-1h4z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
<path
|
||||
d="M13 11a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1v-9a1 1 0 011-1h4zm16-7a1 1 0 011 1v16a1 1 0 01-1 1h-4a1 1 0 01-1-1V5a1 1 0 011-1h4z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const BarHorizontalIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M29 16a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1v-4a1 1 0 011-1h28zM22 0a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V1a1 1 0 011-1h21z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
<path
|
||||
d="M0 9a1 1 0 011-1h15a1 1 0 011 1v4a1 1 0 01-1 1H1a1 1 0 01-1-1V9z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const BarHorizontalPercentageIcon = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M20 16v6H1a1 1 0 01-1-1v-4a1 1 0 011-1h19zm-3-8v6H1.222C.547 14 0 13.552 0 13V9c0-.552.547-1 1.222-1H17zm1-8v6H1.042C.466 6 0 5.552 0 5V1c0-.552.466-1 1.042-1H18z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
<path
|
||||
d="M29 16a1 1 0 011 1v4a1 1 0 01-1 1h-7v-6h7zm-.222-8C29.453 8 30 8.448 30 9v4c0 .552-.547 1-1.222 1H19V8h9.778zm.18-8C29.534 0 30 .448 30 1v4c0 .552-.466 1-1.042 1H20V0h8.958z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const BarHorizontalStackedIcon = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M18 16v6H1a1 1 0 01-1-1v-4a1 1 0 011-1h17zm-3-8v6H1.222C.547 14 0 13.552 0 13V9c0-.552.547-1 1.222-1H15zm1-8v6H1.042C.466 6 0 5.552 0 5V1c0-.552.466-1 1.042-1H16z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
<path
|
||||
d="M29 16a1 1 0 011 1v4a1 1 0 01-1 1h-9v-6h9zm-9.222-8C20.453 8 21 8.448 21 9v4c0 .552-.547 1-1.222 1H17V8h2.778zm3.18-8C23.534 0 24 .448 24 1v4c0 .552-.466 1-1.042 1H18V0h4.958z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const BarPercentageIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M6 13v8a1 1 0 01-1 1H1a1 1 0 01-1-1v-8h6zm8-4v12a1 1 0 01-1 1H9a1 1 0 01-1-1V9h6zm8 4v8a1 1 0 01-1 1h-4a1 1 0 01-1-1v-8h6zm8 1v7a1 1 0 01-1 1h-4a1 1 0 01-1-1v-7h6z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
<path
|
||||
d="M29 0a1 1 0 011 1v11h-6V1a1 1 0 011-1h4zM5 0a1 1 0 011 1v10H0V1a1 1 0 011-1h4zm16 0a1 1 0 011 1v10h-6V1a1 1 0 011-1h4zm-8 0a1 1 0 011 1v6H8V1a1 1 0 011-1h4z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const BarReferenceLineIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 16 12"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<g>
|
||||
<path
|
||||
className="lensChartIcon__subdued"
|
||||
d="M3.2 4.79997C3.2 4.50542 2.96122 4.26663 2.66667 4.26663H0.533333C0.238784 4.26663 0 4.50542 0 4.79997V6.39997H3.2V4.79997ZM3.2 9.59997H0V13.3333C0 13.6279 0.238784 13.8666 0.533333 13.8666H2.66667C2.96122 13.8666 3.2 13.6279 3.2 13.3333V9.59997ZM8.53333 9.59997H11.7333V13.3333C11.7333 13.6279 11.4946 13.8666 11.2 13.8666H9.06667C8.77211 13.8666 8.53333 13.6279 8.53333 13.3333V9.59997ZM11.7333 6.39997H8.53333V2.66663C8.53333 2.37208 8.77211 2.1333 9.06667 2.1333H11.2C11.4946 2.1333 11.7333 2.37208 11.7333 2.66663V6.39997ZM12.8 9.59997V13.3333C12.8 13.6279 13.0388 13.8666 13.3333 13.8666H15.4667C15.7612 13.8666 16 13.6279 16 13.3333V9.59997H12.8ZM16 6.39997V5.86663C16 5.57208 15.7612 5.3333 15.4667 5.3333H13.3333C13.0388 5.3333 12.8 5.57208 12.8 5.86663V6.39997H16ZM7.46667 11.2C7.46667 10.9054 7.22789 10.6666 6.93333 10.6666H4.8C4.50544 10.6666 4.26667 10.9054 4.26667 11.2V13.3333C4.26667 13.6279 4.50544 13.8666 4.8 13.8666H6.93333C7.22789 13.8666 7.46667 13.6279 7.46667 13.3333V11.2Z"
|
||||
/>
|
||||
<rect
|
||||
y="7.4668"
|
||||
width="16"
|
||||
height="1.06667"
|
||||
rx="0.533334"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const BarStackedIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
className="lensChartIcon__subdued"
|
||||
d="M6 13v8a1 1 0 01-1 1H1a1 1 0 01-1-1v-8h6zm8-4v12a1 1 0 01-1 1H9a1 1 0 01-1-1V9h6zm8 4v8a1 1 0 01-1 1h-4a1 1 0 01-1-1v-8h6zm8 1v7a1 1 0 01-1 1h-4a1 1 0 01-1-1v-7h6z"
|
||||
/>
|
||||
<path
|
||||
d="M29 1a1 1 0 011 1v10h-6V2a1 1 0 011-1h4zM5 7a1 1 0 011 1v3H0V8a1 1 0 011-1h4zm16-4a1 1 0 011 1v7h-6V4a1 1 0 011-1h4zm-8-3a1 1 0 011 1v6H8V1a1 1 0 011-1h4z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export const CircleIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<circle
|
||||
strokeWidth="1"
|
||||
stroke="currentColor"
|
||||
cx="8"
|
||||
cy="8"
|
||||
className={classnames('lensAnnotationIconNoFill', props.className)}
|
||||
r="7"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { BarHorizontalPercentageIcon } from './bar_horizontal_percentage';
|
||||
export { BarHorizontalStackedIcon } from './bar_horizontal_stacked';
|
||||
export { BarReferenceLineIcon } from './bar_reference_line';
|
||||
export { AreaPercentageIcon } from './area_percentage';
|
||||
export { BarHorizontalIcon } from './bar_horizontal';
|
||||
export { BarPercentageIcon } from './bar_percentage';
|
||||
export { AreaStackedIcon } from './area_stacked';
|
||||
export { BarStackedIcon } from './bar_stacked';
|
||||
export { TriangleIcon } from './triangle';
|
||||
export { MixedXyIcon } from './mixed_xy';
|
||||
export { CircleIcon } from './circle';
|
||||
export { AreaIcon } from './area';
|
||||
export { LineIcon } from './line';
|
||||
export { BarIcon } from './bar';
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const LineIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M23.434 16.456c.211.553.406.982.58 1.277l.073.119c.038.058.072.105.101.141.52-.04 1.07-.248 2.13-.77l.235-.117C28.198 16.283 28.953 16 30 16v2c-.507 0-.988.146-1.89.571l-1.209.592C25.621 19.78 24.924 20 24 20l-.174-.005c-1.251-.076-1.805-1.036-2.994-4.993L22.93 15c.18.563.335 1.012.505 1.455zM6 1c1.269 0 1.966.69 3.492 2.939l.774 1.146.272.387.26.358C11.419 6.658 11.788 7 12 7c.466 0 .967-.252 1.99-.997l.891-.659.458-.325C16.424 4.272 17.093 4 18 4c1.377 0 1.925.863 3.168 4.999L19.07 9a26.766 26.766 0 00-.505-1.457c-.24-.631-.46-1.1-.652-1.395l-.051-.074-.045-.06c-.452.064-.988.375-2.007 1.13l-.691.512-.458.325C13.576 8.728 12.907 9 12 9c-1.269 0-1.966-.69-3.492-2.939l-.774-1.146-.272-.387-.26-.358C6.581 3.342 6.213 3 6 3c-.294 0-.885.651-2.017 2.33l-.491.731-.326.475C1.859 8.409 1.175 9 0 9V7c.294 0 .885-.651 2.017-2.33l.491-.731.326-.475C4.141 1.591 4.825 1 6 1z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
<path
|
||||
d="M0 21c1.123 0 1.852-.477 3.295-1.885l.758-.75.345-.33C5.208 17.275 5.648 17 6 17c.466 0 .967.252 1.99.997l.891.659.458.325C10.424 19.728 11.093 20 12 20c1.325 0 1.996-.772 3.546-3.444l.593-1.028.385-.646C17.328 13.562 17.796 13 18 13h6c1.333 0 1.978-.795 3.452-3.676l.692-1.37.358-.69C29.333 5.7 29.831 5 30 5V3c-1.333 0-1.978.795-3.452 3.676l-.692 1.37-.358.69C24.667 10.3 24.169 11 24 11h-6c-1.325 0-1.996.772-3.546 3.444l-.593 1.028-.385.646C12.672 17.438 12.204 18 12 18c-.466 0-.967-.252-1.99-.997l-.891-.659-.458-.325C7.576 15.272 6.907 15 6 15c-1.123 0-1.852.477-3.295 1.885l-.758.75-.345.33C.792 18.725.352 19 0 19v2z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
|
||||
export const MixedXyIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
viewBox="0 0 30 22"
|
||||
width={30}
|
||||
height={22}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M24 16l.186-.002.345-.02.266-.03.334-.058.193-.043.25-.067.223-.07.215-.074.209-.079.305-.124c.166-.07.34-.148.524-.234l.285-.135 1.202-.588c.428-.203.728-.326.966-.396.146-.044.27-.067.384-.076L30 14v7a1 1 0 01-1 1H1a1 1 0 01-1-1v-2c1 0 3.5-4 6-4s5 3 6 3 3.23-7.994 5.865-7.997L19.032 10l.541 1.205.272.595.29.612c.517 1.069.955 1.842 1.391 2.391.08.1.16.194.241.28.095.101.189.191.283.272l.143.114c.218.164.446.284.69.368.207.071.426.116.662.14l.14.012.154.008L24 16zm6-12v6l-.186.002-.345.02-.266.03-.331.057-.196.044-.25.067-.304.097-.134.047a9.492 9.492 0 00-.386.15l-.128.053c-.166.07-.34.148-.524.234l-.285.135-1.264.618-.352.159-.187.078-.256.095-.178.054a13.872 13.872 0 01-.38-.687l-.16-.311-.233-.47-.016-.472H24c1 0 3-6 6-6z"
|
||||
className="lensChartIcon__accent"
|
||||
/>
|
||||
<path
|
||||
d="M30 13c-.507 0-.988.146-1.89.571l-1.209.592C25.621 14.78 24.924 15 24 15c-1.384 0-2.003-.865-3.516-4.206l-.637-1.42-.346-.749-.213-.445C18.572 6.698 18.127 6 18 6c-.466 0-.967.252-1.99.997l-.891.659-.458.325C13.576 8.728 12.907 9 12 9c-1.269 0-1.966-.69-3.492-2.939l-.774-1.146-.272-.387-.26-.358C6.581 3.342 6.213 3 6 3c-.294 0-.885.651-2.017 2.33l-.491.731-.326.475C1.859 8.409 1.175 9 0 9V7c.294 0 .885-.651 2.017-2.33l.491-.731.326-.475C4.141 1.591 4.825 1 6 1c1.269 0 1.966.69 3.492 2.939l.774 1.146.272.387.26.358C11.419 6.658 11.788 7 12 7c.466 0 .967-.252 1.99-.997l.891-.659.458-.325C16.424 4.272 17.093 4 18 4c1.384 0 2.003.865 3.516 4.206l.637 1.42.346.749.213.445C23.428 12.302 23.873 13 24 13c.507 0 .988-.146 1.89-.571l1.209-.592C28.379 11.22 29.076 11 30 11v2z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
<path
|
||||
d="M6 13v7.889C6 21.503 5.552 22 5 22H1c-.552 0-1-.497-1-1.111V13a1 1 0 011-1h4a1 1 0 011 1zm8-1v9a1 1 0 01-1 1H9a1 1 0 01-1-1v-9a1 1 0 011-1h4a1 1 0 011 1zm8 5v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4a1 1 0 011-1h4a1 1 0 011 1zm8 2v2a1 1 0 01-1 1h-4a1 1 0 01-1-1v-2a1 1 0 011-1h4a1 1 0 011 1z"
|
||||
className="lensChartIcon__subdued"
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiIconProps } from '@elastic/eui';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export const TriangleIcon = ({ title, titleId, ...props }: Omit<EuiIconProps, 'type'>) => (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
strokeWidth="1"
|
||||
stroke="currentColor"
|
||||
d="M 6.9 11.612 C 7.64533 12.7953 8.39067 12.7953 9.136 11.612 L 13.11 5.3 C 13.8553 4.11667 13.4827 3.525 11.992 3.525 L 4.044 3.525 C 2.55333 3.525 2.18067 4.11667 2.926 5.3 Z"
|
||||
className={classnames('lensAnnotationIconNoFill', props.className)}
|
||||
/>
|
||||
</svg>
|
||||
);
|
17
src/plugins/chart_expressions/expression_xy/public/index.ts
Executable file
17
src/plugins/chart_expressions/expression_xy/public/index.ts
Executable file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { ExpressionXyPlugin } from './plugin';
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
export function plugin() {
|
||||
return new ExpressionXyPlugin();
|
||||
}
|
||||
|
||||
export type { ExpressionXyPluginSetup, ExpressionXyPluginStart } from './types';
|
99
src/plugins/chart_expressions/expression_xy/public/plugin.ts
Executable file
99
src/plugins/chart_expressions/expression_xy/public/plugin.ts
Executable file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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 moment from 'moment';
|
||||
import { LEGACY_TIME_AXIS } from '../../../charts/common';
|
||||
import { DataPublicPluginStart } from '../../../data/public';
|
||||
import { FieldFormatsStart } from '../../../field_formats/public';
|
||||
import { ChartsPluginStart } from '../../../charts/public';
|
||||
import { CoreSetup, CoreStart, IUiSettingsClient } from '../../../../core/public';
|
||||
import { ExpressionXyPluginSetup, ExpressionXyPluginStart, SetupDeps } from './types';
|
||||
import {
|
||||
xyVisFunction,
|
||||
yAxisConfigFunction,
|
||||
legendConfigFunction,
|
||||
gridlinesConfigFunction,
|
||||
dataLayerConfigFunction,
|
||||
axisExtentConfigFunction,
|
||||
tickLabelsConfigFunction,
|
||||
annotationLayerConfigFunction,
|
||||
labelsOrientationConfigFunction,
|
||||
referenceLineLayerConfigFunction,
|
||||
axisTitlesVisibilityConfigFunction,
|
||||
} from '../common';
|
||||
import { GetStartDepsFn, getXyChartRenderer } from './expression_renderers';
|
||||
import { EventAnnotationPluginSetup } from '../../../event_annotation/public';
|
||||
|
||||
export interface XYPluginStartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
charts: ChartsPluginStart;
|
||||
eventAnnotation: EventAnnotationPluginSetup;
|
||||
}
|
||||
|
||||
export function getTimeZone(uiSettings: IUiSettingsClient) {
|
||||
const configuredTimeZone = uiSettings.get('dateFormat:tz');
|
||||
if (configuredTimeZone === 'Browser') {
|
||||
return moment.tz.guess();
|
||||
}
|
||||
|
||||
return configuredTimeZone;
|
||||
}
|
||||
|
||||
export class ExpressionXyPlugin {
|
||||
public setup(
|
||||
core: CoreSetup<XYPluginStartDependencies>,
|
||||
{ expressions, charts }: SetupDeps
|
||||
): ExpressionXyPluginSetup {
|
||||
expressions.registerFunction(yAxisConfigFunction);
|
||||
expressions.registerFunction(legendConfigFunction);
|
||||
expressions.registerFunction(gridlinesConfigFunction);
|
||||
expressions.registerFunction(dataLayerConfigFunction);
|
||||
expressions.registerFunction(axisExtentConfigFunction);
|
||||
expressions.registerFunction(tickLabelsConfigFunction);
|
||||
expressions.registerFunction(annotationLayerConfigFunction);
|
||||
expressions.registerFunction(labelsOrientationConfigFunction);
|
||||
expressions.registerFunction(referenceLineLayerConfigFunction);
|
||||
expressions.registerFunction(axisTitlesVisibilityConfigFunction);
|
||||
expressions.registerFunction(xyVisFunction);
|
||||
|
||||
const getStartDeps: GetStartDepsFn = async () => {
|
||||
const [coreStart, deps] = await core.getStartServices();
|
||||
const {
|
||||
data,
|
||||
fieldFormats,
|
||||
eventAnnotation,
|
||||
charts: { activeCursor, theme, palettes },
|
||||
} = deps;
|
||||
|
||||
const paletteService = await palettes.getPalettes();
|
||||
|
||||
const { theme: kibanaTheme } = coreStart;
|
||||
const eventAnnotationService = await eventAnnotation.getService();
|
||||
const useLegacyTimeAxis = core.uiSettings.get(LEGACY_TIME_AXIS);
|
||||
|
||||
return {
|
||||
data,
|
||||
formatFactory: fieldFormats.deserialize,
|
||||
kibanaTheme,
|
||||
theme,
|
||||
activeCursor,
|
||||
paletteService,
|
||||
useLegacyTimeAxis,
|
||||
eventAnnotationService,
|
||||
timeZone: getTimeZone(core.uiSettings),
|
||||
};
|
||||
};
|
||||
|
||||
expressions.registerRenderer(getXyChartRenderer({ getStartDeps }));
|
||||
}
|
||||
|
||||
public start(core: CoreStart): ExpressionXyPluginStart {}
|
||||
|
||||
public stop() {}
|
||||
}
|
116
src/plugins/chart_expressions/expression_xy/public/types.ts
Executable file
116
src/plugins/chart_expressions/expression_xy/public/types.ts
Executable file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 { IconType } from '@elastic/eui';
|
||||
import { DataPublicPluginSetup } from '../../../data/public';
|
||||
import { FieldFormatsSetup } from '../../../field_formats/public';
|
||||
import { ChartsPluginSetup } from '../../../charts/public';
|
||||
import { IFieldFormat, SerializedFieldFormat } from '../../../../plugins/field_formats/common';
|
||||
import type { RangeSelectContext, ValueClickContext } from '../../../../plugins/embeddable/public';
|
||||
import { ExpressionsServiceStart, ExpressionsSetup } from '../../../expressions/public';
|
||||
|
||||
export interface SetupDeps {
|
||||
expressions: ExpressionsSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
fieldFormats: FieldFormatsSetup;
|
||||
charts: ChartsPluginSetup;
|
||||
}
|
||||
|
||||
export interface StartDeps {
|
||||
expression: ExpressionsServiceStart;
|
||||
}
|
||||
|
||||
export type ExpressionXyPluginSetup = void;
|
||||
export type ExpressionXyPluginStart = void;
|
||||
|
||||
export interface FilterEvent {
|
||||
name: 'filter';
|
||||
data: ValueClickContext['data'];
|
||||
}
|
||||
|
||||
export interface BrushEvent {
|
||||
name: 'brush';
|
||||
data: RangeSelectContext['data'];
|
||||
}
|
||||
|
||||
export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
|
||||
|
||||
export interface OperationDescriptor extends Operation {
|
||||
hasTimeShift: boolean;
|
||||
}
|
||||
|
||||
export type SortingHint = 'version';
|
||||
export type FieldOnlyDataType = 'document' | 'ip' | 'histogram' | 'geo_point' | 'geo_shape';
|
||||
export type DataType = 'string' | 'number' | 'date' | 'boolean' | FieldOnlyDataType;
|
||||
|
||||
export interface OperationMetadata {
|
||||
// The output of this operation will have this data type
|
||||
dataType: DataType;
|
||||
// A bucketed operation is grouped by duplicate values, otherwise each row is
|
||||
// treated as unique
|
||||
isBucketed: boolean;
|
||||
/**
|
||||
* ordinal: Each name is a unique value, but the names are in sorted order, like "Top values"
|
||||
* interval: Histogram data, like date or number histograms
|
||||
* ratio: Most number data is rendered as a ratio that includes 0
|
||||
*/
|
||||
scale?: 'ordinal' | 'interval' | 'ratio';
|
||||
// Extra meta-information like cardinality, color
|
||||
// TODO currently it's not possible to differentiate between a field from a raw
|
||||
// document and an aggregated metric which might be handy in some cases. Once we
|
||||
// introduce a raw document datasource, this should be considered here.
|
||||
isStaticValue?: boolean;
|
||||
}
|
||||
|
||||
export interface Operation extends OperationMetadata {
|
||||
// User-facing label for the operation
|
||||
label: string;
|
||||
sortingHint?: SortingHint;
|
||||
}
|
||||
|
||||
/**
|
||||
* A visualization type advertised to the user in the chart switcher
|
||||
*/
|
||||
export interface VisualizationType {
|
||||
/**
|
||||
* Unique id of the visualization type within the visualization defining it
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Icon used in the chart switcher
|
||||
*/
|
||||
icon: IconType;
|
||||
/**
|
||||
* Visible label used in the chart switcher and above the workspace panel in collapsed state
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Optional label used in visualization type search if chart switcher is expanded and for tooltips
|
||||
*/
|
||||
fullLabel?: string;
|
||||
/**
|
||||
* The group the visualization belongs to
|
||||
*/
|
||||
groupLabel: string;
|
||||
/**
|
||||
* The priority of the visualization in the list (global priority)
|
||||
* Higher number means higher priority. When omitted defaults to 0
|
||||
*/
|
||||
sortPriority?: number;
|
||||
/**
|
||||
* Indicates if visualization is in the experimental stage.
|
||||
*/
|
||||
showExperimentalBadge?: boolean;
|
||||
}
|
||||
|
||||
export interface AccessorConfig {
|
||||
columnId: string;
|
||||
triggerIcon?: 'color' | 'disabled' | 'colorBy' | 'none' | 'invisible';
|
||||
color?: string;
|
||||
palette?: string[] | Array<{ color: string; stop: number }>;
|
||||
}
|
15
src/plugins/chart_expressions/expression_xy/server/index.ts
Executable file
15
src/plugins/chart_expressions/expression_xy/server/index.ts
Executable file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { ExpressionXyPlugin } from './plugin';
|
||||
|
||||
export function plugin() {
|
||||
return new ExpressionXyPlugin();
|
||||
}
|
||||
|
||||
export type { ExpressionXyPluginSetup, ExpressionXyPluginStart } from './types';
|
47
src/plugins/chart_expressions/expression_xy/server/plugin.ts
Executable file
47
src/plugins/chart_expressions/expression_xy/server/plugin.ts
Executable file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { CoreSetup, CoreStart, Plugin } from '../../../../core/server';
|
||||
|
||||
import { ExpressionXyPluginSetup, ExpressionXyPluginStart } from './types';
|
||||
import {
|
||||
xyVisFunction,
|
||||
yAxisConfigFunction,
|
||||
legendConfigFunction,
|
||||
gridlinesConfigFunction,
|
||||
dataLayerConfigFunction,
|
||||
axisExtentConfigFunction,
|
||||
tickLabelsConfigFunction,
|
||||
annotationLayerConfigFunction,
|
||||
labelsOrientationConfigFunction,
|
||||
referenceLineLayerConfigFunction,
|
||||
axisTitlesVisibilityConfigFunction,
|
||||
} from '../common';
|
||||
import { SetupDeps } from './types';
|
||||
|
||||
export class ExpressionXyPlugin
|
||||
implements Plugin<ExpressionXyPluginSetup, ExpressionXyPluginStart>
|
||||
{
|
||||
public setup(core: CoreSetup, { expressions }: SetupDeps) {
|
||||
expressions.registerFunction(yAxisConfigFunction);
|
||||
expressions.registerFunction(legendConfigFunction);
|
||||
expressions.registerFunction(gridlinesConfigFunction);
|
||||
expressions.registerFunction(dataLayerConfigFunction);
|
||||
expressions.registerFunction(axisExtentConfigFunction);
|
||||
expressions.registerFunction(tickLabelsConfigFunction);
|
||||
expressions.registerFunction(annotationLayerConfigFunction);
|
||||
expressions.registerFunction(labelsOrientationConfigFunction);
|
||||
expressions.registerFunction(referenceLineLayerConfigFunction);
|
||||
expressions.registerFunction(axisTitlesVisibilityConfigFunction);
|
||||
expressions.registerFunction(xyVisFunction);
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {}
|
||||
|
||||
public stop() {}
|
||||
}
|
20
src/plugins/chart_expressions/expression_xy/server/types.ts
Executable file
20
src/plugins/chart_expressions/expression_xy/server/types.ts
Executable file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { ExpressionsServerStart, ExpressionsServerSetup } from '../../../expressions/server';
|
||||
|
||||
export type ExpressionXyPluginSetup = void;
|
||||
export type ExpressionXyPluginStart = void;
|
||||
|
||||
export interface SetupDeps {
|
||||
expressions: ExpressionsServerSetup;
|
||||
}
|
||||
|
||||
export interface StartDeps {
|
||||
expression: ExpressionsServerStart;
|
||||
}
|
26
src/plugins/chart_expressions/expression_xy/tsconfig.json
Normal file
26
src/plugins/chart_expressions/expression_xy/tsconfig.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target/types",
|
||||
"emitDeclarationOnly": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"common/**/*",
|
||||
"public/**/*",
|
||||
"server/**/*",
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../charts/tsconfig.json" },
|
||||
{ "path": "../../../core/tsconfig.json" },
|
||||
{ "path": "../../expressions/tsconfig.json" },
|
||||
{ "path": "../../data/tsconfig.json"},
|
||||
{ "path": "../../ui_actions/tsconfig.json" },
|
||||
{ "path": "../../field_formats/tsconfig.json"},
|
||||
{ "path": "../../kibana_utils/tsconfig.json" },
|
||||
{ "path": "../../event_annotation/tsconfig.json" },
|
||||
{ "path": "../../visualizations/tsconfig.json" },
|
||||
]
|
||||
}
|
|
@ -10,22 +10,25 @@ import React from 'react';
|
|||
import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import './empty_placeholder.scss';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export const EmptyPlaceholder = ({
|
||||
icon,
|
||||
iconColor = 'subdued',
|
||||
message = <FormattedMessage id="charts.noDataLabel" defaultMessage="No results found" />,
|
||||
dataTestSubj = 'emptyPlaceholder',
|
||||
className,
|
||||
}: {
|
||||
icon: IconType;
|
||||
iconColor?: string;
|
||||
message?: JSX.Element;
|
||||
dataTestSubj?: string;
|
||||
className?: string;
|
||||
}) => (
|
||||
<>
|
||||
<EuiText
|
||||
data-test-subj={dataTestSubj}
|
||||
className="chart__empty-placeholder"
|
||||
className={classnames('chart__empty-placeholder', className)}
|
||||
textAlign="center"
|
||||
color="subdued"
|
||||
size="xs"
|
||||
|
|
|
@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await renderable.waitForRender();
|
||||
log.debug('Checking charts rendered');
|
||||
await elasticChart.waitForRenderComplete('lnsVisualizationContainer');
|
||||
await elasticChart.waitForRenderComplete('xyVisChart');
|
||||
log.debug('Checking saved searches rendered');
|
||||
await dashboardExpect.savedSearchRowCount(10);
|
||||
log.debug('Checking input controls rendered');
|
||||
|
|
|
@ -11,6 +11,5 @@ export * from './rename_columns';
|
|||
export * from './merge_tables';
|
||||
export * from './time_scale';
|
||||
export * from './datatable';
|
||||
export * from './xy_chart';
|
||||
|
||||
export * from './expression_types';
|
||||
|
|
|
@ -1,207 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type {
|
||||
ArgumentType,
|
||||
ExpressionFunctionDefinition,
|
||||
} from '../../../../../../src/plugins/expressions/common';
|
||||
|
||||
export interface AxesSettingsConfig {
|
||||
x: boolean;
|
||||
yLeft: boolean;
|
||||
yRight: boolean;
|
||||
}
|
||||
|
||||
export interface AxisExtentConfig {
|
||||
mode: 'full' | 'dataBounds' | 'custom';
|
||||
lowerBound?: number;
|
||||
upperBound?: number;
|
||||
}
|
||||
|
||||
interface AxisConfig {
|
||||
title: string;
|
||||
hide?: boolean;
|
||||
}
|
||||
|
||||
export type YAxisMode = 'auto' | 'left' | 'right' | 'bottom';
|
||||
export type LineStyle = 'solid' | 'dashed' | 'dotted';
|
||||
export type FillStyle = 'none' | 'above' | 'below';
|
||||
export type IconPosition = 'auto' | 'left' | 'right' | 'above' | 'below';
|
||||
|
||||
export interface YConfig {
|
||||
forAccessor: string;
|
||||
axisMode?: YAxisMode;
|
||||
color?: string;
|
||||
icon?: string;
|
||||
lineWidth?: number;
|
||||
lineStyle?: LineStyle;
|
||||
fill?: FillStyle;
|
||||
iconPosition?: IconPosition;
|
||||
textVisibility?: boolean;
|
||||
}
|
||||
|
||||
export type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & {
|
||||
type: 'lens_xy_axisTitlesVisibilityConfig';
|
||||
};
|
||||
|
||||
export const axisTitlesVisibilityConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_axisTitlesVisibilityConfig',
|
||||
null,
|
||||
AxesSettingsConfig,
|
||||
AxisTitlesVisibilityConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_axisTitlesVisibilityConfig',
|
||||
aliases: [],
|
||||
type: 'lens_xy_axisTitlesVisibilityConfig',
|
||||
help: `Configure the xy chart's axis titles appearance`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
x: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.xAxisTitle.help', {
|
||||
defaultMessage: 'Specifies whether or not the title of the x-axis are visible.',
|
||||
}),
|
||||
},
|
||||
yLeft: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yLeftAxisTitle.help', {
|
||||
defaultMessage: 'Specifies whether or not the title of the left y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
yRight: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yRightAxisTitle.help', {
|
||||
defaultMessage: 'Specifies whether or not the title of the right y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: AxesSettingsConfig) {
|
||||
return {
|
||||
type: 'lens_xy_axisTitlesVisibilityConfig',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export type AxisExtentConfigResult = AxisExtentConfig & { type: 'lens_xy_axisExtentConfig' };
|
||||
|
||||
export const axisExtentConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_axisExtentConfig',
|
||||
null,
|
||||
AxisExtentConfig,
|
||||
AxisExtentConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_axisExtentConfig',
|
||||
aliases: [],
|
||||
type: 'lens_xy_axisExtentConfig',
|
||||
help: `Configure the xy chart's axis extents`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
mode: {
|
||||
types: ['string'],
|
||||
options: ['full', 'dataBounds', 'custom'],
|
||||
help: i18n.translate('xpack.lens.xyChart.extentMode.help', {
|
||||
defaultMessage: 'The extent mode',
|
||||
}),
|
||||
},
|
||||
lowerBound: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('xpack.lens.xyChart.extentMode.help', {
|
||||
defaultMessage: 'The extent mode',
|
||||
}),
|
||||
},
|
||||
upperBound: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('xpack.lens.xyChart.extentMode.help', {
|
||||
defaultMessage: 'The extent mode',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: AxisExtentConfig) {
|
||||
return {
|
||||
type: 'lens_xy_axisExtentConfig',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const axisConfig: { [key in keyof AxisConfig]: ArgumentType<AxisConfig[key]> } = {
|
||||
title: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('xpack.lens.xyChart.title.help', {
|
||||
defaultMessage: 'The axis title',
|
||||
}),
|
||||
},
|
||||
hide: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: 'Show / hide axis',
|
||||
},
|
||||
};
|
||||
|
||||
export type YConfigResult = YConfig & { type: 'lens_xy_yConfig' };
|
||||
|
||||
export const yAxisConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_yConfig',
|
||||
null,
|
||||
YConfig,
|
||||
YConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_yConfig',
|
||||
aliases: [],
|
||||
type: 'lens_xy_yConfig',
|
||||
help: `Configure the behavior of a xy chart's y axis metric`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
forAccessor: {
|
||||
types: ['string'],
|
||||
help: 'The accessor this configuration is for',
|
||||
},
|
||||
axisMode: {
|
||||
types: ['string'],
|
||||
options: ['auto', 'left', 'right'],
|
||||
help: 'The axis mode of the metric',
|
||||
},
|
||||
color: {
|
||||
types: ['string'],
|
||||
help: 'The color of the series',
|
||||
},
|
||||
lineStyle: {
|
||||
types: ['string'],
|
||||
options: ['solid', 'dotted', 'dashed'],
|
||||
help: 'The style of the reference line',
|
||||
},
|
||||
lineWidth: {
|
||||
types: ['number'],
|
||||
help: 'The width of the reference line',
|
||||
},
|
||||
icon: {
|
||||
types: ['string'],
|
||||
help: 'An optional icon used for reference lines',
|
||||
},
|
||||
iconPosition: {
|
||||
types: ['string'],
|
||||
options: ['auto', 'above', 'below', 'left', 'right'],
|
||||
help: 'The placement of the icon for the reference line',
|
||||
},
|
||||
textVisibility: {
|
||||
types: ['boolean'],
|
||||
help: 'Visibility of the label on the reference line',
|
||||
},
|
||||
fill: {
|
||||
types: ['string'],
|
||||
options: ['none', 'above', 'below'],
|
||||
help: '',
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: YConfig) {
|
||||
return {
|
||||
type: 'lens_xy_yConfig',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './axis_config';
|
||||
export * from './fitting_function';
|
||||
export * from './end_value';
|
||||
export * from './grid_lines_config';
|
||||
export * from './layer_config';
|
||||
export * from './legend_config';
|
||||
export * from './series_type';
|
||||
export * from './tick_labels_config';
|
||||
export * from './xy_args';
|
||||
export * from './xy_chart';
|
||||
export * from './labels_orientation_config';
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
EventAnnotationConfig,
|
||||
EventAnnotationOutput,
|
||||
} from '../../../../../../../src/plugins/event_annotation/common';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../../src/plugins/expressions/common';
|
||||
import { layerTypes } from '../../../constants';
|
||||
|
||||
export interface XYAnnotationLayerConfig {
|
||||
layerId: string;
|
||||
layerType: typeof layerTypes.ANNOTATIONS;
|
||||
annotations: EventAnnotationConfig[];
|
||||
hide?: boolean;
|
||||
}
|
||||
|
||||
export interface AnnotationLayerArgs {
|
||||
annotations: EventAnnotationOutput[];
|
||||
layerId: string;
|
||||
layerType: typeof layerTypes.ANNOTATIONS;
|
||||
hide?: boolean;
|
||||
}
|
||||
export type XYAnnotationLayerArgsResult = AnnotationLayerArgs & {
|
||||
type: 'lens_xy_annotation_layer';
|
||||
};
|
||||
export function annotationLayerConfig(): ExpressionFunctionDefinition<
|
||||
'lens_xy_annotation_layer',
|
||||
null,
|
||||
AnnotationLayerArgs,
|
||||
XYAnnotationLayerArgsResult
|
||||
> {
|
||||
return {
|
||||
name: 'lens_xy_annotation_layer',
|
||||
aliases: [],
|
||||
type: 'lens_xy_annotation_layer',
|
||||
inputTypes: ['null'],
|
||||
help: 'Annotation layer in lens',
|
||||
args: {
|
||||
layerId: {
|
||||
types: ['string'],
|
||||
help: '',
|
||||
},
|
||||
layerType: { types: ['string'], options: [layerTypes.ANNOTATIONS], help: '' },
|
||||
hide: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: 'Show details',
|
||||
},
|
||||
annotations: {
|
||||
types: ['manual_event_annotation'],
|
||||
help: '',
|
||||
multi: true,
|
||||
},
|
||||
},
|
||||
fn: (input, args) => {
|
||||
return {
|
||||
type: 'lens_xy_annotation_layer',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { layerTypes } from '../../../constants';
|
||||
import type { PaletteOutput } from '../../../../../../../src/plugins/charts/common';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../../src/plugins/expressions/common';
|
||||
import { axisConfig, YConfig } from '../axis_config';
|
||||
import type { SeriesType } from '../series_type';
|
||||
|
||||
export interface XYDataLayerConfig {
|
||||
layerId: string;
|
||||
layerType: typeof layerTypes.DATA;
|
||||
accessors: string[];
|
||||
seriesType: SeriesType;
|
||||
xAccessor?: string;
|
||||
hide?: boolean;
|
||||
yConfig?: YConfig[];
|
||||
splitAccessor?: string;
|
||||
palette?: PaletteOutput;
|
||||
}
|
||||
export interface ValidLayer extends XYDataLayerConfig {
|
||||
xAccessor: NonNullable<XYDataLayerConfig['xAccessor']>;
|
||||
}
|
||||
|
||||
export type DataLayerArgs = XYDataLayerConfig & {
|
||||
columnToLabel?: string; // Actually a JSON key-value pair
|
||||
yScaleType: 'time' | 'linear' | 'log' | 'sqrt';
|
||||
xScaleType: 'time' | 'linear' | 'ordinal';
|
||||
isHistogram: boolean;
|
||||
// palette will always be set on the expression
|
||||
palette: PaletteOutput;
|
||||
};
|
||||
|
||||
export type DataLayerConfigResult = DataLayerArgs & { type: 'lens_xy_data_layer' };
|
||||
|
||||
export const dataLayerConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_data_layer',
|
||||
null,
|
||||
DataLayerArgs,
|
||||
DataLayerConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_data_layer',
|
||||
aliases: [],
|
||||
type: 'lens_xy_data_layer',
|
||||
help: `Configure a layer in the xy chart`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
...axisConfig,
|
||||
layerId: {
|
||||
types: ['string'],
|
||||
help: '',
|
||||
},
|
||||
xAccessor: {
|
||||
types: ['string'],
|
||||
help: '',
|
||||
},
|
||||
layerType: { types: ['string'], options: [layerTypes.DATA], help: '' },
|
||||
seriesType: {
|
||||
types: ['string'],
|
||||
options: [
|
||||
'bar',
|
||||
'line',
|
||||
'area',
|
||||
'bar_stacked',
|
||||
'area_stacked',
|
||||
'bar_percentage_stacked',
|
||||
'area_percentage_stacked',
|
||||
],
|
||||
help: 'The type of chart to display.',
|
||||
},
|
||||
xScaleType: {
|
||||
options: ['ordinal', 'linear', 'time'],
|
||||
help: 'The scale type of the x axis',
|
||||
default: 'ordinal',
|
||||
},
|
||||
isHistogram: {
|
||||
types: ['boolean'],
|
||||
default: false,
|
||||
help: 'Whether to layout the chart as a histogram',
|
||||
},
|
||||
yScaleType: {
|
||||
options: ['log', 'sqrt', 'linear', 'time'],
|
||||
help: 'The scale type of the y axes',
|
||||
default: 'linear',
|
||||
},
|
||||
splitAccessor: {
|
||||
types: ['string'],
|
||||
help: 'The column to split by',
|
||||
multi: false,
|
||||
},
|
||||
accessors: {
|
||||
types: ['string'],
|
||||
help: 'The columns to display on the y axis.',
|
||||
multi: true,
|
||||
},
|
||||
yConfig: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
types: ['lens_xy_yConfig' as any],
|
||||
help: 'Additional configuration for y axes',
|
||||
multi: true,
|
||||
},
|
||||
columnToLabel: {
|
||||
types: ['string'],
|
||||
help: 'JSON key-value pairs of column ID to label',
|
||||
},
|
||||
palette: {
|
||||
default: `{theme "palette" default={system_palette name="default"} }`,
|
||||
help: '',
|
||||
types: ['palette'],
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: DataLayerArgs) {
|
||||
return {
|
||||
type: 'lens_xy_data_layer',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { XYDataLayerConfig } from './data_layer_config';
|
||||
import { XYReferenceLineLayerConfig } from './reference_line_layer_config';
|
||||
import { XYAnnotationLayerConfig } from './annotation_layer_config';
|
||||
export * from './data_layer_config';
|
||||
export * from './reference_line_layer_config';
|
||||
export * from './annotation_layer_config';
|
||||
|
||||
export type XYLayerConfig =
|
||||
| XYDataLayerConfig
|
||||
| XYReferenceLineLayerConfig
|
||||
| XYAnnotationLayerConfig;
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../../src/plugins/expressions/common';
|
||||
import { layerTypes } from '../../../constants';
|
||||
import { YConfig } from '../axis_config';
|
||||
|
||||
export interface XYReferenceLineLayerConfig {
|
||||
layerId: string;
|
||||
layerType: typeof layerTypes.REFERENCELINE;
|
||||
accessors: string[];
|
||||
yConfig?: YConfig[];
|
||||
}
|
||||
export type ReferenceLineLayerArgs = XYReferenceLineLayerConfig & {
|
||||
columnToLabel?: string;
|
||||
};
|
||||
export type ReferenceLineLayerConfigResult = ReferenceLineLayerArgs & {
|
||||
type: 'lens_xy_referenceLine_layer';
|
||||
};
|
||||
|
||||
export const referenceLineLayerConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_referenceLine_layer',
|
||||
null,
|
||||
ReferenceLineLayerArgs,
|
||||
ReferenceLineLayerConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_referenceLine_layer',
|
||||
aliases: [],
|
||||
type: 'lens_xy_referenceLine_layer',
|
||||
help: `Configure a layer in the xy chart`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
layerId: {
|
||||
types: ['string'],
|
||||
help: '',
|
||||
},
|
||||
layerType: { types: ['string'], options: [layerTypes.REFERENCELINE], help: '' },
|
||||
accessors: {
|
||||
types: ['string'],
|
||||
help: 'The columns to display on the y axis.',
|
||||
multi: true,
|
||||
},
|
||||
yConfig: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
types: ['lens_xy_yConfig' as any],
|
||||
help: 'Additional configuration for y axes',
|
||||
multi: true,
|
||||
},
|
||||
columnToLabel: {
|
||||
types: ['string'],
|
||||
help: 'JSON key-value pairs of column ID to label',
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: ReferenceLineLayerArgs) {
|
||||
return {
|
||||
type: 'lens_xy_referenceLine_layer',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common';
|
||||
|
||||
export interface LegendConfig {
|
||||
/**
|
||||
* Flag whether the legend should be shown. If there is just a single series, it will be hidden
|
||||
*/
|
||||
isVisible: boolean;
|
||||
/**
|
||||
* Position of the legend relative to the chart
|
||||
*/
|
||||
position: Position;
|
||||
/**
|
||||
* Flag whether the legend should be shown even with just a single series
|
||||
*/
|
||||
showSingleSeries?: boolean;
|
||||
/**
|
||||
* Flag whether the legend is inside the chart
|
||||
*/
|
||||
isInside?: boolean;
|
||||
/**
|
||||
* Horizontal Alignment of the legend when it is set inside chart
|
||||
*/
|
||||
horizontalAlignment?: HorizontalAlignment;
|
||||
/**
|
||||
* Vertical Alignment of the legend when it is set inside chart
|
||||
*/
|
||||
verticalAlignment?: VerticalAlignment;
|
||||
/**
|
||||
* Number of columns when legend is set inside chart
|
||||
*/
|
||||
floatingColumns?: number;
|
||||
/**
|
||||
* Maximum number of lines per legend item
|
||||
*/
|
||||
maxLines?: number;
|
||||
/**
|
||||
* Flag whether the legend items are truncated or not
|
||||
*/
|
||||
shouldTruncate?: boolean;
|
||||
/**
|
||||
* Exact legend width (vertical) or height (horizontal)
|
||||
* Limited to max of 70% of the chart container dimension Vertical legends limited to min of 30% of computed width
|
||||
*/
|
||||
legendSize?: number;
|
||||
}
|
||||
|
||||
export type LegendConfigResult = LegendConfig & { type: 'lens_xy_legendConfig' };
|
||||
|
||||
export const legendConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_legendConfig',
|
||||
null,
|
||||
LegendConfig,
|
||||
LegendConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_legendConfig',
|
||||
aliases: [],
|
||||
type: 'lens_xy_legendConfig',
|
||||
help: `Configure the xy chart's legend`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
isVisible: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.isVisible.help', {
|
||||
defaultMessage: 'Specifies whether or not the legend is visible.',
|
||||
}),
|
||||
},
|
||||
position: {
|
||||
types: ['string'],
|
||||
options: [Position.Top, Position.Right, Position.Bottom, Position.Left],
|
||||
help: i18n.translate('xpack.lens.xyChart.position.help', {
|
||||
defaultMessage: 'Specifies the legend position.',
|
||||
}),
|
||||
},
|
||||
showSingleSeries: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.showSingleSeries.help', {
|
||||
defaultMessage: 'Specifies whether a legend with just a single entry should be shown',
|
||||
}),
|
||||
},
|
||||
isInside: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.isInside.help', {
|
||||
defaultMessage: 'Specifies whether a legend is inside the chart',
|
||||
}),
|
||||
},
|
||||
horizontalAlignment: {
|
||||
types: ['string'],
|
||||
options: [HorizontalAlignment.Right, HorizontalAlignment.Left],
|
||||
help: i18n.translate('xpack.lens.xyChart.horizontalAlignment.help', {
|
||||
defaultMessage:
|
||||
'Specifies the horizontal alignment of the legend when it is displayed inside chart.',
|
||||
}),
|
||||
},
|
||||
verticalAlignment: {
|
||||
types: ['string'],
|
||||
options: [VerticalAlignment.Top, VerticalAlignment.Bottom],
|
||||
help: i18n.translate('xpack.lens.xyChart.verticalAlignment.help', {
|
||||
defaultMessage:
|
||||
'Specifies the vertical alignment of the legend when it is displayed inside chart.',
|
||||
}),
|
||||
},
|
||||
floatingColumns: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('xpack.lens.xyChart.floatingColumns.help', {
|
||||
defaultMessage: 'Specifies the number of columns when legend is displayed inside chart.',
|
||||
}),
|
||||
},
|
||||
maxLines: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('xpack.lens.xyChart.maxLines.help', {
|
||||
defaultMessage: 'Specifies the number of lines per legend item.',
|
||||
}),
|
||||
},
|
||||
shouldTruncate: {
|
||||
types: ['boolean'],
|
||||
default: true,
|
||||
help: i18n.translate('xpack.lens.xyChart.shouldTruncate.help', {
|
||||
defaultMessage: 'Specifies whether the legend items will be truncated or not',
|
||||
}),
|
||||
},
|
||||
legendSize: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('xpack.lens.xyChart.legendSize.help', {
|
||||
defaultMessage: 'Specifies the legend size in pixels.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: LegendConfig) {
|
||||
return {
|
||||
type: 'lens_xy_legendConfig',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type SeriesType =
|
||||
| 'bar'
|
||||
| 'bar_horizontal'
|
||||
| 'line'
|
||||
| 'area'
|
||||
| 'bar_stacked'
|
||||
| 'bar_percentage_stacked'
|
||||
| 'bar_horizontal_stacked'
|
||||
| 'bar_horizontal_percentage_stacked'
|
||||
| 'area_stacked'
|
||||
| 'area_percentage_stacked';
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AxisExtentConfigResult, AxisTitlesVisibilityConfigResult } from './axis_config';
|
||||
import type { FittingFunction } from './fitting_function';
|
||||
import type { EndValue } from './end_value';
|
||||
import type { GridlinesConfigResult } from './grid_lines_config';
|
||||
import type { AnnotationLayerArgs, DataLayerArgs } from './layer_config';
|
||||
import type { LegendConfigResult } from './legend_config';
|
||||
import type { TickLabelsConfigResult } from './tick_labels_config';
|
||||
import type { LabelsOrientationConfigResult } from './labels_orientation_config';
|
||||
import type { ValueLabelConfig } from '../../types';
|
||||
|
||||
export type XYCurveType = 'LINEAR' | 'CURVE_MONOTONE_X';
|
||||
export type XYLayerArgs = DataLayerArgs | AnnotationLayerArgs;
|
||||
|
||||
// Arguments to XY chart expression, with computed properties
|
||||
export interface XYArgs {
|
||||
title?: string;
|
||||
description?: string;
|
||||
xTitle: string;
|
||||
yTitle: string;
|
||||
yRightTitle: string;
|
||||
yLeftExtent: AxisExtentConfigResult;
|
||||
yRightExtent: AxisExtentConfigResult;
|
||||
legend: LegendConfigResult;
|
||||
valueLabels: ValueLabelConfig;
|
||||
layers: XYLayerArgs[];
|
||||
fittingFunction?: FittingFunction;
|
||||
endValue?: EndValue;
|
||||
emphasizeFitting?: boolean;
|
||||
axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult;
|
||||
tickLabelsVisibilitySettings?: TickLabelsConfigResult;
|
||||
gridlinesVisibilitySettings?: GridlinesConfigResult;
|
||||
labelsOrientation?: LabelsOrientationConfigResult;
|
||||
curveType?: XYCurveType;
|
||||
fillOpacity?: number;
|
||||
hideEndzones?: boolean;
|
||||
valuesInLegend?: boolean;
|
||||
ariaLabel?: string;
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
"eventAnnotation"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"expressionXY",
|
||||
"usageCollection",
|
||||
"taskManager",
|
||||
"globalSearch",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue