[Lens] Fitting functions (#69820)
This commit is contained in:
parent
e70fcc708e
commit
bbda3f99ef
15 changed files with 379 additions and 37 deletions
|
@ -68,29 +68,33 @@ export function WorkspacePanelWrapper({
|
|||
return (
|
||||
<EuiFlexGroup gutterSize="s" direction="column" alignItems="stretch" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChartSwitch
|
||||
data-test-subj="lnsChartSwitcher"
|
||||
visualizationMap={visualizationMap}
|
||||
visualizationId={visualizationId}
|
||||
visualizationState={visualizationState}
|
||||
datasourceMap={datasourceMap}
|
||||
datasourceStates={datasourceStates}
|
||||
dispatch={dispatch}
|
||||
framePublicAPI={framePublicAPI}
|
||||
/>
|
||||
<EuiFlexGroup gutterSize="s" direction="row" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChartSwitch
|
||||
data-test-subj="lnsChartSwitcher"
|
||||
visualizationMap={visualizationMap}
|
||||
visualizationId={visualizationId}
|
||||
visualizationState={visualizationState}
|
||||
datasourceMap={datasourceMap}
|
||||
datasourceStates={datasourceStates}
|
||||
dispatch={dispatch}
|
||||
framePublicAPI={framePublicAPI}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{activeVisualization && activeVisualization.renderToolbar && (
|
||||
<EuiFlexItem grow>
|
||||
<NativeRenderer
|
||||
render={activeVisualization.renderToolbar}
|
||||
nativeProps={{
|
||||
frame: framePublicAPI,
|
||||
state: visualizationState,
|
||||
setState: setVisualizationState,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{activeVisualization && activeVisualization.renderToolbar && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<NativeRenderer
|
||||
render={activeVisualization.renderToolbar}
|
||||
nativeProps={{
|
||||
frame: framePublicAPI,
|
||||
state: visualizationState,
|
||||
setState: setVisualizationState,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiPageContent className="lnsWorkspacePanelWrapper">
|
||||
{(!emptyExpression || title) && (
|
||||
|
|
|
@ -5,6 +5,9 @@ Object {
|
|||
"chain": Array [
|
||||
Object {
|
||||
"arguments": Object {
|
||||
"fittingFunction": Array [
|
||||
"Carry",
|
||||
],
|
||||
"layers": Array [
|
||||
Object {
|
||||
"chain": Array [
|
||||
|
|
|
@ -54,6 +54,11 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
fit={
|
||||
Object {
|
||||
"type": "none",
|
||||
}
|
||||
}
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
|
@ -93,6 +98,11 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
fit={
|
||||
Object {
|
||||
"type": "none",
|
||||
}
|
||||
}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
|
@ -402,6 +412,11 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
fit={
|
||||
Object {
|
||||
"type": "none",
|
||||
}
|
||||
}
|
||||
groupId="left"
|
||||
id="d-a"
|
||||
key="0-0"
|
||||
|
@ -441,6 +456,11 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
]
|
||||
}
|
||||
enableHistogramMode={false}
|
||||
fit={
|
||||
Object {
|
||||
"type": "none",
|
||||
}
|
||||
}
|
||||
groupId="left"
|
||||
id="d-b"
|
||||
key="0-1"
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 { Fit } from '@elastic/charts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export type FittingFunction = typeof fittingFunctionDefinitions[number]['id'];
|
||||
|
||||
export const fittingFunctionDefinitions = [
|
||||
{
|
||||
id: 'None',
|
||||
title: i18n.translate('xpack.lens.fittingFunctionsTitle.none', {
|
||||
defaultMessage: 'Hide',
|
||||
}),
|
||||
description: i18n.translate('xpack.lens.fittingFunctionsDescription.none', {
|
||||
defaultMessage: 'Do not fill gaps',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'Zero',
|
||||
title: i18n.translate('xpack.lens.fittingFunctionsTitle.zero', {
|
||||
defaultMessage: 'Zero',
|
||||
}),
|
||||
description: i18n.translate('xpack.lens.fittingFunctionsDescription.zero', {
|
||||
defaultMessage: 'Fill gaps with zeros',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'Linear',
|
||||
title: i18n.translate('xpack.lens.fittingFunctionsTitle.linear', {
|
||||
defaultMessage: 'Linear',
|
||||
}),
|
||||
description: i18n.translate('xpack.lens.fittingFunctionsDescription.linear', {
|
||||
defaultMessage: 'Fill gaps with a line',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'Carry',
|
||||
title: i18n.translate('xpack.lens.fittingFunctionsTitle.carry', {
|
||||
defaultMessage: 'Last',
|
||||
}),
|
||||
description: i18n.translate('xpack.lens.fittingFunctionsDescription.carry', {
|
||||
defaultMessage: 'Fill gaps with the last value',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'Lookahead',
|
||||
title: i18n.translate('xpack.lens.fittingFunctionsTitle.lookahead', {
|
||||
defaultMessage: 'Next',
|
||||
}),
|
||||
description: i18n.translate('xpack.lens.fittingFunctionsDescription.lookahead', {
|
||||
defaultMessage: 'Fill gaps with the next value',
|
||||
}),
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function getFitEnum(fittingFunction?: FittingFunction) {
|
||||
if (fittingFunction) {
|
||||
return Fit[fittingFunction];
|
||||
}
|
||||
return Fit.None;
|
||||
}
|
||||
|
||||
export function getFitOptions(fittingFunction?: FittingFunction) {
|
||||
return { type: getFitEnum(fittingFunction) };
|
||||
}
|
|
@ -40,6 +40,7 @@ describe('#toExpression', () => {
|
|||
{
|
||||
legend: { position: Position.Bottom, isVisible: true },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'Carry',
|
||||
layers: [
|
||||
{
|
||||
layerId: 'first',
|
||||
|
@ -55,6 +56,27 @@ describe('#toExpression', () => {
|
|||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should default the fitting function to None', () => {
|
||||
expect(
|
||||
(xyVisualization.toExpression(
|
||||
{
|
||||
legend: { position: Position.Bottom, isVisible: true },
|
||||
preferredSeriesType: 'bar',
|
||||
layers: [
|
||||
{
|
||||
layerId: 'first',
|
||||
seriesType: 'area',
|
||||
splitAccessor: 'd',
|
||||
xAccessor: 'a',
|
||||
accessors: ['b', 'c'],
|
||||
},
|
||||
],
|
||||
},
|
||||
frame
|
||||
) as Ast).chain[0].arguments.fittingFunction[0]
|
||||
).toEqual('None');
|
||||
});
|
||||
|
||||
it('should not generate an expression when missing x', () => {
|
||||
expect(
|
||||
xyVisualization.toExpression(
|
||||
|
|
|
@ -133,6 +133,7 @@ export const buildExpression = (
|
|||
],
|
||||
},
|
||||
],
|
||||
fittingFunction: [state.fittingFunction || 'None'],
|
||||
layers: validLayers.map((layer) => {
|
||||
const columnToLabel: Record<string, string> = {};
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import chartBarHorizontalStackedSVG from '../assets/chart_bar_horizontal_stacked
|
|||
import chartLineSVG from '../assets/chart_line.svg';
|
||||
|
||||
import { VisualizationType } from '../index';
|
||||
import { FittingFunction } from './fitting_functions';
|
||||
|
||||
export interface LegendConfig {
|
||||
isVisible: boolean;
|
||||
|
@ -225,12 +226,14 @@ export interface XYArgs {
|
|||
yTitle: string;
|
||||
legend: LegendConfig & { type: 'lens_xy_legendConfig' };
|
||||
layers: LayerArgs[];
|
||||
fittingFunction?: FittingFunction;
|
||||
}
|
||||
|
||||
// Persisted parts of the state
|
||||
export interface XYState {
|
||||
preferredSeriesType: SeriesType;
|
||||
legend: LegendConfig;
|
||||
fittingFunction?: FittingFunction;
|
||||
layers: LayerConfig[];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.lnsXyToolbar__popover {
|
||||
width: 400px;
|
||||
}
|
|
@ -5,15 +5,15 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mountWithIntl as mount } from 'test_utils/enzyme_helpers';
|
||||
import { EuiButtonGroupProps } from '@elastic/eui';
|
||||
import { LayerContextMenu } from './xy_config_panel';
|
||||
import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/enzyme_helpers';
|
||||
import { EuiButtonGroupProps, EuiSuperSelect } from '@elastic/eui';
|
||||
import { LayerContextMenu, XyToolbar } from './xy_config_panel';
|
||||
import { FramePublicAPI } from '../types';
|
||||
import { State } from './types';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { createMockFramePublicAPI, createMockDatasource } from '../editor_frame_service/mocks';
|
||||
|
||||
describe('LayerContextMenu', () => {
|
||||
describe('XY Config panels', () => {
|
||||
let frame: FramePublicAPI;
|
||||
|
||||
function testState(): State {
|
||||
|
@ -39,11 +39,6 @@ describe('LayerContextMenu', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test.skip('allows toggling of legend visibility', () => {});
|
||||
test.skip('allows changing legend position', () => {});
|
||||
test.skip('allows toggling the y axis gridlines', () => {});
|
||||
test.skip('allows toggling the x axis gridlines', () => {});
|
||||
|
||||
describe('LayerContextMenu', () => {
|
||||
test('enables stacked chart types even when there is no split series', () => {
|
||||
const state = testState();
|
||||
|
@ -92,4 +87,45 @@ describe('LayerContextMenu', () => {
|
|||
expect(options!.filter(({ isDisabled }) => isDisabled).map(({ id }) => id)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('XyToolbar', () => {
|
||||
it('should show currently selected fitting function', () => {
|
||||
const state = testState();
|
||||
|
||||
const component = shallow(
|
||||
<XyToolbar
|
||||
frame={frame}
|
||||
setState={jest.fn()}
|
||||
state={{
|
||||
...state,
|
||||
layers: [{ ...state.layers[0], seriesType: 'line' }],
|
||||
fittingFunction: 'Carry',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry');
|
||||
});
|
||||
|
||||
it('should disable the select if there is no unstacked area or line series', () => {
|
||||
const state = testState();
|
||||
|
||||
const component = shallow(
|
||||
<XyToolbar
|
||||
frame={frame}
|
||||
setState={jest.fn()}
|
||||
state={{
|
||||
...state,
|
||||
layers: [
|
||||
{ ...state.layers[0], seriesType: 'bar' },
|
||||
{ ...state.layers[0], seriesType: 'area_stacked' },
|
||||
],
|
||||
fittingFunction: 'Carry',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(EuiSuperSelect).prop('disabled')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,8 +8,14 @@ import React, { useState } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { debounce } from 'lodash';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButtonGroup,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSuperSelect,
|
||||
EuiFormRow,
|
||||
EuiPopover,
|
||||
EuiText,
|
||||
htmlIdGenerator,
|
||||
EuiForm,
|
||||
EuiColorPicker,
|
||||
|
@ -17,10 +23,17 @@ import {
|
|||
EuiToolTip,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
VisualizationLayerWidgetProps,
|
||||
VisualizationDimensionEditorProps,
|
||||
VisualizationToolbarProps,
|
||||
} from '../types';
|
||||
import { State, SeriesType, visualizationTypes, YAxisMode } from './types';
|
||||
import { VisualizationDimensionEditorProps, VisualizationLayerWidgetProps } from '../types';
|
||||
import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers';
|
||||
import { trackUiEvent } from '../lens_ui_telemetry';
|
||||
import { fittingFunctionDefinitions } from './fitting_functions';
|
||||
|
||||
import './xy_config_panel.scss';
|
||||
|
||||
type UnwrapArray<T> = T extends Array<infer P> ? P : T;
|
||||
|
||||
|
@ -78,6 +91,75 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps<State>) {
|
|||
);
|
||||
}
|
||||
|
||||
export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const hasNonBarSeries = props.state?.layers.some(
|
||||
(layer) => layer.seriesType === 'line' || layer.seriesType === 'area'
|
||||
);
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
panelClassName="lnsXyToolbar__popover"
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.lens.xyChart.settingsLabel', { defaultMessage: 'Settings' })}
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
isOpen={open}
|
||||
closePopover={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
anchorPosition="downRight"
|
||||
>
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.xyChart.fittingLabel', {
|
||||
defaultMessage: 'Fill missing values',
|
||||
})}
|
||||
helpText={
|
||||
!hasNonBarSeries &&
|
||||
i18n.translate('xpack.lens.xyChart.fittingDisabledHelpText', {
|
||||
defaultMessage:
|
||||
'This setting only applies to line charts and unstacked area charts.',
|
||||
})
|
||||
}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
compressed
|
||||
disabled={!hasNonBarSeries}
|
||||
options={fittingFunctionDefinitions.map(({ id, title, description }) => {
|
||||
return {
|
||||
value: id,
|
||||
dropdownDisplay: (
|
||||
<>
|
||||
<strong>{title}</strong>
|
||||
<EuiText size="xs" color="subdued">
|
||||
<p>{description}</p>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
inputDisplay: title,
|
||||
};
|
||||
})}
|
||||
valueOfSelected={props.state?.fittingFunction || 'None'}
|
||||
onChange={(value) => props.setState({ ...props.state, fittingFunction: value })}
|
||||
itemLayoutAlign="top"
|
||||
hasDividers
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
const idPrefix = htmlIdGenerator()();
|
||||
|
||||
export function DimensionEditor(props: VisualizationDimensionEditorProps<State>) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
GeometryValue,
|
||||
XYChartSeriesIdentifier,
|
||||
SeriesNameFn,
|
||||
Fit,
|
||||
} from '@elastic/charts';
|
||||
import { xyChart, XYChart } from './xy_expression';
|
||||
import { LensMultiTable } from '../types';
|
||||
|
@ -1554,5 +1555,66 @@ describe('xy_expression', () => {
|
|||
|
||||
expect(component.find(Settings).prop('showLegend')).toEqual(true);
|
||||
});
|
||||
|
||||
test('it should apply the fitting function to all non-bar series', () => {
|
||||
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' },
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
const args: XYArgs = createArgsWithLayers([
|
||||
{ ...sampleLayer, accessors: ['a'] },
|
||||
{ ...sampleLayer, seriesType: 'bar', accessors: ['a'] },
|
||||
{ ...sampleLayer, seriesType: 'area', accessors: ['a'] },
|
||||
{ ...sampleLayer, seriesType: 'area_stacked', accessors: ['a'] },
|
||||
]);
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
data={{ ...data }}
|
||||
args={{ ...args, fittingFunction: 'Carry' }}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartsThemeService={chartsThemeService}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(LineSeries).prop('fit')).toEqual({ type: Fit.Carry });
|
||||
expect(component.find(BarSeries).prop('fit')).toEqual(undefined);
|
||||
expect(component.find(AreaSeries).at(0).prop('fit')).toEqual({ type: Fit.Carry });
|
||||
expect(component.find(AreaSeries).at(0).prop('stackAccessors')).toEqual([]);
|
||||
// stacked area series doesn't get the fit prop
|
||||
expect(component.find(AreaSeries).at(1).prop('fit')).toEqual(undefined);
|
||||
expect(component.find(AreaSeries).at(1).prop('stackAccessors')).toEqual(['c']);
|
||||
});
|
||||
|
||||
test('it should apply None fitting function if not specified', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
args.layers[0].accessors = ['a'];
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
data={{ ...data }}
|
||||
args={{ ...args }}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartsThemeService={chartsThemeService}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(LineSeries).prop('fit')).toEqual({ type: Fit.None });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,6 +40,7 @@ import { parseInterval } from '../../../../../src/plugins/data/common';
|
|||
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
|
||||
import { EmptyPlaceholder } from '../shared_components';
|
||||
import { desanitizeFilterContext } from '../utils';
|
||||
import { fittingFunctionDefinitions, getFitOptions } from './fitting_functions';
|
||||
import { getAxesConfiguration } from './axes_configuration';
|
||||
|
||||
type InferPropType<T> = T extends React.FunctionComponent<infer P> ? P : T;
|
||||
|
@ -94,6 +95,13 @@ export const xyChart: ExpressionFunctionDefinition<
|
|||
defaultMessage: 'Configure the chart legend.',
|
||||
}),
|
||||
},
|
||||
fittingFunction: {
|
||||
types: ['string'],
|
||||
options: [...fittingFunctionDefinitions.map(({ id }) => id)],
|
||||
help: i18n.translate('xpack.lens.xyChart.fittingFunction.help', {
|
||||
defaultMessage: 'Define how missing values are treated',
|
||||
}),
|
||||
},
|
||||
layers: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
types: ['lens_xy_layer'] as any,
|
||||
|
@ -191,7 +199,7 @@ export function XYChart({
|
|||
onClickValue,
|
||||
onSelectRange,
|
||||
}: XYChartRenderProps) {
|
||||
const { legend, layers } = args;
|
||||
const { legend, layers, fittingFunction } = args;
|
||||
const chartTheme = chartsThemeService.useChartsTheme();
|
||||
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
|
||||
|
||||
|
@ -463,7 +471,7 @@ export function XYChart({
|
|||
}
|
||||
// This handles both split and single-y cases:
|
||||
// * If split series without formatting, show the value literally
|
||||
// * If single Y, the seriesKey will be the acccessor, so we show the human-readable name
|
||||
// * If single Y, the seriesKey will be the accessor, so we show the human-readable name
|
||||
return splitAccessor ? d.seriesKeys[0] : columnToLabelMap[d.seriesKeys[0]] ?? '';
|
||||
},
|
||||
};
|
||||
|
@ -472,17 +480,29 @@ export function XYChart({
|
|||
|
||||
switch (seriesType) {
|
||||
case 'line':
|
||||
return <LineSeries key={index} {...seriesProps} />;
|
||||
return (
|
||||
<LineSeries key={index} {...seriesProps} fit={getFitOptions(fittingFunction)} />
|
||||
);
|
||||
case 'bar':
|
||||
case 'bar_stacked':
|
||||
case 'bar_horizontal':
|
||||
case 'bar_horizontal_stacked':
|
||||
return <BarSeries key={index} {...seriesProps} />;
|
||||
default:
|
||||
case 'area_stacked':
|
||||
return <AreaSeries key={index} {...seriesProps} />;
|
||||
case 'area':
|
||||
return (
|
||||
<AreaSeries key={index} {...seriesProps} fit={getFitOptions(fittingFunction)} />
|
||||
);
|
||||
default:
|
||||
return assertNever(seriesType);
|
||||
}
|
||||
})
|
||||
)}
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
|
||||
function assertNever(x: never): never {
|
||||
throw new Error('Unexpected series type: ' + x);
|
||||
}
|
||||
|
|
|
@ -331,6 +331,7 @@ describe('xy_suggestions', () => {
|
|||
test('makes a visible seriesType suggestion for unchanged table without split', () => {
|
||||
const currentState: XYState = {
|
||||
legend: { isVisible: true, position: 'bottom' },
|
||||
fittingFunction: 'None',
|
||||
preferredSeriesType: 'bar',
|
||||
layers: [
|
||||
{
|
||||
|
@ -368,6 +369,7 @@ describe('xy_suggestions', () => {
|
|||
const currentState: XYState = {
|
||||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price', 'quantity'],
|
||||
|
@ -408,6 +410,7 @@ describe('xy_suggestions', () => {
|
|||
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
|
||||
const currentState: XYState = {
|
||||
legend: { isVisible: true, position: 'bottom' },
|
||||
fittingFunction: 'None',
|
||||
preferredSeriesType: 'bar',
|
||||
layers: [
|
||||
{
|
||||
|
@ -440,6 +443,7 @@ describe('xy_suggestions', () => {
|
|||
const currentState: XYState = {
|
||||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price'],
|
||||
|
@ -474,6 +478,7 @@ describe('xy_suggestions', () => {
|
|||
const currentState: XYState = {
|
||||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price', 'quantity'],
|
||||
|
@ -512,6 +517,7 @@ describe('xy_suggestions', () => {
|
|||
const currentState: XYState = {
|
||||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price'],
|
||||
|
@ -551,6 +557,7 @@ describe('xy_suggestions', () => {
|
|||
const currentState: XYState = {
|
||||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price', 'quantity'],
|
||||
|
|
|
@ -402,6 +402,7 @@ function buildSuggestion({
|
|||
|
||||
const state: State = {
|
||||
legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right },
|
||||
fittingFunction: currentState?.fittingFunction || 'None',
|
||||
preferredSeriesType: seriesType,
|
||||
layers: [...keptLayers, newLayer],
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Position } from '@elastic/charts';
|
|||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getSuggestions } from './xy_suggestions';
|
||||
import { DimensionEditor, LayerContextMenu } from './xy_config_panel';
|
||||
import { LayerContextMenu, XyToolbar, DimensionEditor } from './xy_config_panel';
|
||||
import { Visualization, OperationMetadata, VisualizationType } from '../types';
|
||||
import { State, PersistableState, SeriesType, visualizationTypes, LayerConfig } from './types';
|
||||
import chartBarStackedSVG from '../assets/chart_bar_stacked.svg';
|
||||
|
@ -264,6 +264,15 @@ export const xyVisualization: Visualization<State, PersistableState> = {
|
|||
);
|
||||
},
|
||||
|
||||
renderToolbar(domElement, props) {
|
||||
render(
|
||||
<I18nProvider>
|
||||
<XyToolbar {...props} />
|
||||
</I18nProvider>,
|
||||
domElement
|
||||
);
|
||||
},
|
||||
|
||||
renderDimensionEditor(domElement, props) {
|
||||
render(
|
||||
<I18nProvider>
|
||||
|
|
Loading…
Reference in a new issue