in x-pack/platform/plugins/shared/lens/public/visualizations/xy/visualization.tsx [830:1066]
getUserMessages(state, { frame }) {
const { datasourceLayers, dataViews, activeData } = frame;
const annotationLayers = getAnnotationsLayers(state.layers);
const errors: UserMessage[] = [];
const hasDateHistogram = isTimeChart(getDataLayers(state.layers), frame);
annotationLayers.forEach((layer) => {
layer.annotations.forEach((annotation) => {
if (!hasDateHistogram) {
errors.push({
uniqueId: ANNOTATION_MISSING_DATE_HISTOGRAM,
severity: 'error',
fixableInEditor: true,
displayLocations: [{ id: 'dimensionButton', dimensionId: annotation.id }],
shortMessage: i18n.translate(
'xpack.lens.xyChart.addAnnotationsLayerLabelDisabledHelp',
{
defaultMessage:
'Annotations require a time based chart to work. Add a date histogram.',
}
),
longMessage: '',
});
}
const errorMessages = getAnnotationLayerErrors(layer, annotation.id, dataViews);
errors.push(...errorMessages);
});
});
// check if the layers in the state are compatible with this type of chart
if (state && state.layers.length > 1) {
// Order is important here: Y Axis is fundamental to exist to make it valid
const yLayerValidation = validateLayersForDimension(
'y',
state.layers,
({ accessors }) => accessors == null || accessors.length === 0 // has no accessor
);
if (!yLayerValidation.valid) {
errors.push(yLayerValidation.error);
}
const breakDownLayerValidation = validateLayersForDimension(
'break_down',
state.layers,
({ splitAccessor, seriesType }) =>
seriesType.includes('percentage') && splitAccessor == null // check if no split accessor
);
if (!breakDownLayerValidation.valid) {
errors.push(breakDownLayerValidation.error);
}
}
// temporary fix for #87068
errors.push(
...checkXAccessorCompatibility(state, datasourceLayers).map<UserMessage>(
({ shortMessage, longMessage }) => ({
severity: 'error',
uniqueId: XY_X_WRONG_DATA_TYPE,
fixableInEditor: true,
displayLocations: [{ id: 'visualization' }],
shortMessage,
longMessage,
})
)
);
for (const layer of getDataLayers(state.layers)) {
const datasourceAPI = datasourceLayers[layer.layerId];
if (datasourceAPI) {
for (const accessor of layer.accessors) {
const operation = datasourceAPI.getOperationForColumnId(accessor);
if (operation && operation.dataType !== 'number') {
errors.push({
uniqueId: XY_Y_WRONG_DATA_TYPE,
severity: 'error',
fixableInEditor: true,
displayLocations: [{ id: 'visualization' }],
shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureYShort', {
defaultMessage: `Wrong data type for {axis}.`,
values: {
axis: getAxisName('y', { isHorizontal: isHorizontalChart(state.layers) }),
},
}),
longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureYLong', {
defaultMessage: `The dimension {label} provided for the {axis} has the wrong data type. Expected number but have {dataType}`,
values: {
label: operation.label,
dataType: operation.dataType,
axis: getAxisName('y', { isHorizontal: isHorizontalChart(state.layers) }),
},
}),
});
}
}
}
}
const warnings: UserMessage[] = [];
if (state?.layers.length > 0 && activeData) {
const filteredLayers = [
...getDataLayers(state.layers),
...getReferenceLayers(state.layers),
].filter(({ accessors }) => accessors.length > 0);
const accessorsWithArrayValues = [];
for (const layer of filteredLayers) {
const { layerId, accessors } = layer;
const rows = activeData?.[layerId] && activeData[layerId].rows;
if (!rows) {
break;
}
const columnToLabel = getColumnToLabelMap(layer, datasourceLayers[layerId]);
for (const accessor of accessors) {
const hasArrayValues = rows.some((row) => Array.isArray(row[accessor]));
if (hasArrayValues) {
accessorsWithArrayValues.push(columnToLabel[accessor]);
}
}
}
accessorsWithArrayValues.forEach((label) =>
warnings.push({
uniqueId: XY_RENDER_ARRAY_VALUES,
severity: 'warning',
fixableInEditor: true,
displayLocations: [{ id: 'toolbar' }],
shortMessage: '',
longMessage: (
<FormattedMessage
key={label}
id="xpack.lens.xyVisualization.arrayValues"
defaultMessage="{label} contains array values. Your visualization may not render as expected."
values={{
label: <strong>{label}</strong>,
}}
/>
),
})
);
}
const shouldRotate = state?.layers.length ? isHorizontalChart(state.layers) : false;
const dataLayers = getDataLayers(state.layers);
const axisGroups = getAxesConfiguration(dataLayers, shouldRotate, frame.activeData);
const logAxisGroups = axisGroups.filter(
({ groupId }) =>
(groupId === 'left' && state.yLeftScale === 'log') ||
(groupId === 'right' && state.yRightScale === 'log')
);
if (logAxisGroups.length > 0) {
logAxisGroups
.map((axis) => {
const mixedDomainSeries = axis.series.filter((series) => {
let hasNegValues = false;
let hasPosValues = false;
const arr = activeData?.[series.layer]?.rows ?? [];
for (let index = 0; index < arr.length; index++) {
const value = arr[index][series.accessor];
if (value < 0) {
hasNegValues = true;
} else {
hasPosValues = true;
}
if (hasNegValues && hasPosValues) {
return true;
}
}
return false;
});
return {
...axis,
mixedDomainSeries,
};
})
.forEach((axisGroup) => {
if (axisGroup.mixedDomainSeries.length === 0) return;
const { groupId } = axisGroup;
warnings.push({
// TODO: can we push the group into the metadata and use a correct unique ID here?
uniqueId: `${XY_MIXED_LOG_SCALE}${groupId}`,
severity: 'warning',
shortMessage: '',
longMessage: (
<FormattedMessage
id="xpack.lens.xyVisualization.mixedLogScaleWarning"
defaultMessage="When the {axisName} axis is set to logarithmic scale, the dataset should not contain positive and negative data."
values={{
axisName:
groupId === 'left' ? (
<FormattedMessage
id="xpack.lens.xyVisualization.mixedLogScaleWarningLeft"
defaultMessage="left"
/>
) : (
<FormattedMessage
id="xpack.lens.xyVisualization.mixedLogScaleWarningRight"
defaultMessage="right"
/>
),
}}
/>
),
displayLocations: [{ id: 'toolbar' }],
fixableInEditor: true,
});
axisGroup.mixedDomainSeries.forEach(({ accessor }) => {
warnings.push({
// TODO: can we push the group into the metadata and use a correct unique ID here?
uniqueId: `${XY_MIXED_LOG_SCALE_DIMENSION}${accessor}`,
severity: 'warning',
shortMessage: '',
longMessage: (
<FormattedMessage
id="xpack.lens.xyVisualization.mixedLogScaleDimensionWarning"
defaultMessage="This metric is using logarithmic scale and should not contain positive and negative data."
/>
),
displayLocations: [{ id: 'dimensionButton', dimensionId: accessor }],
fixableInEditor: true,
});
});
});
}
const info = getNotifiableFeatures(state, frame, paletteService, fieldFormats);
return errors.concat(warnings, info);
},