[Lens] Multiple y axes (#69911)
This commit is contained in:
parent
c1dc53c6fb
commit
e1665e8b27
|
@ -186,7 +186,7 @@ export function LayerPanel(
|
|||
},
|
||||
];
|
||||
|
||||
if (activeVisualization.renderDimensionEditor) {
|
||||
if (activeVisualization.renderDimensionEditor && group.enableDimensionEditor) {
|
||||
tabs.push({
|
||||
id: 'visualization',
|
||||
name: i18n.translate('xpack.lens.editorFrame.formatStyleLabel', {
|
||||
|
|
|
@ -38,6 +38,7 @@ Object {
|
|||
"xScaleType": Array [
|
||||
"linear",
|
||||
],
|
||||
"yConfig": Array [],
|
||||
"yScaleType": Array [
|
||||
"linear",
|
||||
],
|
||||
|
|
|
@ -12,6 +12,11 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={Object {}}
|
||||
tooltip={
|
||||
Object {
|
||||
"headerFormatter": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="x"
|
||||
|
@ -21,11 +26,13 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
title="c"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="y"
|
||||
groupId="left"
|
||||
id="left"
|
||||
key="left"
|
||||
position="left"
|
||||
showGridLines={false}
|
||||
tickFormat={[Function]}
|
||||
title=""
|
||||
title="a"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
|
@ -45,8 +52,9 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
id="d"
|
||||
key="0"
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
|
@ -60,6 +68,43 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
yAccessors={
|
||||
Array [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": "I",
|
||||
"d": "Foo",
|
||||
},
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 5,
|
||||
"c": "J",
|
||||
"d": "Bar",
|
||||
},
|
||||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
"d",
|
||||
]
|
||||
}
|
||||
stackAccessors={Array []}
|
||||
timeZone="UTC"
|
||||
xAccessor="c"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={
|
||||
Array [
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
@ -80,6 +125,11 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={Object {}}
|
||||
tooltip={
|
||||
Object {
|
||||
"headerFormatter": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="x"
|
||||
|
@ -89,11 +139,13 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
|||
title="c"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="y"
|
||||
groupId="left"
|
||||
id="left"
|
||||
key="left"
|
||||
position="left"
|
||||
showGridLines={false}
|
||||
tickFormat={[Function]}
|
||||
title=""
|
||||
title="a"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
|
@ -113,8 +165,9 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
id="d"
|
||||
key="0"
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
|
@ -128,6 +181,43 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
|||
yAccessors={
|
||||
Array [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": "I",
|
||||
"d": "Foo",
|
||||
},
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 5,
|
||||
"c": "J",
|
||||
"d": "Bar",
|
||||
},
|
||||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
"d",
|
||||
]
|
||||
}
|
||||
stackAccessors={Array []}
|
||||
timeZone="UTC"
|
||||
xAccessor="c"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={
|
||||
Array [
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
@ -148,6 +238,11 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={Object {}}
|
||||
tooltip={
|
||||
Object {
|
||||
"headerFormatter": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="x"
|
||||
|
@ -157,11 +252,13 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
|||
title="c"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="y"
|
||||
groupId="left"
|
||||
id="left"
|
||||
key="left"
|
||||
position="bottom"
|
||||
showGridLines={false}
|
||||
tickFormat={[Function]}
|
||||
title=""
|
||||
title="a"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
|
@ -181,8 +278,9 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
id="d"
|
||||
key="0"
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
|
@ -196,6 +294,43 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
|||
yAccessors={
|
||||
Array [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": "I",
|
||||
"d": "Foo",
|
||||
},
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 5,
|
||||
"c": "J",
|
||||
"d": "Bar",
|
||||
},
|
||||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
"d",
|
||||
]
|
||||
}
|
||||
stackAccessors={Array []}
|
||||
timeZone="UTC"
|
||||
xAccessor="c"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={
|
||||
Array [
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
@ -216,6 +351,11 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={Object {}}
|
||||
tooltip={
|
||||
Object {
|
||||
"headerFormatter": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="x"
|
||||
|
@ -225,11 +365,13 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
title="c"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="y"
|
||||
groupId="left"
|
||||
id="left"
|
||||
key="left"
|
||||
position="left"
|
||||
showGridLines={false}
|
||||
tickFormat={[Function]}
|
||||
title=""
|
||||
title="a"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
|
@ -249,8 +391,9 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
id="d"
|
||||
key="0"
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
|
@ -264,6 +407,43 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
yAccessors={
|
||||
Array [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": "I",
|
||||
"d": "Foo",
|
||||
},
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 5,
|
||||
"c": "J",
|
||||
"d": "Bar",
|
||||
},
|
||||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
"d",
|
||||
]
|
||||
}
|
||||
stackAccessors={Array []}
|
||||
timeZone="UTC"
|
||||
xAccessor="c"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={
|
||||
Array [
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
@ -284,6 +464,11 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={Object {}}
|
||||
tooltip={
|
||||
Object {
|
||||
"headerFormatter": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="x"
|
||||
|
@ -293,11 +478,13 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
|
|||
title="c"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="y"
|
||||
groupId="left"
|
||||
id="left"
|
||||
key="left"
|
||||
position="left"
|
||||
showGridLines={false}
|
||||
tickFormat={[Function]}
|
||||
title=""
|
||||
title="a"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
|
@ -317,8 +504,9 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
id="d"
|
||||
key="0"
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
|
@ -336,6 +524,47 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
|
|||
yAccessors={
|
||||
Array [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": "I",
|
||||
"d": "Foo",
|
||||
},
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 5,
|
||||
"c": "J",
|
||||
"d": "Bar",
|
||||
},
|
||||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
"d",
|
||||
]
|
||||
}
|
||||
stackAccessors={
|
||||
Array [
|
||||
"c",
|
||||
]
|
||||
}
|
||||
timeZone="UTC"
|
||||
xAccessor="c"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={
|
||||
Array [
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
@ -356,6 +585,11 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={Object {}}
|
||||
tooltip={
|
||||
Object {
|
||||
"headerFormatter": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="x"
|
||||
|
@ -365,11 +599,13 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
|||
title="c"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="y"
|
||||
groupId="left"
|
||||
id="left"
|
||||
key="left"
|
||||
position="left"
|
||||
showGridLines={false}
|
||||
tickFormat={[Function]}
|
||||
title=""
|
||||
title="a"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
|
@ -389,8 +625,9 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
id="d"
|
||||
key="0"
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
|
@ -408,6 +645,47 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
|||
yAccessors={
|
||||
Array [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": "I",
|
||||
"d": "Foo",
|
||||
},
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 5,
|
||||
"c": "J",
|
||||
"d": "Bar",
|
||||
},
|
||||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
"d",
|
||||
]
|
||||
}
|
||||
stackAccessors={
|
||||
Array [
|
||||
"c",
|
||||
]
|
||||
}
|
||||
timeZone="UTC"
|
||||
xAccessor="c"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={
|
||||
Array [
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
@ -428,6 +706,11 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
|
|||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
theme={Object {}}
|
||||
tooltip={
|
||||
Object {
|
||||
"headerFormatter": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="x"
|
||||
|
@ -437,11 +720,13 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
|
|||
title="c"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
id="y"
|
||||
groupId="left"
|
||||
id="left"
|
||||
key="left"
|
||||
position="bottom"
|
||||
showGridLines={false}
|
||||
tickFormat={[Function]}
|
||||
title=""
|
||||
title="a"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
|
@ -461,8 +746,9 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
id="d"
|
||||
key="0"
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
|
@ -480,6 +766,47 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
|
|||
yAccessors={
|
||||
Array [
|
||||
"a",
|
||||
]
|
||||
}
|
||||
yScaleType="linear"
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": "I",
|
||||
"d": "Foo",
|
||||
},
|
||||
Object {
|
||||
"a": 1,
|
||||
"b": 5,
|
||||
"c": "J",
|
||||
"d": "Bar",
|
||||
},
|
||||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
name={[Function]}
|
||||
splitSeriesAccessors={
|
||||
Array [
|
||||
"d",
|
||||
]
|
||||
}
|
||||
stackAccessors={
|
||||
Array [
|
||||
"c",
|
||||
]
|
||||
}
|
||||
timeZone="UTC"
|
||||
xAccessor="c"
|
||||
xScaleType="ordinal"
|
||||
yAccessors={
|
||||
Array [
|
||||
"b",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { LayerArgs } from './types';
|
||||
import { KibanaDatatable } from '../../../../../src/plugins/expressions/public';
|
||||
import { getAxesConfiguration } from './axes_configuration';
|
||||
|
||||
describe('axes_configuration', () => {
|
||||
const tables: Record<string, KibanaDatatable> = {
|
||||
first: {
|
||||
type: 'kibana_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_histogram',
|
||||
indexPatternId: 'indexPatternId',
|
||||
aggConfigParams: {
|
||||
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: {},
|
||||
},
|
||||
},
|
||||
formatHint: { id: 'date', params: { pattern: 'HH:mm' } },
|
||||
},
|
||||
{
|
||||
id: 'splitAccessorId',
|
||||
name: 'Top values of category.keyword',
|
||||
meta: {
|
||||
type: 'terms',
|
||||
indexPatternId: 'indexPatternId',
|
||||
aggConfigParams: {
|
||||
field: 'category.keyword',
|
||||
orderBy: 'yAccessorId',
|
||||
order: 'desc',
|
||||
size: 3,
|
||||
otherBucket: false,
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucket: false,
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
formatHint: {
|
||||
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: 'count',
|
||||
indexPatternId: 'indexPatternId',
|
||||
aggConfigParams: {},
|
||||
},
|
||||
formatHint: { id: 'number' },
|
||||
},
|
||||
{
|
||||
id: 'yAccessorId2',
|
||||
name: 'Other column',
|
||||
meta: {
|
||||
type: 'average',
|
||||
indexPatternId: 'indexPatternId',
|
||||
aggConfigParams: {},
|
||||
},
|
||||
formatHint: { id: 'bytes' },
|
||||
},
|
||||
{
|
||||
id: 'yAccessorId3',
|
||||
name: 'Other column',
|
||||
meta: {
|
||||
type: 'average',
|
||||
indexPatternId: 'indexPatternId',
|
||||
aggConfigParams: {},
|
||||
},
|
||||
formatHint: { id: 'currency' },
|
||||
},
|
||||
{
|
||||
id: 'yAccessorId4',
|
||||
name: 'Other column',
|
||||
meta: {
|
||||
type: 'average',
|
||||
indexPatternId: 'indexPatternId',
|
||||
aggConfigParams: {},
|
||||
},
|
||||
formatHint: { id: 'currency' },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const sampleLayer: LayerArgs = {
|
||||
layerId: 'first',
|
||||
seriesType: 'line',
|
||||
xAccessor: 'c',
|
||||
accessors: ['yAccessorId'],
|
||||
splitAccessor: 'd',
|
||||
columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}',
|
||||
xScaleType: 'ordinal',
|
||||
yScaleType: 'linear',
|
||||
isHistogram: false,
|
||||
};
|
||||
|
||||
it('should map auto series to left axis', () => {
|
||||
const formatFactory = jest.fn();
|
||||
const groups = getAxesConfiguration([sampleLayer], tables, formatFactory, false);
|
||||
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], tables, formatFactory, false);
|
||||
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], tables, formatFactory, false);
|
||||
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: [{ forAccessor: 'yAccessorId', axisMode: 'right' }] }],
|
||||
tables,
|
||||
formatFactory,
|
||||
false
|
||||
);
|
||||
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: [{ forAccessor: 'yAccessorId', axisMode: 'right' }],
|
||||
},
|
||||
],
|
||||
tables,
|
||||
formatFactory,
|
||||
false
|
||||
);
|
||||
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: [{ forAccessor: 'yAccessorId', axisMode: 'right' }],
|
||||
},
|
||||
],
|
||||
tables,
|
||||
formatFactory,
|
||||
false
|
||||
);
|
||||
expect(formatFactory).toHaveBeenCalledTimes(2);
|
||||
expect(formatFactory).toHaveBeenCalledWith({ id: 'number' });
|
||||
expect(formatFactory).toHaveBeenCalledWith({ id: 'currency' });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { LayerConfig } from './types';
|
||||
import {
|
||||
KibanaDatatable,
|
||||
SerializedFieldFormat,
|
||||
} from '../../../../../src/plugins/expressions/public';
|
||||
import { IFieldFormat } from '../../../../../src/plugins/data/public';
|
||||
|
||||
interface FormattedMetric {
|
||||
layer: string;
|
||||
accessor: string;
|
||||
fieldFormat: SerializedFieldFormat;
|
||||
}
|
||||
|
||||
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 getAxesConfiguration(
|
||||
layers: LayerConfig[],
|
||||
tables: Record<string, KibanaDatatable>,
|
||||
formatFactory: (mapping: SerializedFieldFormat) => IFieldFormat,
|
||||
shouldRotate: boolean
|
||||
): GroupsConfiguration {
|
||||
const series: { auto: FormattedMetric[]; left: FormattedMetric[]; right: FormattedMetric[] } = {
|
||||
auto: [],
|
||||
left: [],
|
||||
right: [],
|
||||
};
|
||||
|
||||
layers.forEach((layer) => {
|
||||
const table = tables[layer.layerId];
|
||||
layer.accessors.forEach((accessor) => {
|
||||
const mode =
|
||||
layer.yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode ||
|
||||
'auto';
|
||||
const formatter: SerializedFieldFormat = table.columns.find(
|
||||
(column) => column.id === accessor
|
||||
)?.formatHint || { id: 'number' };
|
||||
series[mode].push({
|
||||
layer: layer.layerId,
|
||||
accessor,
|
||||
fieldFormat: formatter,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
series.auto.forEach((currentSeries) => {
|
||||
if (
|
||||
series.left.length === 0 ||
|
||||
series.left.every((leftSeries) =>
|
||||
isFormatterCompatible(leftSeries.fieldFormat, currentSeries.fieldFormat)
|
||||
)
|
||||
) {
|
||||
series.left.push(currentSeries);
|
||||
} else if (
|
||||
series.right.length === 0 ||
|
||||
series.right.every((rightSeries) =>
|
||||
isFormatterCompatible(rightSeries.fieldFormat, currentSeries.fieldFormat)
|
||||
)
|
||||
) {
|
||||
series.right.push(currentSeries);
|
||||
} else if (series.right.length >= series.left.length) {
|
||||
series.left.push(currentSeries);
|
||||
} else {
|
||||
series.right.push(currentSeries);
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
|
@ -11,7 +11,7 @@ import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'
|
|||
import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
|
||||
import { xyVisualization } from './xy_visualization';
|
||||
import { xyChart, getXyChartRenderer } from './xy_expression';
|
||||
import { legendConfig, xConfig, layerConfig } from './types';
|
||||
import { legendConfig, layerConfig, yAxisConfig } from './types';
|
||||
import { EditorFrameSetup, FormatFactory } from '../types';
|
||||
|
||||
export interface XyVisualizationPluginSetupPlugins {
|
||||
|
@ -37,7 +37,7 @@ export class XyVisualization {
|
|||
{ expressions, formatFactory, editorFrame }: XyVisualizationPluginSetupPlugins
|
||||
) {
|
||||
expressions.registerFunction(() => legendConfig);
|
||||
expressions.registerFunction(() => xConfig);
|
||||
expressions.registerFunction(() => yAxisConfig);
|
||||
expressions.registerFunction(() => layerConfig);
|
||||
expressions.registerFunction(() => xyChart);
|
||||
|
||||
|
|
|
@ -179,6 +179,21 @@ export const buildExpression = (
|
|||
],
|
||||
isHistogram: [isHistogramDimension],
|
||||
splitAccessor: layer.splitAccessor ? [layer.splitAccessor] : [],
|
||||
yConfig: layer.yConfig
|
||||
? layer.yConfig.map((yConfig) => ({
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'lens_xy_yConfig',
|
||||
arguments: {
|
||||
forAccessor: [yConfig.forAccessor],
|
||||
axisMode: [yConfig.axisMode],
|
||||
},
|
||||
},
|
||||
],
|
||||
}))
|
||||
: [],
|
||||
seriesType: [layer.seriesType],
|
||||
accessors: layer.accessors,
|
||||
columnToLabel: [JSON.stringify(columnToLabel)],
|
||||
|
|
|
@ -77,37 +77,33 @@ const axisConfig: { [key in keyof AxisConfig]: ArgumentType<AxisConfig[key]> } =
|
|||
},
|
||||
};
|
||||
|
||||
export interface YState extends AxisConfig {
|
||||
accessors: string[];
|
||||
}
|
||||
type YConfigResult = YConfig & { type: 'lens_xy_yConfig' };
|
||||
|
||||
export interface XConfig extends AxisConfig {
|
||||
accessor: string;
|
||||
}
|
||||
|
||||
type XConfigResult = XConfig & { type: 'lens_xy_xConfig' };
|
||||
|
||||
export const xConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_xConfig',
|
||||
export const yAxisConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_yConfig',
|
||||
null,
|
||||
XConfig,
|
||||
XConfigResult
|
||||
YConfig,
|
||||
YConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_xConfig',
|
||||
name: 'lens_xy_yConfig',
|
||||
aliases: [],
|
||||
type: 'lens_xy_xConfig',
|
||||
help: `Configure the xy chart's x axis`,
|
||||
type: 'lens_xy_yConfig',
|
||||
help: `Configure the behavior of a xy chart's y axis metric`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
...axisConfig,
|
||||
accessor: {
|
||||
forAccessor: {
|
||||
types: ['string'],
|
||||
help: 'The column to display on the x axis.',
|
||||
help: 'The accessor this configuration is for',
|
||||
},
|
||||
axisMode: {
|
||||
types: ['string'],
|
||||
options: ['auto', 'left', 'right'],
|
||||
help: 'The axis mode of the metric',
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: XConfig) {
|
||||
fn: function fn(input: unknown, args: YConfig) {
|
||||
return {
|
||||
type: 'lens_xy_xConfig',
|
||||
type: 'lens_xy_yConfig',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
|
@ -166,6 +162,12 @@ export const layerConfig: ExpressionFunctionDefinition<
|
|||
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',
|
||||
|
@ -188,11 +190,19 @@ export type SeriesType =
|
|||
| 'bar_horizontal_stacked'
|
||||
| 'area_stacked';
|
||||
|
||||
export type YAxisMode = 'auto' | 'left' | 'right';
|
||||
|
||||
export interface YConfig {
|
||||
forAccessor: string;
|
||||
axisMode?: YAxisMode;
|
||||
}
|
||||
|
||||
export interface LayerConfig {
|
||||
hide?: boolean;
|
||||
layerId: string;
|
||||
xAccessor?: string;
|
||||
accessors: string[];
|
||||
yConfig?: YConfig[];
|
||||
seriesType: SeriesType;
|
||||
splitAccessor?: string;
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
|
||||
import { State, SeriesType, visualizationTypes } from './types';
|
||||
import { VisualizationLayerWidgetProps } from '../types';
|
||||
import { EuiButtonGroup, EuiFormRow, htmlIdGenerator } from '@elastic/eui';
|
||||
import { State, SeriesType, visualizationTypes, YAxisMode } from './types';
|
||||
import { VisualizationDimensionEditorProps, VisualizationLayerWidgetProps } from '../types';
|
||||
import { isHorizontalChart, isHorizontalSeries } from './state_helpers';
|
||||
import { trackUiEvent } from '../lens_ui_telemetry';
|
||||
|
||||
|
@ -68,3 +67,73 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps<State>) {
|
|||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
const idPrefix = htmlIdGenerator()();
|
||||
|
||||
export function DimensionEditor({
|
||||
state,
|
||||
setState,
|
||||
layerId,
|
||||
accessor,
|
||||
}: VisualizationDimensionEditorProps<State>) {
|
||||
const index = state.layers.findIndex((l) => l.layerId === layerId);
|
||||
const layer = state.layers[index];
|
||||
const axisMode =
|
||||
(layer.yConfig &&
|
||||
layer.yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode) ||
|
||||
'auto';
|
||||
return (
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.xyChart.axisSide.label', {
|
||||
defaultMessage: 'Axis side',
|
||||
})}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
legend={i18n.translate('xpack.lens.xyChart.axisSide.label', {
|
||||
defaultMessage: 'Axis side',
|
||||
})}
|
||||
name="axisSide"
|
||||
buttonSize="compressed"
|
||||
className="eui-displayInlineBlock"
|
||||
options={[
|
||||
{
|
||||
id: `${idPrefix}auto`,
|
||||
label: i18n.translate('xpack.lens.xyChart.axisSide.auto', {
|
||||
defaultMessage: 'Auto',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: `${idPrefix}left`,
|
||||
label: i18n.translate('xpack.lens.xyChart.axisSide.left', {
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: `${idPrefix}right`,
|
||||
label: i18n.translate('xpack.lens.xyChart.axisSide.right', {
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
idSelected={`${idPrefix}${axisMode}`}
|
||||
onChange={(id) => {
|
||||
const newMode = id.replace(idPrefix, '') as YAxisMode;
|
||||
const newYAxisConfigs = [...(layer.yConfig || [])];
|
||||
const existingIndex = newYAxisConfigs.findIndex(
|
||||
(yAxisConfig) => yAxisConfig.forAccessor === accessor
|
||||
);
|
||||
if (existingIndex !== -1) {
|
||||
newYAxisConfigs[existingIndex].axisMode = newMode;
|
||||
} else {
|
||||
newYAxisConfigs.push({
|
||||
forAccessor: accessor,
|
||||
axisMode: newMode,
|
||||
});
|
||||
}
|
||||
setState(updateLayer(state, { ...layer, yConfig: newYAxisConfigs }, index));
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -280,6 +280,58 @@ describe('xy_expression', () => {
|
|||
let getFormatSpy: jest.Mock;
|
||||
let convertSpy: jest.Mock;
|
||||
|
||||
const dataWithoutFormats: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
first: {
|
||||
type: 'kibana_datatable',
|
||||
columns: [
|
||||
{ id: 'a', name: 'a' },
|
||||
{ id: 'b', name: 'b' },
|
||||
{ id: 'c', name: 'c' },
|
||||
{ id: 'd', name: 'd' },
|
||||
],
|
||||
rows: [
|
||||
{ a: 1, b: 2, c: 'I', d: 'Row 1' },
|
||||
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const dataWithFormats: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
first: {
|
||||
type: 'kibana_datatable',
|
||||
columns: [
|
||||
{ id: 'a', name: 'a' },
|
||||
{ id: 'b', name: 'b' },
|
||||
{ id: 'c', name: 'c' },
|
||||
{ id: 'd', name: 'd', formatHint: { id: 'custom' } },
|
||||
],
|
||||
rows: [
|
||||
{ a: 1, b: 2, c: 'I', d: 'Row 1' },
|
||||
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const getRenderedComponent = (data: LensMultiTable, args: XYArgs) => {
|
||||
return shallow(
|
||||
<XYChart
|
||||
data={data}
|
||||
args={args}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartTheme={{}}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
convertSpy = jest.fn((x) => x);
|
||||
getFormatSpy = jest.fn();
|
||||
|
@ -302,7 +354,9 @@ describe('xy_expression', () => {
|
|||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(component.find(LineSeries)).toHaveLength(1);
|
||||
expect(component.find(LineSeries)).toHaveLength(2);
|
||||
expect(component.find(LineSeries).at(0).prop('yAccessors')).toEqual(['a']);
|
||||
expect(component.find(LineSeries).at(1).prop('yAccessors')).toEqual(['b']);
|
||||
});
|
||||
|
||||
describe('date range', () => {
|
||||
|
@ -559,7 +613,9 @@ describe('xy_expression', () => {
|
|||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(component.find(BarSeries)).toHaveLength(1);
|
||||
expect(component.find(BarSeries)).toHaveLength(2);
|
||||
expect(component.find(BarSeries).at(0).prop('yAccessors')).toEqual(['a']);
|
||||
expect(component.find(BarSeries).at(1).prop('yAccessors')).toEqual(['b']);
|
||||
});
|
||||
|
||||
test('it renders area', () => {
|
||||
|
@ -577,7 +633,9 @@ describe('xy_expression', () => {
|
|||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(component.find(AreaSeries)).toHaveLength(1);
|
||||
expect(component.find(AreaSeries)).toHaveLength(2);
|
||||
expect(component.find(AreaSeries).at(0).prop('yAccessors')).toEqual(['a']);
|
||||
expect(component.find(AreaSeries).at(1).prop('yAccessors')).toEqual(['b']);
|
||||
});
|
||||
|
||||
test('it renders horizontal bar', () => {
|
||||
|
@ -595,7 +653,9 @@ describe('xy_expression', () => {
|
|||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(component.find(BarSeries)).toHaveLength(1);
|
||||
expect(component.find(BarSeries)).toHaveLength(2);
|
||||
expect(component.find(BarSeries).at(0).prop('yAccessors')).toEqual(['a']);
|
||||
expect(component.find(BarSeries).at(1).prop('yAccessors')).toEqual(['b']);
|
||||
expect(component.find(Settings).prop('rotation')).toEqual(90);
|
||||
});
|
||||
|
||||
|
@ -705,8 +765,9 @@ describe('xy_expression', () => {
|
|||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(component.find(BarSeries)).toHaveLength(1);
|
||||
expect(component.find(BarSeries).prop('stackAccessors')).toHaveLength(1);
|
||||
expect(component.find(BarSeries)).toHaveLength(2);
|
||||
expect(component.find(BarSeries).at(0).prop('stackAccessors')).toHaveLength(1);
|
||||
expect(component.find(BarSeries).at(1).prop('stackAccessors')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('it renders stacked area', () => {
|
||||
|
@ -724,8 +785,9 @@ describe('xy_expression', () => {
|
|||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(component.find(AreaSeries)).toHaveLength(1);
|
||||
expect(component.find(AreaSeries).prop('stackAccessors')).toHaveLength(1);
|
||||
expect(component.find(AreaSeries)).toHaveLength(2);
|
||||
expect(component.find(AreaSeries).at(0).prop('stackAccessors')).toHaveLength(1);
|
||||
expect(component.find(AreaSeries).at(1).prop('stackAccessors')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('it renders stacked horizontal bar', () => {
|
||||
|
@ -746,8 +808,9 @@ describe('xy_expression', () => {
|
|||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(component.find(BarSeries)).toHaveLength(1);
|
||||
expect(component.find(BarSeries).prop('stackAccessors')).toHaveLength(1);
|
||||
expect(component.find(BarSeries)).toHaveLength(2);
|
||||
expect(component.find(BarSeries).at(0).prop('stackAccessors')).toHaveLength(1);
|
||||
expect(component.find(BarSeries).at(1).prop('stackAccessors')).toHaveLength(1);
|
||||
expect(component.find(Settings).prop('rotation')).toEqual(90);
|
||||
});
|
||||
|
||||
|
@ -765,7 +828,8 @@ describe('xy_expression', () => {
|
|||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
expect(component.find(LineSeries).prop('timeZone')).toEqual('CEST');
|
||||
expect(component.find(LineSeries).at(0).prop('timeZone')).toEqual('CEST');
|
||||
expect(component.find(LineSeries).at(1).prop('timeZone')).toEqual('CEST');
|
||||
});
|
||||
|
||||
test('it applies histogram mode to the series for single series', () => {
|
||||
|
@ -784,7 +848,8 @@ describe('xy_expression', () => {
|
|||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(true);
|
||||
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true);
|
||||
expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(true);
|
||||
});
|
||||
|
||||
test('it applies histogram mode to the series for stacked series', () => {
|
||||
|
@ -810,7 +875,8 @@ describe('xy_expression', () => {
|
|||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(true);
|
||||
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(true);
|
||||
expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(true);
|
||||
});
|
||||
|
||||
test('it does not apply histogram mode for splitted series', () => {
|
||||
|
@ -830,47 +896,104 @@ describe('xy_expression', () => {
|
|||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
expect(component.find(BarSeries).prop('enableHistogramMode')).toEqual(false);
|
||||
expect(component.find(BarSeries).at(0).prop('enableHistogramMode')).toEqual(false);
|
||||
expect(component.find(BarSeries).at(1).prop('enableHistogramMode')).toEqual(false);
|
||||
});
|
||||
|
||||
describe('y axes', () => {
|
||||
test('single axis if possible', () => {
|
||||
const args = createArgsWithLayers();
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, args);
|
||||
const axes = component.find(Axis);
|
||||
expect(axes).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('multiple axes because of config', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['a', 'b'],
|
||||
yConfig: [
|
||||
{
|
||||
forAccessor: 'a',
|
||||
axisMode: 'left',
|
||||
},
|
||||
{
|
||||
forAccessor: 'b',
|
||||
axisMode: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as XYArgs;
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const axes = component.find(Axis);
|
||||
expect(axes).toHaveLength(3);
|
||||
expect(component.find(LineSeries).at(0).prop('groupId')).toEqual(
|
||||
axes.at(1).prop('groupId')
|
||||
);
|
||||
expect(component.find(LineSeries).at(1).prop('groupId')).toEqual(
|
||||
axes.at(2).prop('groupId')
|
||||
);
|
||||
});
|
||||
|
||||
test('multiple axes because of incompatible formatters', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['c', 'd'],
|
||||
},
|
||||
],
|
||||
} as XYArgs;
|
||||
|
||||
const component = getRenderedComponent(dataWithFormats, newArgs);
|
||||
const axes = component.find(Axis);
|
||||
expect(axes).toHaveLength(3);
|
||||
expect(component.find(LineSeries).at(0).prop('groupId')).toEqual(
|
||||
axes.at(1).prop('groupId')
|
||||
);
|
||||
expect(component.find(LineSeries).at(1).prop('groupId')).toEqual(
|
||||
axes.at(2).prop('groupId')
|
||||
);
|
||||
});
|
||||
|
||||
test('single axis despite different formatters if enforced', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
...args,
|
||||
layers: [
|
||||
{
|
||||
...args.layers[0],
|
||||
accessors: ['c', 'd'],
|
||||
yConfig: [
|
||||
{
|
||||
forAccessor: 'c',
|
||||
axisMode: 'left',
|
||||
},
|
||||
{
|
||||
forAccessor: 'd',
|
||||
axisMode: 'left',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as XYArgs;
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const axes = component.find(Axis);
|
||||
expect(axes).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('provides correct series naming', () => {
|
||||
const dataWithoutFormats: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
first: {
|
||||
type: 'kibana_datatable',
|
||||
columns: [
|
||||
{ id: 'a', name: 'a' },
|
||||
{ id: 'b', name: 'b' },
|
||||
{ id: 'c', name: 'c' },
|
||||
{ id: 'd', name: 'd' },
|
||||
],
|
||||
rows: [
|
||||
{ a: 1, b: 2, c: 'I', d: 'Row 1' },
|
||||
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const dataWithFormats: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
tables: {
|
||||
first: {
|
||||
type: 'kibana_datatable',
|
||||
columns: [
|
||||
{ id: 'a', name: 'a' },
|
||||
{ id: 'b', name: 'b' },
|
||||
{ id: 'c', name: 'c' },
|
||||
{ id: 'd', name: 'd', formatHint: { id: 'custom' } },
|
||||
],
|
||||
rows: [
|
||||
{ a: 1, b: 2, c: 'I', d: 'Row 1' },
|
||||
{ a: 1, b: 5, c: 'J', d: 'Row 2' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const nameFnArgs = {
|
||||
seriesKeys: [],
|
||||
key: '',
|
||||
|
@ -879,21 +1002,6 @@ describe('xy_expression', () => {
|
|||
splitAccessors: new Map(),
|
||||
};
|
||||
|
||||
const getRenderedComponent = (data: LensMultiTable, args: XYArgs) => {
|
||||
return shallow(
|
||||
<XYChart
|
||||
data={data}
|
||||
args={args}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartTheme={{}}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
test('simplest xy chart without human-readable name', () => {
|
||||
const args = createArgsWithLayers();
|
||||
const newArgs = {
|
||||
|
@ -973,13 +1081,14 @@ describe('xy_expression', () => {
|
|||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
const nameFn1 = component.find(LineSeries).at(0).prop('name') as SeriesNameFn;
|
||||
const nameFn2 = component.find(LineSeries).at(1).prop('name') as SeriesNameFn;
|
||||
|
||||
// This accessor has a human-readable name
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('Label A');
|
||||
expect(nameFn1({ ...nameFnArgs, seriesKeys: ['a'] }, false)).toEqual('Label A');
|
||||
// This accessor does not
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['b'] }, false)).toEqual('');
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual('');
|
||||
expect(nameFn2({ ...nameFnArgs, seriesKeys: ['b'] }, false)).toEqual('');
|
||||
expect(nameFn1({ ...nameFnArgs, seriesKeys: ['nonsense'] }, false)).toEqual('');
|
||||
});
|
||||
|
||||
test('split series without formatting and single y accessor', () => {
|
||||
|
@ -1039,9 +1148,13 @@ describe('xy_expression', () => {
|
|||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithoutFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
const nameFn1 = component.find(LineSeries).at(0).prop('name') as SeriesNameFn;
|
||||
const nameFn2 = component.find(LineSeries).at(0).prop('name') as SeriesNameFn;
|
||||
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual(
|
||||
expect(nameFn1({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual(
|
||||
'split1 - Label A'
|
||||
);
|
||||
expect(nameFn2({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual(
|
||||
'split1 - Label B'
|
||||
);
|
||||
});
|
||||
|
@ -1061,13 +1174,14 @@ describe('xy_expression', () => {
|
|||
};
|
||||
|
||||
const component = getRenderedComponent(dataWithFormats, newArgs);
|
||||
const nameFn = component.find(LineSeries).prop('name') as SeriesNameFn;
|
||||
const nameFn1 = component.find(LineSeries).at(0).prop('name') as SeriesNameFn;
|
||||
const nameFn2 = component.find(LineSeries).at(1).prop('name') as SeriesNameFn;
|
||||
|
||||
convertSpy.mockReturnValueOnce('formatted1').mockReturnValueOnce('formatted2');
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual(
|
||||
expect(nameFn1({ ...nameFnArgs, seriesKeys: ['split1', 'a'] }, false)).toEqual(
|
||||
'formatted1 - Label A'
|
||||
);
|
||||
expect(nameFn({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual(
|
||||
expect(nameFn2({ ...nameFnArgs, seriesKeys: ['split1', 'b'] }, false)).toEqual(
|
||||
'formatted2 - Label B'
|
||||
);
|
||||
});
|
||||
|
@ -1088,7 +1202,8 @@ describe('xy_expression', () => {
|
|||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
expect(component.find(LineSeries).prop('xScaleType')).toEqual(ScaleType.Ordinal);
|
||||
expect(component.find(LineSeries).at(0).prop('xScaleType')).toEqual(ScaleType.Ordinal);
|
||||
expect(component.find(LineSeries).at(1).prop('xScaleType')).toEqual(ScaleType.Ordinal);
|
||||
});
|
||||
|
||||
test('it set the scale of the y axis according to the args prop', () => {
|
||||
|
@ -1106,7 +1221,8 @@ describe('xy_expression', () => {
|
|||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
expect(component.find(LineSeries).prop('yScaleType')).toEqual(ScaleType.Sqrt);
|
||||
expect(component.find(LineSeries).at(0).prop('yScaleType')).toEqual(ScaleType.Sqrt);
|
||||
expect(component.find(LineSeries).at(1).prop('yScaleType')).toEqual(ScaleType.Sqrt);
|
||||
});
|
||||
|
||||
test('it gets the formatter for the x axis', () => {
|
||||
|
@ -1128,25 +1244,6 @@ describe('xy_expression', () => {
|
|||
expect(getFormatSpy).toHaveBeenCalledWith({ id: 'string' });
|
||||
});
|
||||
|
||||
test('it gets a default formatter for y if there are multiple y accessors', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
shallow(
|
||||
<XYChart
|
||||
data={{ ...data }}
|
||||
args={{ ...args }}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartTheme={{}}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getFormatSpy).toHaveBeenCalledWith({ id: 'number' });
|
||||
});
|
||||
|
||||
test('it gets the formatter for the y axis if there is only one accessor', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ import { isHorizontalChart } from './state_helpers';
|
|||
import { parseInterval } from '../../../../../src/plugins/data/common';
|
||||
import { EmptyPlaceholder } from '../shared_components';
|
||||
import { desanitizeFilterContext } from '../utils';
|
||||
import { getAxesConfiguration } from './axes_configuration';
|
||||
|
||||
type InferPropType<T> = T extends React.FunctionComponent<infer P> ? P : T;
|
||||
type SeriesSpec = InferPropType<typeof LineSeries> &
|
||||
|
@ -213,23 +214,19 @@ export function XYChart({
|
|||
);
|
||||
const xAxisFormatter = formatFactory(xAxisColumn && xAxisColumn.formatHint);
|
||||
|
||||
// use default number formatter for y axis and use formatting hint if there is just a single y column
|
||||
let yAxisFormatter = formatFactory({ id: 'number' });
|
||||
if (filteredLayers.length === 1 && filteredLayers[0].accessors.length === 1) {
|
||||
const firstYAxisColumn = Object.values(data.tables)[0].columns.find(
|
||||
({ id }) => id === filteredLayers[0].accessors[0]
|
||||
);
|
||||
if (firstYAxisColumn && firstYAxisColumn.formatHint) {
|
||||
yAxisFormatter = formatFactory(firstYAxisColumn.formatHint);
|
||||
}
|
||||
}
|
||||
|
||||
const chartHasMoreThanOneSeries =
|
||||
filteredLayers.length > 1 ||
|
||||
filteredLayers.some((layer) => layer.accessors.length > 1) ||
|
||||
filteredLayers.some((layer) => layer.splitAccessor);
|
||||
const shouldRotate = isHorizontalChart(filteredLayers);
|
||||
|
||||
const yAxesConfiguration = getAxesConfiguration(
|
||||
filteredLayers,
|
||||
data.tables,
|
||||
formatFactory,
|
||||
shouldRotate
|
||||
);
|
||||
|
||||
const xTitle = (xAxisColumn && xAxisColumn.name) || args.xTitle;
|
||||
|
||||
function calculateMinInterval() {
|
||||
|
@ -279,6 +276,9 @@ export function XYChart({
|
|||
legendPosition={legend.position}
|
||||
showLegendExtra={false}
|
||||
theme={chartTheme}
|
||||
tooltip={{
|
||||
headerFormatter: (d) => xAxisFormatter.convert(d.value),
|
||||
}}
|
||||
rotation={shouldRotate ? 90 : 0}
|
||||
xDomain={xDomain}
|
||||
onBrushEnd={({ x }) => {
|
||||
|
@ -368,18 +368,30 @@ export function XYChart({
|
|||
tickFormat={(d) => xAxisFormatter.convert(d)}
|
||||
/>
|
||||
|
||||
<Axis
|
||||
id="y"
|
||||
position={shouldRotate ? Position.Bottom : Position.Left}
|
||||
title={args.yTitle}
|
||||
showGridLines={false}
|
||||
hide={filteredLayers[0].hide}
|
||||
tickFormat={(d) => yAxisFormatter.convert(d)}
|
||||
/>
|
||||
{yAxesConfiguration.map((axis, index) => (
|
||||
<Axis
|
||||
key={axis.groupId}
|
||||
id={axis.groupId}
|
||||
groupId={axis.groupId}
|
||||
position={axis.position}
|
||||
title={
|
||||
axis.series
|
||||
.map(
|
||||
(series) =>
|
||||
data.tables[series.layer].columns.find((column) => column.id === series.accessor)
|
||||
?.name
|
||||
)
|
||||
.filter((name) => Boolean(name))[0] || args.yTitle
|
||||
}
|
||||
showGridLines={false}
|
||||
hide={filteredLayers[0].hide}
|
||||
tickFormat={(d) => axis.formatter.convert(d)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{filteredLayers.map(
|
||||
(
|
||||
{
|
||||
{filteredLayers.flatMap((layer, layerIndex) =>
|
||||
layer.accessors.map((accessor, accessorIndex) => {
|
||||
const {
|
||||
splitAccessor,
|
||||
seriesType,
|
||||
accessors,
|
||||
|
@ -389,9 +401,7 @@ export function XYChart({
|
|||
yScaleType,
|
||||
xScaleType,
|
||||
isHistogram,
|
||||
},
|
||||
index
|
||||
) => {
|
||||
} = layer;
|
||||
const columnToLabelMap: Record<string, string> = columnToLabel
|
||||
? JSON.parse(columnToLabel)
|
||||
: {};
|
||||
|
@ -407,19 +417,22 @@ export function XYChart({
|
|||
!(
|
||||
splitAccessor &&
|
||||
typeof row[splitAccessor] === 'undefined' &&
|
||||
accessors.every((accessor) => typeof row[accessor] === 'undefined')
|
||||
typeof row[accessor] === 'undefined'
|
||||
)
|
||||
);
|
||||
|
||||
const seriesProps: SeriesSpec = {
|
||||
splitSeriesAccessors: splitAccessor ? [splitAccessor] : [],
|
||||
stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [],
|
||||
id: splitAccessor || accessors.join(','),
|
||||
id: `${splitAccessor}-${accessor}`,
|
||||
xAccessor,
|
||||
yAccessors: accessors,
|
||||
yAccessors: [accessor],
|
||||
data: rows,
|
||||
xScaleType,
|
||||
yScaleType,
|
||||
groupId: yAxesConfiguration.find((axisConfiguration) =>
|
||||
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
|
||||
)?.groupId,
|
||||
enableHistogramMode: isHistogram && (seriesType.includes('stacked') || !splitAccessor),
|
||||
timeZone,
|
||||
name(d) {
|
||||
|
@ -451,6 +464,8 @@ export function XYChart({
|
|||
},
|
||||
};
|
||||
|
||||
const index = `${layerIndex}-${accessorIndex}`;
|
||||
|
||||
switch (seriesType) {
|
||||
case 'line':
|
||||
return <LineSeries key={index} {...seriesProps} />;
|
||||
|
@ -462,7 +477,7 @@ export function XYChart({
|
|||
default:
|
||||
return <AreaSeries key={index} {...seriesProps} />;
|
||||
}
|
||||
}
|
||||
})
|
||||
)}
|
||||
</Chart>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
TableSuggestion,
|
||||
TableChangeType,
|
||||
} from '../types';
|
||||
import { State, SeriesType, XYState, visualizationTypes } from './types';
|
||||
import { State, SeriesType, XYState, visualizationTypes, LayerConfig } from './types';
|
||||
import { getIconForSeries } from './state_helpers';
|
||||
|
||||
const columnSortOrder = {
|
||||
|
@ -379,13 +379,19 @@ function buildSuggestion({
|
|||
changeType: TableChangeType;
|
||||
keptLayerIds: string[];
|
||||
}) {
|
||||
const existingLayer: LayerConfig | {} = getExistingLayer(currentState, layerId) || {};
|
||||
const accessors = yValues.map((col) => col.columnId);
|
||||
const newLayer = {
|
||||
...(getExistingLayer(currentState, layerId) || {}),
|
||||
...existingLayer,
|
||||
layerId,
|
||||
seriesType,
|
||||
xAccessor: xValue.columnId,
|
||||
splitAccessor: splitBy?.columnId,
|
||||
accessors: yValues.map((col) => col.columnId),
|
||||
accessors,
|
||||
yConfig:
|
||||
'yConfig' in existingLayer && existingLayer.yConfig
|
||||
? existingLayer.yConfig.filter(({ forAccessor }) => accessors.indexOf(forAccessor) !== -1)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const keptLayers = currentState
|
||||
|
|
|
@ -11,13 +11,13 @@ import { Position } from '@elastic/charts';
|
|||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getSuggestions } from './xy_suggestions';
|
||||
import { LayerContextMenu } from './xy_config_panel';
|
||||
import { DimensionEditor, LayerContextMenu } from './xy_config_panel';
|
||||
import { Visualization, OperationMetadata, VisualizationType } from '../types';
|
||||
import { State, PersistableState, SeriesType, visualizationTypes, LayerConfig } from './types';
|
||||
import { toExpression, toPreviewExpression } from './to_expression';
|
||||
import chartBarStackedSVG from '../assets/chart_bar_stacked.svg';
|
||||
import chartMixedSVG from '../assets/chart_mixed_xy.svg';
|
||||
import { isHorizontalChart } from './state_helpers';
|
||||
import { toExpression, toPreviewExpression } from './to_expression';
|
||||
|
||||
const defaultIcon = chartBarStackedSVG;
|
||||
const defaultSeriesType = 'bar_stacked';
|
||||
|
@ -187,6 +187,7 @@ export const xyVisualization: Visualization<State, PersistableState> = {
|
|||
supportsMoreColumns: true,
|
||||
required: true,
|
||||
dataTestSubj: 'lnsXY_yDimensionPanel',
|
||||
enableDimensionEditor: true,
|
||||
},
|
||||
{
|
||||
groupId: 'breakdown',
|
||||
|
@ -239,6 +240,10 @@ export const xyVisualization: Visualization<State, PersistableState> = {
|
|||
newLayer.accessors = newLayer.accessors.filter((a) => a !== columnId);
|
||||
}
|
||||
|
||||
if (newLayer.yConfig) {
|
||||
newLayer.yConfig = newLayer.yConfig.filter(({ forAccessor }) => forAccessor !== columnId);
|
||||
}
|
||||
|
||||
return {
|
||||
...prevState,
|
||||
layers: prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)),
|
||||
|
@ -259,6 +264,15 @@ export const xyVisualization: Visualization<State, PersistableState> = {
|
|||
);
|
||||
},
|
||||
|
||||
renderDimensionEditor(domElement, props) {
|
||||
render(
|
||||
<I18nProvider>
|
||||
<DimensionEditor {...props} />
|
||||
</I18nProvider>,
|
||||
domElement
|
||||
);
|
||||
},
|
||||
|
||||
toExpression,
|
||||
toPreviewExpression,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue