[Lens] Add styling options for x and y axes on the settings popover (#71829)
* [Lens] Add styling options for x axis on the settings popover * ts related changes * Changes to the popover's design and y-axis implementatin * fix types and add unit tests * Add extra translations * Fix functional test and change the logic of the yTitle * fixes * fix showTitle settings bug * Fix ticklabels bug on y axes * fix some tests * Change the user flow on x and y titles on settings popover and enable the gridlines by default * disable linter warning * PR Comments * Add a comment to callback to explain the decision to listen only to open changes Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
75b8a3cb71
commit
0cfc7b464c
|
@ -8,6 +8,25 @@ Object {
|
|||
"fittingFunction": Array [
|
||||
"Carry",
|
||||
],
|
||||
"gridlinesVisibilitySettings": Array [
|
||||
Object {
|
||||
"chain": Array [
|
||||
Object {
|
||||
"arguments": Object {
|
||||
"x": Array [
|
||||
false,
|
||||
],
|
||||
"y": Array [
|
||||
true,
|
||||
],
|
||||
},
|
||||
"function": "lens_xy_gridlinesConfig",
|
||||
"type": "function",
|
||||
},
|
||||
],
|
||||
"type": "expression",
|
||||
},
|
||||
],
|
||||
"layers": Array [
|
||||
Object {
|
||||
"chain": Array [
|
||||
|
@ -73,11 +92,36 @@ Object {
|
|||
"type": "expression",
|
||||
},
|
||||
],
|
||||
"showXAxisTitle": Array [
|
||||
true,
|
||||
],
|
||||
"showYAxisTitle": Array [
|
||||
true,
|
||||
],
|
||||
"tickLabelsVisibilitySettings": Array [
|
||||
Object {
|
||||
"chain": Array [
|
||||
Object {
|
||||
"arguments": Object {
|
||||
"x": Array [
|
||||
false,
|
||||
],
|
||||
"y": Array [
|
||||
true,
|
||||
],
|
||||
},
|
||||
"function": "lens_xy_tickLabelsConfig",
|
||||
"type": "function",
|
||||
},
|
||||
],
|
||||
"type": "expression",
|
||||
},
|
||||
],
|
||||
"xTitle": Array [
|
||||
"col_a",
|
||||
"",
|
||||
],
|
||||
"yTitle": Array [
|
||||
"col_b",
|
||||
"",
|
||||
],
|
||||
},
|
||||
"function": "lens_xy_chart",
|
||||
|
|
|
@ -20,9 +20,14 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
gridLineStyle={
|
||||
Object {
|
||||
"strokeWidth": 2,
|
||||
}
|
||||
}
|
||||
id="x"
|
||||
position="bottom"
|
||||
showGridLines={false}
|
||||
showGridLines={true}
|
||||
tickFormat={[Function]}
|
||||
title="c"
|
||||
/>
|
||||
|
@ -146,9 +151,14 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
|||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
gridLineStyle={
|
||||
Object {
|
||||
"strokeWidth": 2,
|
||||
}
|
||||
}
|
||||
id="x"
|
||||
position="bottom"
|
||||
showGridLines={false}
|
||||
showGridLines={true}
|
||||
tickFormat={[Function]}
|
||||
title="c"
|
||||
/>
|
||||
|
@ -262,9 +272,14 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
|||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
gridLineStyle={
|
||||
Object {
|
||||
"strokeWidth": 2,
|
||||
}
|
||||
}
|
||||
id="x"
|
||||
position="left"
|
||||
showGridLines={false}
|
||||
showGridLines={true}
|
||||
tickFormat={[Function]}
|
||||
title="c"
|
||||
/>
|
||||
|
@ -378,9 +393,14 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
gridLineStyle={
|
||||
Object {
|
||||
"strokeWidth": 2,
|
||||
}
|
||||
}
|
||||
id="x"
|
||||
position="bottom"
|
||||
showGridLines={false}
|
||||
showGridLines={true}
|
||||
tickFormat={[Function]}
|
||||
title="c"
|
||||
/>
|
||||
|
@ -504,9 +524,14 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
|
|||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
gridLineStyle={
|
||||
Object {
|
||||
"strokeWidth": 2,
|
||||
}
|
||||
}
|
||||
id="x"
|
||||
position="bottom"
|
||||
showGridLines={false}
|
||||
showGridLines={true}
|
||||
tickFormat={[Function]}
|
||||
title="c"
|
||||
/>
|
||||
|
@ -628,9 +653,14 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
|||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
gridLineStyle={
|
||||
Object {
|
||||
"strokeWidth": 2,
|
||||
}
|
||||
}
|
||||
id="x"
|
||||
position="bottom"
|
||||
showGridLines={false}
|
||||
showGridLines={true}
|
||||
tickFormat={[Function]}
|
||||
title="c"
|
||||
/>
|
||||
|
@ -752,9 +782,14 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
|
|||
}
|
||||
/>
|
||||
<Connect(SpecInstance)
|
||||
gridLineStyle={
|
||||
Object {
|
||||
"strokeWidth": 2,
|
||||
}
|
||||
}
|
||||
id="x"
|
||||
position="left"
|
||||
showGridLines={false}
|
||||
showGridLines={true}
|
||||
tickFormat={[Function]}
|
||||
title="c"
|
||||
/>
|
||||
|
|
|
@ -10,7 +10,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, layerConfig, yAxisConfig } from './types';
|
||||
import { legendConfig, layerConfig, yAxisConfig, tickLabelsConfig, gridlinesConfig } from './types';
|
||||
import { EditorFrameSetup, FormatFactory } from '../types';
|
||||
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
|
||||
|
||||
|
@ -39,6 +39,8 @@ export class XyVisualization {
|
|||
) {
|
||||
expressions.registerFunction(() => legendConfig);
|
||||
expressions.registerFunction(() => yAxisConfig);
|
||||
expressions.registerFunction(() => tickLabelsConfig);
|
||||
expressions.registerFunction(() => gridlinesConfig);
|
||||
expressions.registerFunction(() => layerConfig);
|
||||
expressions.registerFunction(() => xyChart);
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ describe('#toExpression', () => {
|
|||
legend: { position: Position.Bottom, isVisible: true },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'Carry',
|
||||
tickLabelsVisibilitySettings: { x: false, y: true },
|
||||
gridlinesVisibilitySettings: { x: false, y: true },
|
||||
layers: [
|
||||
{
|
||||
layerId: 'first',
|
||||
|
@ -77,6 +79,27 @@ describe('#toExpression', () => {
|
|||
).toEqual('None');
|
||||
});
|
||||
|
||||
it('should default the showXAxisTitle and showYAxisTitle to true', () => {
|
||||
const expression = 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;
|
||||
expect(expression.chain[0].arguments.showXAxisTitle[0]).toBe(true);
|
||||
expect(expression.chain[0].arguments.showYAxisTitle[0]).toBe(true);
|
||||
});
|
||||
|
||||
it('should not generate an expression when missing x', () => {
|
||||
expect(
|
||||
xyVisualization.toExpression(
|
||||
|
@ -140,8 +163,8 @@ describe('#toExpression', () => {
|
|||
expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('b');
|
||||
expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('c');
|
||||
expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('d');
|
||||
expect(expression.chain[0].arguments.xTitle).toEqual(['col_a']);
|
||||
expect(expression.chain[0].arguments.yTitle).toEqual(['col_b']);
|
||||
expect(expression.chain[0].arguments.xTitle).toEqual(['']);
|
||||
expect(expression.chain[0].arguments.yTitle).toEqual(['']);
|
||||
expect(
|
||||
(expression.chain[0].arguments.layers[0] as Ast).chain[0].arguments.columnToLabel
|
||||
).toEqual([
|
||||
|
@ -152,4 +175,54 @@ describe('#toExpression', () => {
|
|||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should default the tick labels visibility settings to true', () => {
|
||||
const expression = 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;
|
||||
expect(
|
||||
(expression.chain[0].arguments.tickLabelsVisibilitySettings[0] as Ast).chain[0].arguments
|
||||
).toEqual({
|
||||
x: [true],
|
||||
y: [true],
|
||||
});
|
||||
});
|
||||
|
||||
it('should default the gridlines visibility settings to true', () => {
|
||||
const expression = 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;
|
||||
expect(
|
||||
(expression.chain[0].arguments.gridlinesVisibilitySettings[0] as Ast).chain[0].arguments
|
||||
).toEqual({
|
||||
x: [true],
|
||||
y: [true],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,28 +13,6 @@ interface ValidLayer extends LayerConfig {
|
|||
xAccessor: NonNullable<LayerConfig['xAccessor']>;
|
||||
}
|
||||
|
||||
function xyTitles(layer: LayerConfig, frame: FramePublicAPI) {
|
||||
const defaults = {
|
||||
xTitle: 'x',
|
||||
yTitle: 'y',
|
||||
};
|
||||
|
||||
if (!layer || !layer.accessors.length) {
|
||||
return defaults;
|
||||
}
|
||||
const datasource = frame.datasourceLayers[layer.layerId];
|
||||
if (!datasource) {
|
||||
return defaults;
|
||||
}
|
||||
const x = layer.xAccessor ? datasource.getOperationForColumnId(layer.xAccessor) : null;
|
||||
const y = layer.accessors[0] ? datasource.getOperationForColumnId(layer.accessors[0]) : null;
|
||||
|
||||
return {
|
||||
xTitle: x ? x.label : defaults.xTitle,
|
||||
yTitle: y ? y.label : defaults.yTitle,
|
||||
};
|
||||
}
|
||||
|
||||
export const toExpression = (state: State, frame: FramePublicAPI): Ast | null => {
|
||||
if (!state || !state.layers.length) {
|
||||
return null;
|
||||
|
@ -52,7 +30,7 @@ export const toExpression = (state: State, frame: FramePublicAPI): Ast | null =>
|
|||
});
|
||||
});
|
||||
|
||||
return buildExpression(state, metadata, frame, xyTitles(state.layers[0], frame));
|
||||
return buildExpression(state, metadata, frame);
|
||||
};
|
||||
|
||||
export function toPreviewExpression(state: State, frame: FramePublicAPI) {
|
||||
|
@ -99,8 +77,7 @@ export function getScaleType(metadata: OperationMetadata | null, defaultScale: S
|
|||
export const buildExpression = (
|
||||
state: State,
|
||||
metadata: Record<string, Record<string, OperationMetadata | null>>,
|
||||
frame?: FramePublicAPI,
|
||||
{ xTitle, yTitle }: { xTitle: string; yTitle: string } = { xTitle: '', yTitle: '' }
|
||||
frame?: FramePublicAPI
|
||||
): Ast | null => {
|
||||
const validLayers = state.layers.filter((layer): layer is ValidLayer =>
|
||||
Boolean(layer.xAccessor && layer.accessors.length)
|
||||
|
@ -116,8 +93,8 @@ export const buildExpression = (
|
|||
type: 'function',
|
||||
function: 'lens_xy_chart',
|
||||
arguments: {
|
||||
xTitle: [xTitle],
|
||||
yTitle: [yTitle],
|
||||
xTitle: [state.xTitle || ''],
|
||||
yTitle: [state.yTitle || ''],
|
||||
legend: [
|
||||
{
|
||||
type: 'expression',
|
||||
|
@ -137,6 +114,38 @@ export const buildExpression = (
|
|||
},
|
||||
],
|
||||
fittingFunction: [state.fittingFunction || 'None'],
|
||||
showXAxisTitle: [state.showXAxisTitle ?? true],
|
||||
showYAxisTitle: [state.showYAxisTitle ?? true],
|
||||
tickLabelsVisibilitySettings: [
|
||||
{
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'lens_xy_tickLabelsConfig',
|
||||
arguments: {
|
||||
x: [state?.tickLabelsVisibilitySettings?.x ?? true],
|
||||
y: [state?.tickLabelsVisibilitySettings?.y ?? true],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
gridlinesVisibilitySettings: [
|
||||
{
|
||||
type: 'expression',
|
||||
chain: [
|
||||
{
|
||||
type: 'function',
|
||||
function: 'lens_xy_gridlinesConfig',
|
||||
arguments: {
|
||||
x: [state?.gridlinesVisibilitySettings?.x ?? true],
|
||||
y: [state?.gridlinesVisibilitySettings?.y ?? true],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
layers: validLayers.map((layer) => {
|
||||
const columnToLabel: Record<string, string> = {};
|
||||
|
||||
|
|
|
@ -75,6 +75,81 @@ export const legendConfig: ExpressionFunctionDefinition<
|
|||
},
|
||||
};
|
||||
|
||||
export interface AxesSettingsConfig {
|
||||
x: boolean;
|
||||
y: boolean;
|
||||
}
|
||||
|
||||
type TickLabelsConfigResult = AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' };
|
||||
|
||||
export const tickLabelsConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_tickLabelsConfig',
|
||||
null,
|
||||
AxesSettingsConfig,
|
||||
TickLabelsConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_tickLabelsConfig',
|
||||
aliases: [],
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
help: `Configure the xy chart's tick labels appearance`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
x: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.xAxisTickLabels.help', {
|
||||
defaultMessage: 'Specifies whether or not the tick labels of the x-axis are visible.',
|
||||
}),
|
||||
},
|
||||
y: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yAxisTickLabels.help', {
|
||||
defaultMessage: 'Specifies whether or not the tick labels of the y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: AxesSettingsConfig) {
|
||||
return {
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
type GridlinesConfigResult = AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' };
|
||||
|
||||
export const gridlinesConfig: ExpressionFunctionDefinition<
|
||||
'lens_xy_gridlinesConfig',
|
||||
null,
|
||||
AxesSettingsConfig,
|
||||
GridlinesConfigResult
|
||||
> = {
|
||||
name: 'lens_xy_gridlinesConfig',
|
||||
aliases: [],
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
help: `Configure the xy chart's gridlines appearance`,
|
||||
inputTypes: ['null'],
|
||||
args: {
|
||||
x: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.xAxisGridlines.help', {
|
||||
defaultMessage: 'Specifies whether or not the gridlines of the x-axis are visible.',
|
||||
}),
|
||||
},
|
||||
y: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.yAxisgridlines.help', {
|
||||
defaultMessage: 'Specifies whether or not the gridlines of the y-axis are visible.',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: function fn(input: unknown, args: AxesSettingsConfig) {
|
||||
return {
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
...args,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
interface AxisConfig {
|
||||
title: string;
|
||||
hide?: boolean;
|
||||
|
@ -243,6 +318,10 @@ export interface XYArgs {
|
|||
legend: LegendConfig & { type: 'lens_xy_legendConfig' };
|
||||
layers: LayerArgs[];
|
||||
fittingFunction?: FittingFunction;
|
||||
showXAxisTitle?: boolean;
|
||||
showYAxisTitle?: boolean;
|
||||
tickLabelsVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' };
|
||||
gridlinesVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' };
|
||||
}
|
||||
|
||||
// Persisted parts of the state
|
||||
|
@ -251,6 +330,12 @@ export interface XYState {
|
|||
legend: LegendConfig;
|
||||
fittingFunction?: FittingFunction;
|
||||
layers: LayerConfig[];
|
||||
xTitle?: string;
|
||||
yTitle?: string;
|
||||
showXAxisTitle?: boolean;
|
||||
showYAxisTitle?: boolean;
|
||||
tickLabelsVisibilitySettings?: AxesSettingsConfig;
|
||||
gridlinesVisibilitySettings?: AxesSettingsConfig;
|
||||
}
|
||||
|
||||
export type State = XYState;
|
||||
|
|
|
@ -109,7 +109,6 @@ describe('XY Config panels', () => {
|
|||
|
||||
it('should disable the select if there is no unstacked area or line series', () => {
|
||||
const state = testState();
|
||||
|
||||
const component = shallow(
|
||||
<XyToolbar
|
||||
frame={frame}
|
||||
|
@ -127,5 +126,73 @@ describe('XY Config panels', () => {
|
|||
|
||||
expect(component.find(EuiSuperSelect).prop('disabled')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should show the values of the X and Y axes titles on the corresponding input text', () => {
|
||||
const state = testState();
|
||||
const component = shallow(
|
||||
<XyToolbar
|
||||
frame={frame}
|
||||
setState={jest.fn()}
|
||||
state={{
|
||||
...state,
|
||||
xTitle: 'My custom X axis title',
|
||||
yTitle: 'My custom Y axis title',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find('[data-test-subj="lnsXAxisTitle"]').prop('value')).toBe(
|
||||
'My custom X axis title'
|
||||
);
|
||||
expect(component.find('[data-test-subj="lnsYAxisTitle"]').prop('value')).toBe(
|
||||
'My custom Y axis title'
|
||||
);
|
||||
});
|
||||
|
||||
it('should disable the input texts if the switch is off', () => {
|
||||
const state = testState();
|
||||
const component = shallow(
|
||||
<XyToolbar
|
||||
frame={frame}
|
||||
setState={jest.fn()}
|
||||
state={{
|
||||
...state,
|
||||
showXAxisTitle: false,
|
||||
showYAxisTitle: false,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find('[data-test-subj="lnsXAxisTitle"]').prop('disabled')).toBe(true);
|
||||
expect(component.find('[data-test-subj="lnsYAxisTitle"]').prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('has the tick labels buttons enabled', () => {
|
||||
const state = testState();
|
||||
const component = shallow(<XyToolbar frame={frame} setState={jest.fn()} state={state} />);
|
||||
|
||||
const options = component
|
||||
.find('[data-test-subj="lnsTickLabelsSettings"]')
|
||||
.prop('options') as EuiButtonGroupProps['options'];
|
||||
|
||||
expect(options!.map(({ label }) => label)).toEqual(['X-axis', 'Y-axis']);
|
||||
|
||||
const selections = component
|
||||
.find('[data-test-subj="lnsTickLabelsSettings"]')
|
||||
.prop('idToSelectedMap');
|
||||
|
||||
expect(selections!).toEqual({ x: true, y: true });
|
||||
});
|
||||
|
||||
it('has the gridlines buttons enabled', () => {
|
||||
const state = testState();
|
||||
const component = shallow(<XyToolbar frame={frame} setState={jest.fn()} state={state} />);
|
||||
|
||||
const selections = component
|
||||
.find('[data-test-subj="lnsGridlinesSettings"]')
|
||||
.prop('idToSelectedMap');
|
||||
|
||||
expect(selections!).toEqual({ x: true, y: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import './xy_config_panel.scss';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Position } from '@elastic/charts';
|
||||
import { debounce } from 'lodash';
|
||||
|
@ -24,14 +24,17 @@ import {
|
|||
EuiColorPickerProps,
|
||||
EuiToolTip,
|
||||
EuiIcon,
|
||||
EuiFieldText,
|
||||
EuiSwitch,
|
||||
EuiHorizontalRule,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
VisualizationLayerWidgetProps,
|
||||
VisualizationDimensionEditorProps,
|
||||
VisualizationToolbarProps,
|
||||
} from '../types';
|
||||
import { State, SeriesType, visualizationTypes, YAxisMode } from './types';
|
||||
import { State, SeriesType, visualizationTypes, YAxisMode, AxesSettingsConfig } from './types';
|
||||
import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers';
|
||||
import { trackUiEvent } from '../lens_ui_telemetry';
|
||||
import { fittingFunctionDefinitions } from './fitting_functions';
|
||||
|
@ -118,14 +121,117 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps<State>) {
|
|||
}
|
||||
|
||||
export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
||||
const axes = [
|
||||
{
|
||||
id: 'x',
|
||||
label: 'X-axis',
|
||||
},
|
||||
{
|
||||
id: 'y',
|
||||
label: 'Y-axis',
|
||||
},
|
||||
];
|
||||
|
||||
const { frame, state, setState } = props;
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const hasNonBarSeries = props.state?.layers.some(
|
||||
const hasNonBarSeries = state?.layers.some(
|
||||
(layer) => layer.seriesType === 'line' || layer.seriesType === 'area'
|
||||
);
|
||||
|
||||
const [xAxisTitle, setXAxisTitle] = useState(state?.xTitle);
|
||||
const [yAxisTitle, setYAxisTitle] = useState(state?.yTitle);
|
||||
|
||||
const xyTitles = useCallback(() => {
|
||||
const defaults = {
|
||||
xTitle: xAxisTitle,
|
||||
yTitle: yAxisTitle,
|
||||
};
|
||||
const layer = state?.layers[0];
|
||||
if (!layer || !layer.accessors.length) {
|
||||
return defaults;
|
||||
}
|
||||
const datasource = frame.datasourceLayers[layer.layerId];
|
||||
if (!datasource) {
|
||||
return defaults;
|
||||
}
|
||||
const x = layer.xAccessor ? datasource.getOperationForColumnId(layer.xAccessor) : null;
|
||||
const y = layer.accessors[0] ? datasource.getOperationForColumnId(layer.accessors[0]) : null;
|
||||
|
||||
return {
|
||||
xTitle: defaults.xTitle || x?.label,
|
||||
yTitle: defaults.yTitle || y?.label,
|
||||
};
|
||||
/* We want this callback to run only if open changes its state. What we want to accomplish here is to give the user a better UX.
|
||||
By default these input fields have the axis legends. If the user changes the input text, the axis legends should also change.
|
||||
BUT if the user cleans up the input text, it should remain empty until the user closes and reopens the panel.
|
||||
In that case, the default axes legend should appear. */
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
xTitle,
|
||||
yTitle,
|
||||
}: { xTitle: string | undefined; yTitle: string | undefined } = xyTitles();
|
||||
setXAxisTitle(xTitle);
|
||||
setYAxisTitle(yTitle);
|
||||
}, [xyTitles]);
|
||||
|
||||
const onXTitleChange = (value: string): void => {
|
||||
setXAxisTitle(value);
|
||||
setState({ ...state, xTitle: value });
|
||||
};
|
||||
|
||||
const onYTitleChange = (value: string): void => {
|
||||
setYAxisTitle(value);
|
||||
setState({ ...state, yTitle: value });
|
||||
};
|
||||
|
||||
type AxesSettingsConfigKeys = keyof AxesSettingsConfig;
|
||||
|
||||
const tickLabelsVisibilitySettings = {
|
||||
x: state?.tickLabelsVisibilitySettings?.x ?? true,
|
||||
y: state?.tickLabelsVisibilitySettings?.y ?? true,
|
||||
};
|
||||
|
||||
const onTickLabelsVisibilitySettingsChange = (optionId: string): void => {
|
||||
const id = optionId as AxesSettingsConfigKeys;
|
||||
const newTickLabelsVisibilitySettings = {
|
||||
...tickLabelsVisibilitySettings,
|
||||
...{
|
||||
[id]: !tickLabelsVisibilitySettings[id],
|
||||
},
|
||||
};
|
||||
setState({
|
||||
...state,
|
||||
tickLabelsVisibilitySettings: newTickLabelsVisibilitySettings,
|
||||
});
|
||||
};
|
||||
|
||||
const gridlinesVisibilitySettings = {
|
||||
x: state?.gridlinesVisibilitySettings?.x ?? true,
|
||||
y: state?.gridlinesVisibilitySettings?.y ?? true,
|
||||
};
|
||||
|
||||
const onGridlinesVisibilitySettingsChange = (optionId: string): void => {
|
||||
const id = optionId as AxesSettingsConfigKeys;
|
||||
const newGridlinesVisibilitySettings = {
|
||||
...gridlinesVisibilitySettings,
|
||||
...{
|
||||
[id]: !gridlinesVisibilitySettings[id],
|
||||
},
|
||||
};
|
||||
setState({
|
||||
...state,
|
||||
gridlinesVisibilitySettings: newGridlinesVisibilitySettings,
|
||||
});
|
||||
};
|
||||
|
||||
const legendMode =
|
||||
props.state?.legend.isVisible && !props.state?.legend.showSingleSeries
|
||||
state?.legend.isVisible && !state?.legend.showSingleSeries
|
||||
? 'auto'
|
||||
: !props.state?.legend.isVisible
|
||||
: !state?.legend.isVisible
|
||||
? 'hide'
|
||||
: 'show';
|
||||
return (
|
||||
|
@ -183,8 +289,8 @@ export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
|||
inputDisplay: title,
|
||||
};
|
||||
})}
|
||||
valueOfSelected={props.state?.fittingFunction || 'None'}
|
||||
onChange={(value) => props.setState({ ...props.state, fittingFunction: value })}
|
||||
valueOfSelected={state?.fittingFunction || 'None'}
|
||||
onChange={(value) => setState({ ...state, fittingFunction: value })}
|
||||
itemLayoutAlign="top"
|
||||
hasDividers
|
||||
/>
|
||||
|
@ -209,19 +315,19 @@ export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
|||
onChange={(optionId) => {
|
||||
const newMode = legendOptions.find(({ id }) => id === optionId)!.value;
|
||||
if (newMode === 'auto') {
|
||||
props.setState({
|
||||
...props.state,
|
||||
legend: { ...props.state.legend, isVisible: true, showSingleSeries: false },
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, isVisible: true, showSingleSeries: false },
|
||||
});
|
||||
} else if (newMode === 'show') {
|
||||
props.setState({
|
||||
...props.state,
|
||||
legend: { ...props.state.legend, isVisible: true, showSingleSeries: true },
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, isVisible: true, showSingleSeries: true },
|
||||
});
|
||||
} else if (newMode === 'hide') {
|
||||
props.setState({
|
||||
...props.state,
|
||||
legend: { ...props.state.legend, isVisible: false, showSingleSeries: false },
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, isVisible: false, showSingleSeries: false },
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
@ -242,15 +348,130 @@ export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
|||
{ value: Position.Right, text: 'Right' },
|
||||
{ value: Position.Bottom, text: 'Bottom' },
|
||||
]}
|
||||
value={props.state?.legend.position}
|
||||
value={state?.legend.position}
|
||||
onChange={(e) => {
|
||||
props.setState({
|
||||
...props.state,
|
||||
legend: { ...props.state.legend, position: e.target.value as Position },
|
||||
setState({
|
||||
...state,
|
||||
legend: { ...state.legend, position: e.target.value as Position },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.xyChart.TickLabels', {
|
||||
defaultMessage: 'Tick Labels',
|
||||
})}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
name="lnsTickLabels"
|
||||
data-test-subj="lnsTickLabelsSettings"
|
||||
legend="Group of Tick Labels Visibility Settings"
|
||||
options={axes}
|
||||
idToSelectedMap={tickLabelsVisibilitySettings}
|
||||
onChange={(id) => onTickLabelsVisibilitySettingsChange(id)}
|
||||
buttonSize="compressed"
|
||||
isFullWidth
|
||||
type="multi"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={i18n.translate('xpack.lens.xyChart.Gridlines', {
|
||||
defaultMessage: 'Gridlines',
|
||||
})}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
name="lnsGridlines"
|
||||
data-test-subj="lnsGridlinesSettings"
|
||||
legend="Group of Gridlines Visibility Settings"
|
||||
options={axes}
|
||||
idToSelectedMap={gridlinesVisibilitySettings}
|
||||
onChange={(id) => onGridlinesVisibilitySettingsChange(id)}
|
||||
buttonSize="compressed"
|
||||
isFullWidth
|
||||
type="multi"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<EuiTitle size="xxs">
|
||||
<span>
|
||||
{i18n.translate('xpack.lens.xyChart.axisTitles', { defaultMessage: 'Axis titles' })}
|
||||
</span>
|
||||
</EuiTitle>
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>X-axis</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
compressed
|
||||
data-test-subj="lnsshowXAxisTitleSwitch"
|
||||
showLabel={false}
|
||||
label={i18n.translate('xpack.lens.xyChart.showXAxisTitleLabel', {
|
||||
defaultMessage: 'show X-axis Title',
|
||||
})}
|
||||
onChange={({ target }) =>
|
||||
setState({ ...state, showXAxisTitle: target.checked })
|
||||
}
|
||||
checked={state?.showXAxisTitle ?? true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="lnsXAxisTitle"
|
||||
compressed
|
||||
placeholder={i18n.translate('xpack.lens.xyChart.overwriteXaxis', {
|
||||
defaultMessage: 'Overwrite X-axis title',
|
||||
})}
|
||||
value={xAxisTitle || ''}
|
||||
disabled={state && 'showXAxisTitle' in state ? !state.showXAxisTitle : false}
|
||||
onChange={({ target }) => onXTitleChange(target.value)}
|
||||
aria-label={i18n.translate('xpack.lens.xyChart.overwriteXaxis', {
|
||||
defaultMessage: 'Overwrite X-axis title',
|
||||
})}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
display="columnCompressed"
|
||||
label={
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>Y-axis</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
compressed
|
||||
data-test-subj="lnsShowYAxisTitleSwitch"
|
||||
showLabel={false}
|
||||
label={i18n.translate('xpack.lens.xyChart.ShowYAxisTitleLabel', {
|
||||
defaultMessage: 'Show Y-axis Title',
|
||||
})}
|
||||
onChange={({ target }) =>
|
||||
setState({ ...state, showYAxisTitle: target.checked })
|
||||
}
|
||||
checked={state?.showYAxisTitle ?? true}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="lnsYAxisTitle"
|
||||
compressed
|
||||
placeholder={i18n.translate('xpack.lens.xyChart.overwriteYaxis', {
|
||||
defaultMessage: 'Overwrite Y-axis title',
|
||||
})}
|
||||
value={yAxisTitle || ''}
|
||||
disabled={state && 'showYAxisTitle' in state ? !state.showYAxisTitle : false}
|
||||
onChange={({ target }) => onYTitleChange(target.value)}
|
||||
aria-label={i18n.translate('xpack.lens.xyChart.overwriteYaxis', {
|
||||
defaultMessage: 'Overwrite Y-axis title',
|
||||
})}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -22,7 +22,16 @@ import { LensMultiTable } from '../types';
|
|||
import { KibanaDatatable, KibanaDatatableRow } from '../../../../../src/plugins/expressions/public';
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { XYArgs, LegendConfig, legendConfig, layerConfig, LayerArgs } from './types';
|
||||
import {
|
||||
XYArgs,
|
||||
LegendConfig,
|
||||
legendConfig,
|
||||
layerConfig,
|
||||
LayerArgs,
|
||||
AxesSettingsConfig,
|
||||
tickLabelsConfig,
|
||||
gridlinesConfig,
|
||||
} from './types';
|
||||
import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks';
|
||||
|
@ -211,6 +220,18 @@ const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
|
|||
isVisible: false,
|
||||
position: Position.Top,
|
||||
},
|
||||
showXAxisTitle: true,
|
||||
showYAxisTitle: true,
|
||||
tickLabelsVisibilitySettings: {
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
x: true,
|
||||
y: false,
|
||||
},
|
||||
gridlinesVisibilitySettings: {
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
x: true,
|
||||
y: false,
|
||||
},
|
||||
layers,
|
||||
});
|
||||
|
||||
|
@ -267,6 +288,34 @@ describe('xy_expression', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('tickLabelsConfig produces the correct arguments', () => {
|
||||
const args: AxesSettingsConfig = {
|
||||
x: true,
|
||||
y: false,
|
||||
};
|
||||
|
||||
const result = tickLabelsConfig.fn(null, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
...args,
|
||||
});
|
||||
});
|
||||
|
||||
test('gridlinesConfig produces the correct arguments', () => {
|
||||
const args: AxesSettingsConfig = {
|
||||
x: true,
|
||||
y: false,
|
||||
};
|
||||
|
||||
const result = gridlinesConfig.fn(null, args, createMockExecutionContext());
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
...args,
|
||||
});
|
||||
});
|
||||
|
||||
describe('xyChart', () => {
|
||||
test('it renders with the specified data and args', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
@ -1365,6 +1414,35 @@ describe('xy_expression', () => {
|
|||
expect(convertSpy).toHaveBeenCalledWith('I');
|
||||
});
|
||||
|
||||
test('it should not pass the formatter function to the x axis if the visibility of the tick labels is off', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
args.tickLabelsVisibilitySettings = { x: false, y: true, type: 'lens_xy_tickLabelsConfig' };
|
||||
|
||||
const instance = shallow(
|
||||
<XYChart
|
||||
data={{ ...data }}
|
||||
args={{ ...args }}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartsThemeService={chartsThemeService}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
|
||||
const tickFormatter = instance.find(Axis).first().prop('tickFormat');
|
||||
|
||||
if (!tickFormatter) {
|
||||
throw new Error('tickFormatter prop not found');
|
||||
}
|
||||
|
||||
tickFormatter('I');
|
||||
|
||||
expect(convertSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('it should remove invalid rows', () => {
|
||||
const data: LensMultiTable = {
|
||||
type: 'lens_multitable',
|
||||
|
@ -1400,6 +1478,16 @@ describe('xy_expression', () => {
|
|||
xTitle: '',
|
||||
yTitle: '',
|
||||
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
|
||||
tickLabelsVisibilitySettings: {
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
x: true,
|
||||
y: true,
|
||||
},
|
||||
gridlinesVisibilitySettings: {
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
x: true,
|
||||
y: false,
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
layerId: 'first',
|
||||
|
@ -1469,6 +1557,16 @@ describe('xy_expression', () => {
|
|||
xTitle: '',
|
||||
yTitle: '',
|
||||
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
|
||||
tickLabelsVisibilitySettings: {
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
x: true,
|
||||
y: false,
|
||||
},
|
||||
gridlinesVisibilitySettings: {
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
x: true,
|
||||
y: false,
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
layerId: 'first',
|
||||
|
@ -1525,6 +1623,16 @@ describe('xy_expression', () => {
|
|||
xTitle: '',
|
||||
yTitle: '',
|
||||
legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top },
|
||||
tickLabelsVisibilitySettings: {
|
||||
type: 'lens_xy_tickLabelsConfig',
|
||||
x: true,
|
||||
y: false,
|
||||
},
|
||||
gridlinesVisibilitySettings: {
|
||||
type: 'lens_xy_gridlinesConfig',
|
||||
x: true,
|
||||
y: false,
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
layerId: 'first',
|
||||
|
@ -1683,5 +1791,68 @@ describe('xy_expression', () => {
|
|||
|
||||
expect(component.find(LineSeries).prop('fit')).toEqual({ type: Fit.None });
|
||||
});
|
||||
|
||||
test('it should apply the xTitle if is specified', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
args.xTitle = 'My custom x-axis title';
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
data={{ ...data }}
|
||||
args={{ ...args }}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartsThemeService={chartsThemeService}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(Axis).at(0).prop('title')).toEqual('My custom x-axis title');
|
||||
});
|
||||
|
||||
test('it should hide the X axis title if the corresponding switch is off', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
args.showXAxisTitle = false;
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
data={{ ...data }}
|
||||
args={{ ...args }}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartsThemeService={chartsThemeService}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(Axis).at(0).prop('title')).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('it should show the X axis gridlines if the setting is on', () => {
|
||||
const { data, args } = sampleArgs();
|
||||
|
||||
args.gridlinesVisibilitySettings = { x: true, y: false, type: 'lens_xy_gridlinesConfig' };
|
||||
|
||||
const component = shallow(
|
||||
<XYChart
|
||||
data={{ ...data }}
|
||||
args={{ ...args }}
|
||||
formatFactory={getFormatSpy}
|
||||
timeZone="UTC"
|
||||
chartsThemeService={chartsThemeService}
|
||||
histogramBarTarget={50}
|
||||
onClickValue={onClickValue}
|
||||
onSelectRange={onSelectRange}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.find(Axis).at(0).prop('showGridLines')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -102,6 +102,30 @@ export const xyChart: ExpressionFunctionDefinition<
|
|||
defaultMessage: 'Define how missing values are treated',
|
||||
}),
|
||||
},
|
||||
tickLabelsVisibilitySettings: {
|
||||
types: ['lens_xy_tickLabelsConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', {
|
||||
defaultMessage: 'Show x and y axes tick labels',
|
||||
}),
|
||||
},
|
||||
gridlinesVisibilitySettings: {
|
||||
types: ['lens_xy_gridlinesConfig'],
|
||||
help: i18n.translate('xpack.lens.xyChart.gridlinesSettings.help', {
|
||||
defaultMessage: 'Show x and y axes gridlines',
|
||||
}),
|
||||
},
|
||||
showXAxisTitle: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.showXAxisTitle.help', {
|
||||
defaultMessage: 'Show x axis title',
|
||||
}),
|
||||
},
|
||||
showYAxisTitle: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('xpack.lens.xyChart.showYAxisTitle.help', {
|
||||
defaultMessage: 'Show y axis title',
|
||||
}),
|
||||
},
|
||||
layers: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
types: ['lens_xy_layer'] as any,
|
||||
|
@ -199,7 +223,7 @@ export function XYChart({
|
|||
onClickValue,
|
||||
onSelectRange,
|
||||
}: XYChartRenderProps) {
|
||||
const { legend, layers, fittingFunction } = args;
|
||||
const { legend, layers, fittingFunction, gridlinesVisibilitySettings } = args;
|
||||
const chartTheme = chartsThemeService.useChartsTheme();
|
||||
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
|
||||
|
||||
|
@ -237,7 +261,10 @@ export function XYChart({
|
|||
shouldRotate
|
||||
);
|
||||
|
||||
const xTitle = (xAxisColumn && xAxisColumn.name) || args.xTitle;
|
||||
const xTitle = args.xTitle || (xAxisColumn && xAxisColumn.name);
|
||||
const showXAxisTitle = args.showXAxisTitle ?? true;
|
||||
const showYAxisTitle = args.showYAxisTitle ?? true;
|
||||
const tickLabelsVisibilitySettings = args.tickLabelsVisibilitySettings || { x: true, y: true };
|
||||
|
||||
function calculateMinInterval() {
|
||||
// check all the tables to see if all of the rows have the same timestamp
|
||||
|
@ -279,6 +306,22 @@ export function XYChart({
|
|||
}
|
||||
: undefined;
|
||||
|
||||
const getYAxesTitles = (
|
||||
axisSeries: Array<{ layer: string; accessor: string }>,
|
||||
index: number
|
||||
) => {
|
||||
if (index > 0 && args.yTitle) return;
|
||||
return (
|
||||
args.yTitle ||
|
||||
axisSeries
|
||||
.map(
|
||||
(series) =>
|
||||
data.tables[series.layer].columns.find((column) => column.id === series.accessor)?.name
|
||||
)
|
||||
.filter((name) => Boolean(name))[0]
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Chart>
|
||||
<Settings
|
||||
|
@ -377,10 +420,11 @@ export function XYChart({
|
|||
<Axis
|
||||
id="x"
|
||||
position={shouldRotate ? Position.Left : Position.Bottom}
|
||||
title={xTitle}
|
||||
showGridLines={false}
|
||||
title={showXAxisTitle ? xTitle : undefined}
|
||||
showGridLines={gridlinesVisibilitySettings?.x}
|
||||
gridLineStyle={{ strokeWidth: 2 }}
|
||||
hide={filteredLayers[0].hide}
|
||||
tickFormat={(d) => xAxisFormatter.convert(d)}
|
||||
tickFormat={tickLabelsVisibilitySettings?.x ? (d) => xAxisFormatter.convert(d) : () => ''}
|
||||
/>
|
||||
|
||||
{yAxesConfiguration.map((axis, index) => (
|
||||
|
@ -389,18 +433,10 @@ export function XYChart({
|
|||
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}
|
||||
title={showYAxisTitle ? getYAxesTitles(axis.series, index) : undefined}
|
||||
showGridLines={gridlinesVisibilitySettings?.y}
|
||||
hide={filteredLayers[0].hide}
|
||||
tickFormat={(d) => axis.formatter.convert(d)}
|
||||
tickFormat={tickLabelsVisibilitySettings?.y ? (d) => axis.formatter.convert(d) : () => ''}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
|
|
@ -445,6 +445,10 @@ describe('xy_suggestions', () => {
|
|||
const currentState: XYState = {
|
||||
legend: { isVisible: true, position: 'bottom' },
|
||||
fittingFunction: 'None',
|
||||
showXAxisTitle: true,
|
||||
showYAxisTitle: true,
|
||||
gridlinesVisibilitySettings: { x: true, y: true },
|
||||
tickLabelsVisibilitySettings: { x: true, y: false },
|
||||
preferredSeriesType: 'bar',
|
||||
layers: [
|
||||
{
|
||||
|
@ -483,6 +487,10 @@ describe('xy_suggestions', () => {
|
|||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
showXAxisTitle: true,
|
||||
showYAxisTitle: true,
|
||||
gridlinesVisibilitySettings: { x: true, y: true },
|
||||
tickLabelsVisibilitySettings: { x: true, y: false },
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price', 'quantity'],
|
||||
|
@ -592,6 +600,10 @@ describe('xy_suggestions', () => {
|
|||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
showXAxisTitle: true,
|
||||
showYAxisTitle: true,
|
||||
gridlinesVisibilitySettings: { x: true, y: true },
|
||||
tickLabelsVisibilitySettings: { x: true, y: false },
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price', 'quantity'],
|
||||
|
@ -631,6 +643,10 @@ describe('xy_suggestions', () => {
|
|||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
showXAxisTitle: true,
|
||||
showYAxisTitle: true,
|
||||
gridlinesVisibilitySettings: { x: true, y: true },
|
||||
tickLabelsVisibilitySettings: { x: true, y: false },
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price'],
|
||||
|
@ -671,6 +687,10 @@ describe('xy_suggestions', () => {
|
|||
legend: { isVisible: true, position: 'bottom' },
|
||||
preferredSeriesType: 'bar',
|
||||
fittingFunction: 'None',
|
||||
showXAxisTitle: true,
|
||||
showYAxisTitle: true,
|
||||
gridlinesVisibilitySettings: { x: true, y: true },
|
||||
tickLabelsVisibilitySettings: { x: true, y: false },
|
||||
layers: [
|
||||
{
|
||||
accessors: ['price', 'quantity'],
|
||||
|
|
|
@ -407,6 +407,18 @@ function buildSuggestion({
|
|||
const state: State = {
|
||||
legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right },
|
||||
fittingFunction: currentState?.fittingFunction || 'None',
|
||||
xTitle: currentState?.xTitle,
|
||||
yTitle: currentState?.yTitle,
|
||||
showXAxisTitle: currentState?.showXAxisTitle ?? true,
|
||||
showYAxisTitle: currentState?.showYAxisTitle ?? true,
|
||||
tickLabelsVisibilitySettings: currentState?.tickLabelsVisibilitySettings || {
|
||||
x: true,
|
||||
y: true,
|
||||
},
|
||||
gridlinesVisibilitySettings: currentState?.gridlinesVisibilitySettings || {
|
||||
x: true,
|
||||
y: true,
|
||||
},
|
||||
preferredSeriesType: seriesType,
|
||||
layers: Object.keys(existingLayer).length ? keptLayers : [...keptLayers, newLayer],
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue