[Lens] New value labels config option for bar charts (#81776)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
03ee1a6476
commit
d3d3fa7bd2
|
@ -11,7 +11,7 @@ import { EuiIconLegend } from '../assets/legend';
|
||||||
|
|
||||||
const typeToIconMap: { [type: string]: string | IconType } = {
|
const typeToIconMap: { [type: string]: string | IconType } = {
|
||||||
legend: EuiIconLegend as IconType,
|
legend: EuiIconLegend as IconType,
|
||||||
values: 'visText',
|
values: 'number',
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ToolbarPopoverProps {
|
export interface ToolbarPopoverProps {
|
||||||
|
|
|
@ -279,6 +279,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
displayValueSettings={
|
||||||
|
Object {
|
||||||
|
"hideClippedValue": true,
|
||||||
|
"isAlternatingValueLabel": false,
|
||||||
|
"isValueContainedInElement": true,
|
||||||
|
"showValueLabel": false,
|
||||||
|
"valueFormatter": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
enableHistogramMode={false}
|
enableHistogramMode={false}
|
||||||
groupId="left"
|
groupId="left"
|
||||||
id="d-a"
|
id="d-a"
|
||||||
|
@ -334,6 +343,15 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
displayValueSettings={
|
||||||
|
Object {
|
||||||
|
"hideClippedValue": true,
|
||||||
|
"isAlternatingValueLabel": false,
|
||||||
|
"isValueContainedInElement": true,
|
||||||
|
"showValueLabel": false,
|
||||||
|
"valueFormatter": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
enableHistogramMode={false}
|
enableHistogramMode={false}
|
||||||
groupId="left"
|
groupId="left"
|
||||||
id="d-b"
|
id="d-b"
|
||||||
|
@ -457,6 +475,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
displayValueSettings={
|
||||||
|
Object {
|
||||||
|
"hideClippedValue": true,
|
||||||
|
"isAlternatingValueLabel": false,
|
||||||
|
"isValueContainedInElement": true,
|
||||||
|
"showValueLabel": false,
|
||||||
|
"valueFormatter": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
enableHistogramMode={false}
|
enableHistogramMode={false}
|
||||||
groupId="left"
|
groupId="left"
|
||||||
id="d-a"
|
id="d-a"
|
||||||
|
@ -512,6 +539,15 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
displayValueSettings={
|
||||||
|
Object {
|
||||||
|
"hideClippedValue": true,
|
||||||
|
"isAlternatingValueLabel": false,
|
||||||
|
"isValueContainedInElement": true,
|
||||||
|
"showValueLabel": false,
|
||||||
|
"valueFormatter": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
enableHistogramMode={false}
|
enableHistogramMode={false}
|
||||||
groupId="left"
|
groupId="left"
|
||||||
id="d-b"
|
id="d-b"
|
||||||
|
@ -1019,6 +1055,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
displayValueSettings={
|
||||||
|
Object {
|
||||||
|
"hideClippedValue": true,
|
||||||
|
"isAlternatingValueLabel": false,
|
||||||
|
"isValueContainedInElement": true,
|
||||||
|
"showValueLabel": false,
|
||||||
|
"valueFormatter": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
enableHistogramMode={false}
|
enableHistogramMode={false}
|
||||||
groupId="left"
|
groupId="left"
|
||||||
id="d-a"
|
id="d-a"
|
||||||
|
@ -1078,6 +1123,15 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
displayValueSettings={
|
||||||
|
Object {
|
||||||
|
"hideClippedValue": true,
|
||||||
|
"isAlternatingValueLabel": false,
|
||||||
|
"isValueContainedInElement": true,
|
||||||
|
"showValueLabel": false,
|
||||||
|
"valueFormatter": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
enableHistogramMode={false}
|
enableHistogramMode={false}
|
||||||
groupId="left"
|
groupId="left"
|
||||||
id="d-b"
|
id="d-b"
|
||||||
|
@ -1205,6 +1259,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
displayValueSettings={
|
||||||
|
Object {
|
||||||
|
"hideClippedValue": true,
|
||||||
|
"isAlternatingValueLabel": false,
|
||||||
|
"isValueContainedInElement": true,
|
||||||
|
"showValueLabel": false,
|
||||||
|
"valueFormatter": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
enableHistogramMode={false}
|
enableHistogramMode={false}
|
||||||
groupId="left"
|
groupId="left"
|
||||||
id="d-a"
|
id="d-a"
|
||||||
|
@ -1264,6 +1327,15 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
displayValueSettings={
|
||||||
|
Object {
|
||||||
|
"hideClippedValue": true,
|
||||||
|
"isAlternatingValueLabel": false,
|
||||||
|
"isValueContainedInElement": true,
|
||||||
|
"showValueLabel": false,
|
||||||
|
"valueFormatter": [Function],
|
||||||
|
}
|
||||||
|
}
|
||||||
enableHistogramMode={false}
|
enableHistogramMode={false}
|
||||||
groupId="left"
|
groupId="left"
|
||||||
id="d-b"
|
id="d-b"
|
||||||
|
|
|
@ -145,6 +145,9 @@ Object {
|
||||||
"title": Array [
|
"title": Array [
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
"valueLabels": Array [
|
||||||
|
"hide",
|
||||||
|
],
|
||||||
"xTitle": Array [
|
"xTitle": Array [
|
||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
|
|
|
@ -256,6 +256,7 @@ const createArgsWithLayers = (layers: LayerArgs[] = [sampleLayer]): XYArgs => ({
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
position: Position.Top,
|
position: Position.Top,
|
||||||
},
|
},
|
||||||
|
valueLabels: 'hide',
|
||||||
axisTitlesVisibilitySettings: {
|
axisTitlesVisibilitySettings: {
|
||||||
type: 'lens_xy_axisTitlesVisibilityConfig',
|
type: 'lens_xy_axisTitlesVisibilityConfig',
|
||||||
x: true,
|
x: true,
|
||||||
|
@ -1867,6 +1868,7 @@ describe('xy_expression', () => {
|
||||||
yTitle: '',
|
yTitle: '',
|
||||||
yRightTitle: '',
|
yRightTitle: '',
|
||||||
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
|
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
|
||||||
|
valueLabels: 'hide',
|
||||||
tickLabelsVisibilitySettings: {
|
tickLabelsVisibilitySettings: {
|
||||||
type: 'lens_xy_tickLabelsConfig',
|
type: 'lens_xy_tickLabelsConfig',
|
||||||
x: true,
|
x: true,
|
||||||
|
@ -1952,6 +1954,7 @@ describe('xy_expression', () => {
|
||||||
yTitle: '',
|
yTitle: '',
|
||||||
yRightTitle: '',
|
yRightTitle: '',
|
||||||
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
|
legend: { type: 'lens_xy_legendConfig', isVisible: false, position: Position.Top },
|
||||||
|
valueLabels: 'hide',
|
||||||
tickLabelsVisibilitySettings: {
|
tickLabelsVisibilitySettings: {
|
||||||
type: 'lens_xy_tickLabelsConfig',
|
type: 'lens_xy_tickLabelsConfig',
|
||||||
x: true,
|
x: true,
|
||||||
|
@ -2023,6 +2026,7 @@ describe('xy_expression', () => {
|
||||||
yTitle: '',
|
yTitle: '',
|
||||||
yRightTitle: '',
|
yRightTitle: '',
|
||||||
legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top },
|
legend: { type: 'lens_xy_legendConfig', isVisible: true, position: Position.Top },
|
||||||
|
valueLabels: 'hide',
|
||||||
tickLabelsVisibilitySettings: {
|
tickLabelsVisibilitySettings: {
|
||||||
type: 'lens_xy_tickLabelsConfig',
|
type: 'lens_xy_tickLabelsConfig',
|
||||||
x: true,
|
x: true,
|
||||||
|
|
|
@ -20,6 +20,10 @@ import {
|
||||||
GeometryValue,
|
GeometryValue,
|
||||||
XYChartSeriesIdentifier,
|
XYChartSeriesIdentifier,
|
||||||
StackMode,
|
StackMode,
|
||||||
|
RecursivePartial,
|
||||||
|
Theme,
|
||||||
|
VerticalAlignment,
|
||||||
|
HorizontalAlignment,
|
||||||
} from '@elastic/charts';
|
} from '@elastic/charts';
|
||||||
import { I18nProvider } from '@kbn/i18n/react';
|
import { I18nProvider } from '@kbn/i18n/react';
|
||||||
import {
|
import {
|
||||||
|
@ -131,6 +135,11 @@ export const xyChart: ExpressionFunctionDefinition<
|
||||||
defaultMessage: 'Define how missing values are treated',
|
defaultMessage: 'Define how missing values are treated',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
valueLabels: {
|
||||||
|
types: ['string'],
|
||||||
|
options: ['hide', 'inside'],
|
||||||
|
help: '',
|
||||||
|
},
|
||||||
tickLabelsVisibilitySettings: {
|
tickLabelsVisibilitySettings: {
|
||||||
types: ['lens_xy_tickLabelsConfig'],
|
types: ['lens_xy_tickLabelsConfig'],
|
||||||
help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', {
|
help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', {
|
||||||
|
@ -214,6 +223,40 @@ export const getXyChartRenderer = (dependencies: {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function mergeThemeWithValueLabelsStyling(
|
||||||
|
theme: RecursivePartial<Theme>,
|
||||||
|
valuesLabelMode: string = 'hide',
|
||||||
|
isHorizontal: boolean
|
||||||
|
) {
|
||||||
|
const VALUE_LABELS_MAX_FONTSIZE = 15;
|
||||||
|
const VALUE_LABELS_MIN_FONTSIZE = 10;
|
||||||
|
const VALUE_LABELS_VERTICAL_OFFSET = -10;
|
||||||
|
const VALUE_LABELS_HORIZONTAL_OFFSET = 10;
|
||||||
|
|
||||||
|
if (valuesLabelMode === 'hide') {
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...theme,
|
||||||
|
...{
|
||||||
|
barSeriesStyle: {
|
||||||
|
...theme.barSeriesStyle,
|
||||||
|
displayValue: {
|
||||||
|
fontSize: { min: VALUE_LABELS_MIN_FONTSIZE, max: VALUE_LABELS_MAX_FONTSIZE },
|
||||||
|
fill: { textInverted: true, textBorder: 2 },
|
||||||
|
alignment: isHorizontal
|
||||||
|
? {
|
||||||
|
vertical: VerticalAlignment.Middle,
|
||||||
|
}
|
||||||
|
: { horizontal: HorizontalAlignment.Center },
|
||||||
|
offsetX: isHorizontal ? VALUE_LABELS_HORIZONTAL_OFFSET : 0,
|
||||||
|
offsetY: isHorizontal ? 0 : VALUE_LABELS_VERTICAL_OFFSET,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getIconForSeriesType(seriesType: SeriesType): IconType {
|
function getIconForSeriesType(seriesType: SeriesType): IconType {
|
||||||
return visualizationTypes.find((c) => c.id === seriesType)!.icon || 'empty';
|
return visualizationTypes.find((c) => c.id === seriesType)!.icon || 'empty';
|
||||||
}
|
}
|
||||||
|
@ -254,7 +297,7 @@ export function XYChart({
|
||||||
onClickValue,
|
onClickValue,
|
||||||
onSelectRange,
|
onSelectRange,
|
||||||
}: XYChartRenderProps) {
|
}: XYChartRenderProps) {
|
||||||
const { legend, layers, fittingFunction, gridlinesVisibilitySettings } = args;
|
const { legend, layers, fittingFunction, gridlinesVisibilitySettings, valueLabels } = args;
|
||||||
const chartTheme = chartsThemeService.useChartsTheme();
|
const chartTheme = chartsThemeService.useChartsTheme();
|
||||||
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
|
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
|
||||||
|
|
||||||
|
@ -396,6 +439,16 @@ export function XYChart({
|
||||||
return style;
|
return style;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shouldShowValueLabels =
|
||||||
|
// No stacked bar charts
|
||||||
|
filteredLayers.every((layer) => !layer.seriesType.includes('stacked')) &&
|
||||||
|
// No histogram charts
|
||||||
|
!isHistogramViz;
|
||||||
|
|
||||||
|
const baseThemeWithMaybeValueLabels = !shouldShowValueLabels
|
||||||
|
? chartTheme
|
||||||
|
: mergeThemeWithValueLabelsStyling(chartTheme, valueLabels, shouldRotate);
|
||||||
|
|
||||||
const colorAssignments = getColorAssignments(args.layers, data, formatFactory);
|
const colorAssignments = getColorAssignments(args.layers, data, formatFactory);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -408,7 +461,7 @@ export function XYChart({
|
||||||
}
|
}
|
||||||
legendPosition={legend.position}
|
legendPosition={legend.position}
|
||||||
showLegendExtra={false}
|
showLegendExtra={false}
|
||||||
theme={chartTheme}
|
theme={baseThemeWithMaybeValueLabels}
|
||||||
baseTheme={chartBaseTheme}
|
baseTheme={chartBaseTheme}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
headerFormatter: (d) => safeXAccessorLabelRenderer(d.value),
|
headerFormatter: (d) => safeXAccessorLabelRenderer(d.value),
|
||||||
|
@ -613,6 +666,10 @@ export function XYChart({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const yAxis = yAxesConfiguration.find((axisConfiguration) =>
|
||||||
|
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
|
||||||
|
);
|
||||||
|
|
||||||
const seriesProps: SeriesSpec = {
|
const seriesProps: SeriesSpec = {
|
||||||
splitSeriesAccessors: splitAccessor ? [splitAccessor] : [],
|
splitSeriesAccessors: splitAccessor ? [splitAccessor] : [],
|
||||||
stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [],
|
stackAccessors: seriesType.includes('stacked') ? [xAccessor as string] : [],
|
||||||
|
@ -649,9 +706,7 @@ export function XYChart({
|
||||||
palette.params
|
palette.params
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
groupId: yAxesConfiguration.find((axisConfiguration) =>
|
groupId: yAxis?.groupId,
|
||||||
axisConfiguration.series.find((currentSeries) => currentSeries.accessor === accessor)
|
|
||||||
)?.groupId,
|
|
||||||
enableHistogramMode:
|
enableHistogramMode:
|
||||||
isHistogram &&
|
isHistogram &&
|
||||||
(seriesType.includes('stacked') || !splitAccessor) &&
|
(seriesType.includes('stacked') || !splitAccessor) &&
|
||||||
|
@ -723,7 +778,19 @@ export function XYChart({
|
||||||
case 'bar_horizontal':
|
case 'bar_horizontal':
|
||||||
case 'bar_horizontal_stacked':
|
case 'bar_horizontal_stacked':
|
||||||
case 'bar_horizontal_percentage_stacked':
|
case 'bar_horizontal_percentage_stacked':
|
||||||
return <BarSeries key={index} {...seriesProps} />;
|
const valueLabelsSettings = {
|
||||||
|
displayValueSettings: {
|
||||||
|
// This format double fixes two issues in elastic-chart
|
||||||
|
// * when rotating the chart, the formatter is not correctly picked
|
||||||
|
// * in some scenarios value labels are not strings, and this breaks the elastic-chart lib
|
||||||
|
valueFormatter: (d: unknown) => yAxis?.formatter?.convert(d) || '',
|
||||||
|
showValueLabel: shouldShowValueLabels && valueLabels !== 'hide',
|
||||||
|
isAlternatingValueLabel: false,
|
||||||
|
isValueContainedInElement: true,
|
||||||
|
hideClippedValue: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return <BarSeries key={index} {...seriesProps} {...valueLabelsSettings} />;
|
||||||
case 'area_stacked':
|
case 'area_stacked':
|
||||||
case 'area_percentage_stacked':
|
case 'area_percentage_stacked':
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
|
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
|
||||||
import { SeriesType, visualizationTypes, LayerConfig, YConfig } from './types';
|
import { FramePublicAPI } from '../types';
|
||||||
|
import { SeriesType, visualizationTypes, LayerConfig, YConfig, ValidLayer } from './types';
|
||||||
|
|
||||||
export function isHorizontalSeries(seriesType: SeriesType) {
|
export function isHorizontalSeries(seriesType: SeriesType) {
|
||||||
return (
|
return (
|
||||||
|
@ -37,3 +38,23 @@ export const getSeriesColor = (layer: LayerConfig, accessor: string) => {
|
||||||
layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null
|
layer?.yConfig?.find((yConfig: YConfig) => yConfig.forAccessor === accessor)?.color || null
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function hasHistogramSeries(
|
||||||
|
layers: ValidLayer[] = [],
|
||||||
|
datasourceLayers?: FramePublicAPI['datasourceLayers']
|
||||||
|
) {
|
||||||
|
if (!datasourceLayers) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const validLayers = layers.filter(({ accessors }) => accessors.length);
|
||||||
|
|
||||||
|
return validLayers.some(({ layerId, xAccessor }: ValidLayer) => {
|
||||||
|
const xAxisOperation = datasourceLayers[layerId].getOperationForColumnId(xAccessor);
|
||||||
|
return (
|
||||||
|
xAxisOperation &&
|
||||||
|
xAxisOperation.isBucketed &&
|
||||||
|
xAxisOperation.scale &&
|
||||||
|
xAxisOperation.scale !== 'ordinal'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ describe('#toExpression', () => {
|
||||||
xyVisualization.toExpression(
|
xyVisualization.toExpression(
|
||||||
{
|
{
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
fittingFunction: 'Carry',
|
fittingFunction: 'Carry',
|
||||||
tickLabelsVisibilitySettings: { x: false, yLeft: true, yRight: true },
|
tickLabelsVisibilitySettings: { x: false, yLeft: true, yRight: true },
|
||||||
|
@ -67,6 +68,7 @@ describe('#toExpression', () => {
|
||||||
(xyVisualization.toExpression(
|
(xyVisualization.toExpression(
|
||||||
{
|
{
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -87,6 +89,7 @@ describe('#toExpression', () => {
|
||||||
const expression = xyVisualization.toExpression(
|
const expression = xyVisualization.toExpression(
|
||||||
{
|
{
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -113,6 +116,7 @@ describe('#toExpression', () => {
|
||||||
const expression = xyVisualization.toExpression(
|
const expression = xyVisualization.toExpression(
|
||||||
{
|
{
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -136,6 +140,7 @@ describe('#toExpression', () => {
|
||||||
xyVisualization.toExpression(
|
xyVisualization.toExpression(
|
||||||
{
|
{
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -156,6 +161,7 @@ describe('#toExpression', () => {
|
||||||
const expression = xyVisualization.toExpression(
|
const expression = xyVisualization.toExpression(
|
||||||
{
|
{
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -191,6 +197,7 @@ describe('#toExpression', () => {
|
||||||
const expression = xyVisualization.toExpression(
|
const expression = xyVisualization.toExpression(
|
||||||
{
|
{
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -217,6 +224,7 @@ describe('#toExpression', () => {
|
||||||
const expression = xyVisualization.toExpression(
|
const expression = xyVisualization.toExpression(
|
||||||
{
|
{
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -238,4 +246,25 @@ describe('#toExpression', () => {
|
||||||
yRight: [true],
|
yRight: [true],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should correctly report the valueLabels visibility settings', () => {
|
||||||
|
const expression = xyVisualization.toExpression(
|
||||||
|
{
|
||||||
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'inside',
|
||||||
|
preferredSeriesType: 'bar',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
layerId: 'first',
|
||||||
|
seriesType: 'area',
|
||||||
|
splitAccessor: 'd',
|
||||||
|
xAccessor: 'a',
|
||||||
|
accessors: ['b', 'c'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
frame.datasourceLayers
|
||||||
|
) as Ast;
|
||||||
|
expect(expression.chain[0].arguments.valueLabels[0] as Ast).toEqual('inside');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,13 +7,9 @@
|
||||||
import { Ast } from '@kbn/interpreter/common';
|
import { Ast } from '@kbn/interpreter/common';
|
||||||
import { ScaleType } from '@elastic/charts';
|
import { ScaleType } from '@elastic/charts';
|
||||||
import { PaletteRegistry } from 'src/plugins/charts/public';
|
import { PaletteRegistry } from 'src/plugins/charts/public';
|
||||||
import { State, LayerConfig } from './types';
|
import { State, ValidLayer, LayerConfig } from './types';
|
||||||
import { OperationMetadata, DatasourcePublicAPI } from '../types';
|
import { OperationMetadata, DatasourcePublicAPI } from '../types';
|
||||||
|
|
||||||
interface ValidLayer extends LayerConfig {
|
|
||||||
xAccessor: NonNullable<LayerConfig['xAccessor']>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: LayerConfig) => {
|
export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: LayerConfig) => {
|
||||||
const originalOrder = datasource
|
const originalOrder = datasource
|
||||||
.getTableSpec()
|
.getTableSpec()
|
||||||
|
@ -60,6 +56,7 @@ export function toPreviewExpression(
|
||||||
...state.legend,
|
...state.legend,
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
},
|
},
|
||||||
|
valueLabels: 'hide',
|
||||||
},
|
},
|
||||||
datasourceLayers,
|
datasourceLayers,
|
||||||
paletteService,
|
paletteService,
|
||||||
|
@ -197,6 +194,7 @@ export const buildExpression = (
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
valueLabels: [state?.valueLabels || 'hide'],
|
||||||
layers: validLayers.map((layer) => {
|
layers: validLayers.map((layer) => {
|
||||||
const columnToLabel: Record<string, string> = {};
|
const columnToLabel: Record<string, string> = {};
|
||||||
|
|
||||||
|
|
|
@ -364,6 +364,8 @@ export type SeriesType =
|
||||||
|
|
||||||
export type YAxisMode = 'auto' | 'left' | 'right';
|
export type YAxisMode = 'auto' | 'left' | 'right';
|
||||||
|
|
||||||
|
export type ValueLabelConfig = 'hide' | 'inside' | 'outside';
|
||||||
|
|
||||||
export interface YConfig {
|
export interface YConfig {
|
||||||
forAccessor: string;
|
forAccessor: string;
|
||||||
axisMode?: YAxisMode;
|
axisMode?: YAxisMode;
|
||||||
|
@ -381,6 +383,10 @@ export interface LayerConfig {
|
||||||
palette?: PaletteOutput;
|
palette?: PaletteOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ValidLayer extends LayerConfig {
|
||||||
|
xAccessor: NonNullable<LayerConfig['xAccessor']>;
|
||||||
|
}
|
||||||
|
|
||||||
export type LayerArgs = LayerConfig & {
|
export type LayerArgs = LayerConfig & {
|
||||||
columnToLabel?: string; // Actually a JSON key-value pair
|
columnToLabel?: string; // Actually a JSON key-value pair
|
||||||
yScaleType: 'time' | 'linear' | 'log' | 'sqrt';
|
yScaleType: 'time' | 'linear' | 'log' | 'sqrt';
|
||||||
|
@ -398,6 +404,7 @@ export interface XYArgs {
|
||||||
yTitle: string;
|
yTitle: string;
|
||||||
yRightTitle: string;
|
yRightTitle: string;
|
||||||
legend: LegendConfig & { type: 'lens_xy_legendConfig' };
|
legend: LegendConfig & { type: 'lens_xy_legendConfig' };
|
||||||
|
valueLabels: ValueLabelConfig;
|
||||||
layers: LayerArgs[];
|
layers: LayerArgs[];
|
||||||
fittingFunction?: FittingFunction;
|
fittingFunction?: FittingFunction;
|
||||||
axisTitlesVisibilitySettings?: AxesSettingsConfig & {
|
axisTitlesVisibilitySettings?: AxesSettingsConfig & {
|
||||||
|
@ -411,6 +418,7 @@ export interface XYArgs {
|
||||||
export interface XYState {
|
export interface XYState {
|
||||||
preferredSeriesType: SeriesType;
|
preferredSeriesType: SeriesType;
|
||||||
legend: LegendConfig;
|
legend: LegendConfig;
|
||||||
|
valueLabels?: ValueLabelConfig;
|
||||||
fittingFunction?: FittingFunction;
|
fittingFunction?: FittingFunction;
|
||||||
layers: LayerConfig[];
|
layers: LayerConfig[];
|
||||||
xTitle?: string;
|
xTitle?: string;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'
|
||||||
function exampleState(): State {
|
function exampleState(): State {
|
||||||
return {
|
return {
|
||||||
legend: { position: Position.Bottom, isVisible: true },
|
legend: { position: Position.Bottom, isVisible: true },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -150,6 +151,7 @@ describe('xy_visualization', () => {
|
||||||
},
|
},
|
||||||
"preferredSeriesType": "bar_stacked",
|
"preferredSeriesType": "bar_stacked",
|
||||||
"title": "Empty XY chart",
|
"title": "Empty XY chart",
|
||||||
|
"valueLabels": "hide",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
|
@ -145,6 +145,7 @@ export const getXyVisualization = ({
|
||||||
state || {
|
state || {
|
||||||
title: 'Empty XY chart',
|
title: 'Empty XY chart',
|
||||||
legend: { isVisible: true, position: Position.Right },
|
legend: { isVisible: true, position: Position.Right },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: defaultSeriesType,
|
preferredSeriesType: defaultSeriesType,
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
.lnsXyToolbar__popover {
|
.lnsXyToolbar__popover {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ describe('XY Config panels', () => {
|
||||||
function testState(): State {
|
function testState(): State {
|
||||||
return {
|
return {
|
||||||
legend: { isVisible: true, position: Position.Right },
|
legend: { isVisible: true, position: Position.Right },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -115,8 +116,9 @@ describe('XY Config panels', () => {
|
||||||
expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry');
|
expect(component.find(EuiSuperSelect).prop('valueOfSelected')).toEqual('Carry');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should disable the popover if there is no area or line series', () => {
|
it('should show currently selected value labels display setting', () => {
|
||||||
const state = testState();
|
const state = testState();
|
||||||
|
|
||||||
const component = shallow(
|
const component = shallow(
|
||||||
<XyToolbar
|
<XyToolbar
|
||||||
frame={frame}
|
frame={frame}
|
||||||
|
@ -125,11 +127,148 @@ describe('XY Config panels', () => {
|
||||||
...state,
|
...state,
|
||||||
layers: [{ ...state.layers[0], seriesType: 'bar' }],
|
layers: [{ ...state.layers[0], seriesType: 'bar' }],
|
||||||
fittingFunction: 'Carry',
|
fittingFunction: 'Carry',
|
||||||
|
valueLabels: 'inside',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(component.find(ToolbarPopover).at(0).prop('isDisabled')).toEqual(true);
|
expect(component.find(EuiButtonGroup).prop('idSelected')).toEqual('value_labels_inside');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable the popover for stacked bar charts', () => {
|
||||||
|
const state = testState();
|
||||||
|
const component = shallow(
|
||||||
|
<XyToolbar
|
||||||
|
frame={frame}
|
||||||
|
setState={jest.fn()}
|
||||||
|
state={{
|
||||||
|
...state,
|
||||||
|
layers: [{ ...state.layers[0], seriesType: 'bar_stacked' }],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable the popover for percentage area charts', () => {
|
||||||
|
const state = testState();
|
||||||
|
const component = shallow(
|
||||||
|
<XyToolbar
|
||||||
|
frame={frame}
|
||||||
|
setState={jest.fn()}
|
||||||
|
state={{
|
||||||
|
...state,
|
||||||
|
layers: [{ ...state.layers[0], seriesType: 'area_percentage_stacked' }],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disabled the popover if there is histogram series', () => {
|
||||||
|
// make it detect an histogram series
|
||||||
|
frame.datasourceLayers.first.getOperationForColumnId = jest.fn().mockReturnValueOnce({
|
||||||
|
isBucketed: true,
|
||||||
|
scale: 'interval',
|
||||||
|
});
|
||||||
|
const state = testState();
|
||||||
|
const component = shallow(
|
||||||
|
<XyToolbar
|
||||||
|
frame={frame}
|
||||||
|
setState={jest.fn()}
|
||||||
|
state={{
|
||||||
|
...state,
|
||||||
|
layers: [{ ...state.layers[0] }],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.find(ToolbarPopover).prop('isDisabled')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the popover and display field enabled for bar and horizontal_bar series', () => {
|
||||||
|
const state = testState();
|
||||||
|
|
||||||
|
const component = shallow(
|
||||||
|
<XyToolbar
|
||||||
|
frame={frame}
|
||||||
|
setState={jest.fn()}
|
||||||
|
state={{
|
||||||
|
...state,
|
||||||
|
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }],
|
||||||
|
fittingFunction: 'Carry',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide the fitting option for bar series', () => {
|
||||||
|
const state = testState();
|
||||||
|
const component = shallow(
|
||||||
|
<XyToolbar
|
||||||
|
frame={frame}
|
||||||
|
setState={jest.fn()}
|
||||||
|
state={{
|
||||||
|
...state,
|
||||||
|
layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }],
|
||||||
|
fittingFunction: 'Carry',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.exists('[data-test-subj="lnsMissingValuesSelect"]')).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide in the popover the display option for area and line series', () => {
|
||||||
|
const state = testState();
|
||||||
|
const component = shallow(
|
||||||
|
<XyToolbar
|
||||||
|
frame={frame}
|
||||||
|
setState={jest.fn()}
|
||||||
|
state={{
|
||||||
|
...state,
|
||||||
|
layers: [{ ...state.layers[0], seriesType: 'area' }],
|
||||||
|
fittingFunction: 'Carry',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep the display option for bar series with multiple layers', () => {
|
||||||
|
frame.datasourceLayers = {
|
||||||
|
...frame.datasourceLayers,
|
||||||
|
second: createMockDatasource('test').publicAPIMock,
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = testState();
|
||||||
|
const component = shallow(
|
||||||
|
<XyToolbar
|
||||||
|
frame={frame}
|
||||||
|
setState={jest.fn()}
|
||||||
|
state={{
|
||||||
|
...state,
|
||||||
|
layers: [
|
||||||
|
{ ...state.layers[0], seriesType: 'bar' },
|
||||||
|
{
|
||||||
|
seriesType: 'bar',
|
||||||
|
layerId: 'second',
|
||||||
|
splitAccessor: 'baz',
|
||||||
|
xAccessor: 'foo',
|
||||||
|
accessors: ['bar'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fittingFunction: 'Carry',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(component.exists('[data-test-subj="lnsValueLabelsDisplay"]')).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should disable the popover if there is no right axis', () => {
|
it('should disable the popover if there is no right axis', () => {
|
||||||
|
|
|
@ -27,8 +27,20 @@ import {
|
||||||
VisualizationToolbarProps,
|
VisualizationToolbarProps,
|
||||||
VisualizationDimensionEditorProps,
|
VisualizationDimensionEditorProps,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { State, SeriesType, visualizationTypes, YAxisMode, AxesSettingsConfig } from './types';
|
import {
|
||||||
import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers';
|
State,
|
||||||
|
SeriesType,
|
||||||
|
visualizationTypes,
|
||||||
|
YAxisMode,
|
||||||
|
AxesSettingsConfig,
|
||||||
|
ValidLayer,
|
||||||
|
} from './types';
|
||||||
|
import {
|
||||||
|
isHorizontalChart,
|
||||||
|
isHorizontalSeries,
|
||||||
|
getSeriesColor,
|
||||||
|
hasHistogramSeries,
|
||||||
|
} from './state_helpers';
|
||||||
import { trackUiEvent } from '../lens_ui_telemetry';
|
import { trackUiEvent } from '../lens_ui_telemetry';
|
||||||
import { fittingFunctionDefinitions } from './fitting_functions';
|
import { fittingFunctionDefinitions } from './fitting_functions';
|
||||||
import { ToolbarPopover, LegendSettingsPopover } from '../shared_components';
|
import { ToolbarPopover, LegendSettingsPopover } from '../shared_components';
|
||||||
|
@ -74,6 +86,27 @@ const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label:
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const valueLabelsOptions: Array<{
|
||||||
|
id: string;
|
||||||
|
value: 'hide' | 'inside' | 'outside';
|
||||||
|
label: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
id: `value_labels_hide`,
|
||||||
|
value: 'hide',
|
||||||
|
label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.auto', {
|
||||||
|
defaultMessage: 'Hide',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `value_labels_inside`,
|
||||||
|
value: 'inside',
|
||||||
|
label: i18n.translate('xpack.lens.xyChart.valueLabelsVisibility.inside', {
|
||||||
|
defaultMessage: 'Show',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function LayerContextMenu(props: VisualizationLayerWidgetProps<State>) {
|
export function LayerContextMenu(props: VisualizationLayerWidgetProps<State>) {
|
||||||
const { state, layerId } = props;
|
const { state, layerId } = props;
|
||||||
const horizontalOnly = isHorizontalChart(state.layers);
|
const horizontalOnly = isHorizontalChart(state.layers);
|
||||||
|
@ -118,12 +151,24 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps<State>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
||||||
const { state, setState } = props;
|
const { state, setState, frame } = props;
|
||||||
|
|
||||||
const hasNonBarSeries = state?.layers.some(({ seriesType }) =>
|
const hasNonBarSeries = state?.layers.some(({ seriesType }) =>
|
||||||
['area_stacked', 'area', 'line'].includes(seriesType)
|
['area_stacked', 'area', 'line'].includes(seriesType)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hasBarNotStacked = state?.layers.some(({ seriesType }) =>
|
||||||
|
['bar', 'bar_horizontal'].includes(seriesType)
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAreaPercentage = state?.layers.some(
|
||||||
|
({ seriesType }) => seriesType === 'area_percentage_stacked'
|
||||||
|
);
|
||||||
|
|
||||||
|
const isHistogramSeries = Boolean(
|
||||||
|
hasHistogramSeries(state?.layers as ValidLayer[], frame.datasourceLayers)
|
||||||
|
);
|
||||||
|
|
||||||
const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false;
|
const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false;
|
||||||
const axisGroups = getAxesConfiguration(state?.layers, shouldRotate);
|
const axisGroups = getAxesConfiguration(state?.layers, shouldRotate);
|
||||||
|
|
||||||
|
@ -191,54 +236,99 @@ export function XyToolbar(props: VisualizationToolbarProps<State>) {
|
||||||
: !state?.legend.isVisible
|
: !state?.legend.isVisible
|
||||||
? 'hide'
|
? 'hide'
|
||||||
: 'show';
|
: 'show';
|
||||||
|
|
||||||
|
const valueLabelsVisibilityMode = state?.valueLabels || 'hide';
|
||||||
|
|
||||||
|
const isValueLabelsEnabled = !hasNonBarSeries && hasBarNotStacked && !isHistogramSeries;
|
||||||
|
const isFittingEnabled = hasNonBarSeries;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup gutterSize="m" justifyContent="spaceBetween">
|
<EuiFlexGroup gutterSize="m" justifyContent="spaceBetween">
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiFlexGroup gutterSize="none" responsive={false}>
|
<EuiFlexGroup gutterSize="none" responsive={false}>
|
||||||
<TooltipWrapper
|
<TooltipWrapper
|
||||||
tooltipContent={i18n.translate('xpack.lens.xyChart.fittingDisabledHelpText', {
|
tooltipContent={
|
||||||
defaultMessage: 'This setting only applies to line and area charts.',
|
isAreaPercentage
|
||||||
})}
|
? i18n.translate('xpack.lens.xyChart.valuesPercentageDisabledHelpText', {
|
||||||
condition={!hasNonBarSeries}
|
defaultMessage: 'This setting cannot be changed on percentage area charts.',
|
||||||
|
})
|
||||||
|
: i18n.translate('xpack.lens.xyChart.valuesStackedDisabledHelpText', {
|
||||||
|
defaultMessage: 'This setting cannot be changed on histograms.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
condition={!isValueLabelsEnabled && !isFittingEnabled}
|
||||||
>
|
>
|
||||||
<ToolbarPopover
|
<ToolbarPopover
|
||||||
title={i18n.translate('xpack.lens.xyChart.valuesLabel', {
|
title={i18n.translate('xpack.lens.xyChart.valuesLabel', {
|
||||||
defaultMessage: 'Values',
|
defaultMessage: 'Values',
|
||||||
})}
|
})}
|
||||||
isDisabled={!hasNonBarSeries}
|
|
||||||
type="values"
|
type="values"
|
||||||
groupPosition="left"
|
groupPosition="left"
|
||||||
buttonDataTestSubj="lnsMissingValuesButton"
|
buttonDataTestSubj="lnsValuesButton"
|
||||||
|
isDisabled={!isValueLabelsEnabled && !isFittingEnabled}
|
||||||
>
|
>
|
||||||
<EuiFormRow
|
{isValueLabelsEnabled ? (
|
||||||
display="columnCompressed"
|
<EuiFormRow
|
||||||
label={i18n.translate('xpack.lens.xyChart.missingValuesLabel', {
|
display="columnCompressed"
|
||||||
defaultMessage: 'Missing values',
|
label={
|
||||||
})}
|
<span>
|
||||||
>
|
{i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', {
|
||||||
<EuiSuperSelect
|
defaultMessage: 'Labels',
|
||||||
data-test-subj="lnsMissingValuesSelect"
|
})}
|
||||||
compressed
|
</span>
|
||||||
options={fittingFunctionDefinitions.map(({ id, title, description }) => {
|
}
|
||||||
return {
|
>
|
||||||
value: id,
|
<EuiButtonGroup
|
||||||
dropdownDisplay: (
|
isFullWidth
|
||||||
<>
|
legend={i18n.translate('xpack.lens.shared.chartValueLabelVisibilityLabel', {
|
||||||
<strong>{title}</strong>
|
defaultMessage: 'Labels',
|
||||||
<EuiText size="xs" color="subdued">
|
})}
|
||||||
<p>{description}</p>
|
data-test-subj="lnsValueLabelsDisplay"
|
||||||
</EuiText>
|
name="valueLabelsDisplay"
|
||||||
</>
|
buttonSize="compressed"
|
||||||
),
|
options={valueLabelsOptions}
|
||||||
inputDisplay: title,
|
idSelected={
|
||||||
};
|
valueLabelsOptions.find(({ value }) => value === valueLabelsVisibilityMode)!
|
||||||
|
.id
|
||||||
|
}
|
||||||
|
onChange={(modeId) => {
|
||||||
|
const newMode = valueLabelsOptions.find(({ id }) => id === modeId)!.value;
|
||||||
|
setState({ ...state, valueLabels: newMode });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
) : null}
|
||||||
|
{isFittingEnabled ? (
|
||||||
|
<EuiFormRow
|
||||||
|
display="columnCompressed"
|
||||||
|
label={i18n.translate('xpack.lens.xyChart.missingValuesLabel', {
|
||||||
|
defaultMessage: 'Missing values',
|
||||||
})}
|
})}
|
||||||
valueOfSelected={state?.fittingFunction || 'None'}
|
>
|
||||||
onChange={(value) => setState({ ...state, fittingFunction: value })}
|
<EuiSuperSelect
|
||||||
itemLayoutAlign="top"
|
data-test-subj="lnsMissingValuesSelect"
|
||||||
hasDividers
|
compressed
|
||||||
/>
|
options={fittingFunctionDefinitions.map(({ id, title, description }) => {
|
||||||
</EuiFormRow>
|
return {
|
||||||
|
value: id,
|
||||||
|
dropdownDisplay: (
|
||||||
|
<>
|
||||||
|
<strong>{title}</strong>
|
||||||
|
<EuiText size="xs" color="subdued">
|
||||||
|
<p>{description}</p>
|
||||||
|
</EuiText>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
inputDisplay: title,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
valueOfSelected={state?.fittingFunction || 'None'}
|
||||||
|
onChange={(value) => setState({ ...state, fittingFunction: value })}
|
||||||
|
itemLayoutAlign="top"
|
||||||
|
hasDividers
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
) : null}
|
||||||
</ToolbarPopover>
|
</ToolbarPopover>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
<LegendSettingsPopover
|
<LegendSettingsPopover
|
||||||
|
|
|
@ -137,6 +137,7 @@ describe('xy_suggestions', () => {
|
||||||
keptLayerIds: [],
|
keptLayerIds: [],
|
||||||
state: {
|
state: {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -249,6 +250,7 @@ describe('xy_suggestions', () => {
|
||||||
keptLayerIds: ['first'],
|
keptLayerIds: ['first'],
|
||||||
state: {
|
state: {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -289,6 +291,7 @@ describe('xy_suggestions', () => {
|
||||||
keptLayerIds: ['first', 'second'],
|
keptLayerIds: ['first', 'second'],
|
||||||
state: {
|
state: {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -523,6 +526,7 @@ describe('xy_suggestions', () => {
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -575,6 +579,7 @@ describe('xy_suggestions', () => {
|
||||||
test('keeps existing seriesType for initial tables', () => {
|
test('keeps existing seriesType for initial tables', () => {
|
||||||
const currentState: XYState = {
|
const currentState: XYState = {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
fittingFunction: 'None',
|
fittingFunction: 'None',
|
||||||
preferredSeriesType: 'line',
|
preferredSeriesType: 'line',
|
||||||
layers: [
|
layers: [
|
||||||
|
@ -608,6 +613,7 @@ describe('xy_suggestions', () => {
|
||||||
test('makes a visible seriesType suggestion for unchanged table without split', () => {
|
test('makes a visible seriesType suggestion for unchanged table without split', () => {
|
||||||
const currentState: XYState = {
|
const currentState: XYState = {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
fittingFunction: 'None',
|
fittingFunction: 'None',
|
||||||
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
||||||
gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
||||||
|
@ -648,6 +654,7 @@ describe('xy_suggestions', () => {
|
||||||
test('suggests seriesType and stacking when there is a split', () => {
|
test('suggests seriesType and stacking when there is a split', () => {
|
||||||
const currentState: XYState = {
|
const currentState: XYState = {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
fittingFunction: 'None',
|
fittingFunction: 'None',
|
||||||
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
||||||
|
@ -693,6 +700,7 @@ describe('xy_suggestions', () => {
|
||||||
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
|
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
|
||||||
const currentState: XYState = {
|
const currentState: XYState = {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
fittingFunction: 'None',
|
fittingFunction: 'None',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
layers: [
|
layers: [
|
||||||
|
@ -725,6 +733,7 @@ describe('xy_suggestions', () => {
|
||||||
test('suggests stacking for unchanged table that has a split', () => {
|
test('suggests stacking for unchanged table that has a split', () => {
|
||||||
const currentState: XYState = {
|
const currentState: XYState = {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
fittingFunction: 'None',
|
fittingFunction: 'None',
|
||||||
layers: [
|
layers: [
|
||||||
|
@ -760,6 +769,7 @@ describe('xy_suggestions', () => {
|
||||||
test('keeps column to dimension mappings on extended tables', () => {
|
test('keeps column to dimension mappings on extended tables', () => {
|
||||||
const currentState: XYState = {
|
const currentState: XYState = {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
fittingFunction: 'None',
|
fittingFunction: 'None',
|
||||||
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
||||||
|
@ -802,6 +812,7 @@ describe('xy_suggestions', () => {
|
||||||
test('changes column mappings when suggestion is reorder', () => {
|
test('changes column mappings when suggestion is reorder', () => {
|
||||||
const currentState: XYState = {
|
const currentState: XYState = {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
fittingFunction: 'None',
|
fittingFunction: 'None',
|
||||||
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
||||||
|
@ -845,6 +856,7 @@ describe('xy_suggestions', () => {
|
||||||
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
|
(generateId as jest.Mock).mockReturnValueOnce('dummyCol');
|
||||||
const currentState: XYState = {
|
const currentState: XYState = {
|
||||||
legend: { isVisible: true, position: 'bottom' },
|
legend: { isVisible: true, position: 'bottom' },
|
||||||
|
valueLabels: 'hide',
|
||||||
preferredSeriesType: 'bar',
|
preferredSeriesType: 'bar',
|
||||||
fittingFunction: 'None',
|
fittingFunction: 'None',
|
||||||
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true },
|
||||||
|
|
|
@ -509,6 +509,7 @@ function buildSuggestion({
|
||||||
|
|
||||||
const state: State = {
|
const state: State = {
|
||||||
legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right },
|
legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right },
|
||||||
|
valueLabels: currentState?.valueLabels || 'hide',
|
||||||
fittingFunction: currentState?.fittingFunction || 'None',
|
fittingFunction: currentState?.fittingFunction || 'None',
|
||||||
xTitle: currentState?.xTitle,
|
xTitle: currentState?.xTitle,
|
||||||
yTitle: currentState?.yTitle,
|
yTitle: currentState?.yTitle,
|
||||||
|
|
|
@ -10937,7 +10937,6 @@
|
||||||
"xpack.lens.xyChart.chartTypeLabel": "チャートタイプ",
|
"xpack.lens.xyChart.chartTypeLabel": "チャートタイプ",
|
||||||
"xpack.lens.xyChart.chartTypeLegend": "チャートタイプ",
|
"xpack.lens.xyChart.chartTypeLegend": "チャートタイプ",
|
||||||
"xpack.lens.xyChart.emptyXLabel": "(空)",
|
"xpack.lens.xyChart.emptyXLabel": "(空)",
|
||||||
"xpack.lens.xyChart.fittingDisabledHelpText": "この設定は折れ線グラフとエリアグラフでのみ適用されます。",
|
|
||||||
"xpack.lens.xyChart.fittingFunction.help": "欠測値の処理方法を定義",
|
"xpack.lens.xyChart.fittingFunction.help": "欠測値の処理方法を定義",
|
||||||
"xpack.lens.xyChart.Gridlines": "グリッド線",
|
"xpack.lens.xyChart.Gridlines": "グリッド線",
|
||||||
"xpack.lens.xyChart.gridlinesSettings.help": "xおよびy軸のグリッド線を表示",
|
"xpack.lens.xyChart.gridlinesSettings.help": "xおよびy軸のグリッド線を表示",
|
||||||
|
|
|
@ -10950,7 +10950,6 @@
|
||||||
"xpack.lens.xyChart.chartTypeLabel": "图表类型",
|
"xpack.lens.xyChart.chartTypeLabel": "图表类型",
|
||||||
"xpack.lens.xyChart.chartTypeLegend": "图表类型",
|
"xpack.lens.xyChart.chartTypeLegend": "图表类型",
|
||||||
"xpack.lens.xyChart.emptyXLabel": "(空)",
|
"xpack.lens.xyChart.emptyXLabel": "(空)",
|
||||||
"xpack.lens.xyChart.fittingDisabledHelpText": "此设置仅适用于折线图和面积图。",
|
|
||||||
"xpack.lens.xyChart.fittingFunction.help": "定义处理缺失值的方式",
|
"xpack.lens.xyChart.fittingFunction.help": "定义处理缺失值的方式",
|
||||||
"xpack.lens.xyChart.Gridlines": "网格线",
|
"xpack.lens.xyChart.Gridlines": "网格线",
|
||||||
"xpack.lens.xyChart.gridlinesSettings.help": "显示 x 和 y 轴网格线",
|
"xpack.lens.xyChart.gridlinesSettings.help": "显示 x 和 y 轴网格线",
|
||||||
|
|
|
@ -222,8 +222,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
|
||||||
},
|
},
|
||||||
async editMissingValues(option: string) {
|
async editMissingValues(option: string) {
|
||||||
await retry.try(async () => {
|
await retry.try(async () => {
|
||||||
await testSubjects.click('lnsMissingValuesButton');
|
await testSubjects.click('lnsValuesButton');
|
||||||
await testSubjects.exists('lnsMissingValuesSelect');
|
await testSubjects.exists('lnsValuesButton');
|
||||||
});
|
});
|
||||||
await testSubjects.click('lnsMissingValuesSelect');
|
await testSubjects.click('lnsMissingValuesSelect');
|
||||||
const optionSelector = await find.byCssSelector(`#${option}`);
|
const optionSelector = await find.byCssSelector(`#${option}`);
|
||||||
|
|
Loading…
Reference in a new issue