2020-05-05 21:59:32 +02:00
|
|
|
/*
|
|
|
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
2021-02-04 03:12:39 +01:00
|
|
|
* or more contributor license agreements. Licensed under the Elastic License
|
|
|
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
|
|
* 2.0.
|
2020-05-05 21:59:32 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
import { render } from 'react-dom';
|
|
|
|
import { i18n } from '@kbn/i18n';
|
|
|
|
import { I18nProvider } from '@kbn/i18n/react';
|
2020-11-04 11:27:52 +01:00
|
|
|
import { PaletteRegistry } from 'src/plugins/charts/public';
|
2020-11-17 10:19:13 +01:00
|
|
|
import { Visualization, OperationMetadata, AccessorConfig } from '../types';
|
2020-05-05 21:59:32 +02:00
|
|
|
import { toExpression, toPreviewExpression } from './to_expression';
|
2021-02-03 12:03:55 +01:00
|
|
|
import { PieLayerState, PieVisualizationState } from './types';
|
2020-05-05 21:59:32 +02:00
|
|
|
import { suggestions } from './suggestions';
|
|
|
|
import { CHART_NAMES, MAX_PIE_BUCKETS, MAX_TREEMAP_BUCKETS } from './constants';
|
2020-11-04 11:27:52 +01:00
|
|
|
import { DimensionEditor, PieToolbar } from './toolbar';
|
2020-05-05 21:59:32 +02:00
|
|
|
|
2021-02-03 12:03:55 +01:00
|
|
|
function newLayerState(layerId: string): PieLayerState {
|
2020-05-05 21:59:32 +02:00
|
|
|
return {
|
|
|
|
layerId,
|
|
|
|
groups: [],
|
|
|
|
metric: undefined,
|
|
|
|
numberDisplay: 'percent',
|
|
|
|
categoryDisplay: 'default',
|
|
|
|
legendDisplay: 'default',
|
|
|
|
nestedLegend: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const bucketedOperations = (op: OperationMetadata) => op.isBucketed;
|
|
|
|
const numberMetricOperations = (op: OperationMetadata) =>
|
|
|
|
!op.isBucketed && op.dataType === 'number';
|
|
|
|
|
2020-11-04 11:27:52 +01:00
|
|
|
export const getPieVisualization = ({
|
|
|
|
paletteService,
|
|
|
|
}: {
|
|
|
|
paletteService: PaletteRegistry;
|
|
|
|
}): Visualization<PieVisualizationState> => ({
|
2020-05-05 21:59:32 +02:00
|
|
|
id: 'lnsPie',
|
|
|
|
|
|
|
|
visualizationTypes: [
|
|
|
|
{
|
|
|
|
id: 'donut',
|
2020-09-15 11:24:02 +02:00
|
|
|
icon: CHART_NAMES.donut.icon,
|
2020-05-05 21:59:32 +02:00
|
|
|
label: CHART_NAMES.donut.label,
|
2021-03-02 11:25:35 +01:00
|
|
|
groupLabel: CHART_NAMES.donut.groupLabel,
|
2020-05-05 21:59:32 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'pie',
|
2020-09-15 11:24:02 +02:00
|
|
|
icon: CHART_NAMES.pie.icon,
|
2020-05-05 21:59:32 +02:00
|
|
|
label: CHART_NAMES.pie.label,
|
2021-03-02 11:25:35 +01:00
|
|
|
groupLabel: CHART_NAMES.pie.groupLabel,
|
2020-05-05 21:59:32 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 'treemap',
|
2020-09-15 11:24:02 +02:00
|
|
|
icon: CHART_NAMES.treemap.icon,
|
2020-05-05 21:59:32 +02:00
|
|
|
label: CHART_NAMES.treemap.label,
|
2021-03-02 11:25:35 +01:00
|
|
|
groupLabel: CHART_NAMES.treemap.groupLabel,
|
2020-05-05 21:59:32 +02:00
|
|
|
},
|
|
|
|
],
|
|
|
|
|
|
|
|
getVisualizationTypeId(state) {
|
|
|
|
return state.shape;
|
|
|
|
},
|
|
|
|
|
|
|
|
getLayerIds(state) {
|
2020-05-22 09:08:58 +02:00
|
|
|
return state.layers.map((l) => l.layerId);
|
2020-05-05 21:59:32 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
clearLayer(state) {
|
|
|
|
return {
|
|
|
|
shape: state.shape,
|
2020-05-22 09:08:58 +02:00
|
|
|
layers: state.layers.map((l) => newLayerState(l.layerId)),
|
2020-05-05 21:59:32 +02:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
getDescription(state) {
|
|
|
|
if (state.shape === 'treemap') {
|
|
|
|
return CHART_NAMES.treemap;
|
|
|
|
}
|
|
|
|
if (state.shape === 'donut') {
|
|
|
|
return CHART_NAMES.donut;
|
|
|
|
}
|
|
|
|
return CHART_NAMES.pie;
|
|
|
|
},
|
|
|
|
|
|
|
|
switchVisualizationType: (visualizationTypeId, state) => ({
|
|
|
|
...state,
|
|
|
|
shape: visualizationTypeId as PieVisualizationState['shape'],
|
|
|
|
}),
|
|
|
|
|
2020-11-04 11:27:52 +01:00
|
|
|
initialize(frame, state, mainPalette) {
|
2020-05-05 21:59:32 +02:00
|
|
|
return (
|
|
|
|
state || {
|
|
|
|
shape: 'donut',
|
|
|
|
layers: [newLayerState(frame.addNewLayer())],
|
2020-11-04 11:27:52 +01:00
|
|
|
palette: mainPalette,
|
2020-05-05 21:59:32 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2020-11-04 11:27:52 +01:00
|
|
|
getMainPalette: (state) => (state ? state.palette : undefined),
|
|
|
|
|
2020-05-05 21:59:32 +02:00
|
|
|
getSuggestions: suggestions,
|
|
|
|
|
|
|
|
getConfiguration({ state, frame, layerId }) {
|
2020-05-22 09:08:58 +02:00
|
|
|
const layer = state.layers.find((l) => l.layerId === layerId);
|
2020-05-05 21:59:32 +02:00
|
|
|
if (!layer) {
|
|
|
|
return { groups: [] };
|
|
|
|
}
|
|
|
|
|
|
|
|
const datasource = frame.datasourceLayers[layer.layerId];
|
|
|
|
const originalOrder = datasource
|
|
|
|
.getTableSpec()
|
|
|
|
.map(({ columnId }) => columnId)
|
2020-05-22 09:08:58 +02:00
|
|
|
.filter((columnId) => columnId !== layer.metric);
|
2020-05-05 21:59:32 +02:00
|
|
|
// When we add a column it could be empty, and therefore have no order
|
2020-11-17 10:19:13 +01:00
|
|
|
const sortedColumns: AccessorConfig[] = Array.from(
|
|
|
|
new Set(originalOrder.concat(layer.groups))
|
|
|
|
).map((accessor) => ({ columnId: accessor }));
|
|
|
|
if (sortedColumns.length > 0) {
|
|
|
|
sortedColumns[0] = {
|
|
|
|
columnId: sortedColumns[0].columnId,
|
|
|
|
triggerIcon: 'colorBy',
|
|
|
|
palette: paletteService
|
|
|
|
.get(state.palette?.name || 'default')
|
|
|
|
.getColors(10, state.palette?.params),
|
|
|
|
};
|
|
|
|
}
|
2020-05-05 21:59:32 +02:00
|
|
|
|
|
|
|
if (state.shape === 'treemap') {
|
|
|
|
return {
|
|
|
|
groups: [
|
|
|
|
{
|
|
|
|
groupId: 'groups',
|
|
|
|
groupLabel: i18n.translate('xpack.lens.pie.treemapGroupLabel', {
|
|
|
|
defaultMessage: 'Group by',
|
|
|
|
}),
|
|
|
|
layerId,
|
|
|
|
accessors: sortedColumns,
|
|
|
|
supportsMoreColumns: sortedColumns.length < MAX_TREEMAP_BUCKETS,
|
|
|
|
filterOperations: bucketedOperations,
|
|
|
|
required: true,
|
2020-08-06 18:52:21 +02:00
|
|
|
dataTestSubj: 'lnsPie_groupByDimensionPanel',
|
2020-11-04 11:27:52 +01:00
|
|
|
enableDimensionEditor: true,
|
2020-05-05 21:59:32 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
groupId: 'metric',
|
|
|
|
groupLabel: i18n.translate('xpack.lens.pie.groupsizeLabel', {
|
|
|
|
defaultMessage: 'Size by',
|
|
|
|
}),
|
|
|
|
layerId,
|
2020-11-17 10:19:13 +01:00
|
|
|
accessors: layer.metric ? [{ columnId: layer.metric }] : [],
|
2020-05-05 21:59:32 +02:00
|
|
|
supportsMoreColumns: !layer.metric,
|
|
|
|
filterOperations: numberMetricOperations,
|
|
|
|
required: true,
|
2020-08-06 18:52:21 +02:00
|
|
|
dataTestSubj: 'lnsPie_sizeByDimensionPanel',
|
2020-05-05 21:59:32 +02:00
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
groups: [
|
|
|
|
{
|
|
|
|
groupId: 'groups',
|
|
|
|
groupLabel: i18n.translate('xpack.lens.pie.sliceGroupLabel', {
|
|
|
|
defaultMessage: 'Slice by',
|
|
|
|
}),
|
|
|
|
layerId,
|
|
|
|
accessors: sortedColumns,
|
|
|
|
supportsMoreColumns: sortedColumns.length < MAX_PIE_BUCKETS,
|
|
|
|
filterOperations: bucketedOperations,
|
|
|
|
required: true,
|
2020-08-06 18:52:21 +02:00
|
|
|
dataTestSubj: 'lnsPie_sliceByDimensionPanel',
|
2020-11-04 11:27:52 +01:00
|
|
|
enableDimensionEditor: true,
|
2020-05-05 21:59:32 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
groupId: 'metric',
|
|
|
|
groupLabel: i18n.translate('xpack.lens.pie.groupsizeLabel', {
|
|
|
|
defaultMessage: 'Size by',
|
|
|
|
}),
|
|
|
|
layerId,
|
2020-11-17 10:19:13 +01:00
|
|
|
accessors: layer.metric ? [{ columnId: layer.metric }] : [],
|
2020-05-05 21:59:32 +02:00
|
|
|
supportsMoreColumns: !layer.metric,
|
|
|
|
filterOperations: numberMetricOperations,
|
|
|
|
required: true,
|
2020-08-06 18:52:21 +02:00
|
|
|
dataTestSubj: 'lnsPie_sizeByDimensionPanel',
|
2020-05-05 21:59:32 +02:00
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
setDimension({ prevState, layerId, columnId, groupId }) {
|
|
|
|
return {
|
|
|
|
...prevState,
|
|
|
|
|
|
|
|
shape:
|
2020-05-22 09:08:58 +02:00
|
|
|
prevState.shape === 'donut' && prevState.layers.every((l) => l.groups.length === 1)
|
2020-05-05 21:59:32 +02:00
|
|
|
? 'pie'
|
|
|
|
: prevState.shape,
|
2020-05-22 09:08:58 +02:00
|
|
|
layers: prevState.layers.map((l) => {
|
2020-05-05 21:59:32 +02:00
|
|
|
if (l.layerId !== layerId) {
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
if (groupId === 'groups') {
|
|
|
|
return { ...l, groups: [...l.groups, columnId] };
|
|
|
|
}
|
|
|
|
return { ...l, metric: columnId };
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
removeDimension({ prevState, layerId, columnId }) {
|
|
|
|
return {
|
|
|
|
...prevState,
|
2020-05-22 09:08:58 +02:00
|
|
|
layers: prevState.layers.map((l) => {
|
2020-05-05 21:59:32 +02:00
|
|
|
if (l.layerId !== layerId) {
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (l.metric === columnId) {
|
|
|
|
return { ...l, metric: undefined };
|
|
|
|
}
|
2020-05-22 09:08:58 +02:00
|
|
|
return { ...l, groups: l.groups.filter((c) => c !== columnId) };
|
2020-05-05 21:59:32 +02:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
},
|
2020-11-04 11:27:52 +01:00
|
|
|
renderDimensionEditor(domElement, props) {
|
|
|
|
render(
|
|
|
|
<I18nProvider>
|
2021-02-15 13:00:31 +01:00
|
|
|
<DimensionEditor {...props} paletteService={paletteService} />
|
2020-11-04 11:27:52 +01:00
|
|
|
</I18nProvider>,
|
|
|
|
domElement
|
|
|
|
);
|
|
|
|
},
|
2020-05-05 21:59:32 +02:00
|
|
|
|
2020-11-04 11:27:52 +01:00
|
|
|
toExpression: (state, layers, attributes) =>
|
|
|
|
toExpression(state, layers, paletteService, attributes),
|
|
|
|
toPreviewExpression: (state, layers) => toPreviewExpression(state, layers, paletteService),
|
2020-05-05 21:59:32 +02:00
|
|
|
|
2020-07-22 12:14:59 +02:00
|
|
|
renderToolbar(domElement, props) {
|
2020-05-05 21:59:32 +02:00
|
|
|
render(
|
|
|
|
<I18nProvider>
|
2020-07-22 12:14:59 +02:00
|
|
|
<PieToolbar {...props} />
|
2020-05-05 21:59:32 +02:00
|
|
|
</I18nProvider>,
|
|
|
|
domElement
|
|
|
|
);
|
|
|
|
},
|
2020-11-04 18:28:00 +01:00
|
|
|
|
2020-12-02 19:55:58 +01:00
|
|
|
getWarningMessages(state, frame) {
|
|
|
|
if (state?.layers.length === 0 || !frame.activeData) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const metricColumnsWithArrayValues = [];
|
|
|
|
|
|
|
|
for (const layer of state.layers) {
|
|
|
|
const { layerId, metric } = layer;
|
|
|
|
const rows = frame.activeData[layerId] && frame.activeData[layerId].rows;
|
|
|
|
if (!rows || !metric) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
const columnToLabel = frame.datasourceLayers[layerId].getOperationForColumnId(metric)?.label;
|
|
|
|
|
|
|
|
const hasArrayValues = rows.some((row) => Array.isArray(row[metric]));
|
|
|
|
if (hasArrayValues) {
|
|
|
|
metricColumnsWithArrayValues.push(columnToLabel || metric);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return metricColumnsWithArrayValues.map((label) => (
|
|
|
|
<>
|
|
|
|
<strong>{label}</strong> contains array values. Your visualization may not render as
|
|
|
|
expected.
|
|
|
|
</>
|
|
|
|
));
|
|
|
|
},
|
|
|
|
|
2021-02-15 13:00:31 +01:00
|
|
|
getErrorMessages(state) {
|
2020-11-04 18:28:00 +01:00
|
|
|
// not possible to break it?
|
|
|
|
return undefined;
|
|
|
|
},
|
2020-11-04 11:27:52 +01:00
|
|
|
});
|