mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[PieVis] Lens adaptation. (#122420)
* Added config for mosaic/pie/donut/treemap/waffle. * Added sortPredicate functionality for waffle/mosaic/treemap/pie/donut * Added Donut handling. * Refactored get_color. * Merged color computation for lens and vis_types. * Added isFlatLegend support. * Added showValuesInLegend for waffle and fixed tests. * Removed not used position, which is equivalent to labels.show = false. * legendDisplay added. * Added migrations for pieVis addLegend argument. * Added startFromSecondLargestSlice and support of correct formatters. * Updated docs. * Added functionality for truncate. * Added unit tests for pie and partial for donut/waffle. * Addressed issue with label truncation by default. * Addressed issue with formatters. * Added tests for accessor.test.ts * Added support of formatter by meta data from columns at splitChartAccessors. * Added tests for filterOutConfig. * Added tests for getFormatters. * Added tests for getAvailableFormatter. * Added tests for getFormatter. * Added tests for get_split_dimension_accessor. * Add is legend scenario. * Added tests for legend. * Replaced sortPredicate, relying on the internal terms params, with the mosaic one. * Fixed pie snapshot and added new snapshot for treemap. * Added snapshots for mosaicVis. * Added snapshot to waffleVis. * Updated unit tests for *_vis_function's. * Added storybook. * Added snapshots for partition vis component. * Added expression error on providing both, splitColumn && splitRow. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f7661c007b
commit
d9aa72c7f8
125 changed files with 7718 additions and 1556 deletions
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -40,7 +40,7 @@
|
|||
/src/plugins/chart_expressions/expression_metric/ @elastic/kibana-vis-editors
|
||||
/src/plugins/chart_expressions/expression_heatmap/ @elastic/kibana-vis-editors
|
||||
/src/plugins/chart_expressions/expression_gauge/ @elastic/kibana-vis-editors
|
||||
/src/plugins/chart_expressions/expression_pie/ @elastic/kibana-vis-editors
|
||||
/src/plugins/chart_expressions/expression_partition_vis/ @elastic/kibana-vis-editors
|
||||
/src/plugins/url_forwarding/ @elastic/kibana-vis-editors
|
||||
/packages/kbn-tinymath/ @elastic/kibana-vis-editors
|
||||
/x-pack/test/functional/apps/lens @elastic/kibana-vis-editors
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"expressionImage": "src/plugins/expression_image",
|
||||
"expressionMetric": "src/plugins/expression_metric",
|
||||
"expressionMetricVis": "src/plugins/chart_expressions/expression_metric",
|
||||
"expressionPie": "src/plugins/chart_expressions/expression_pie",
|
||||
"expressionPartitionVis": "src/plugins/chart_expressions/expression_partition_vis",
|
||||
"expressionRepeatImage": "src/plugins/expression_repeat_image",
|
||||
"expressionRevealImage": "src/plugins/expression_reveal_image",
|
||||
"expressions": "src/plugins/expressions",
|
||||
|
|
|
@ -118,8 +118,8 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a
|
|||
|Expression MetricVis plugin adds a metric renderer and function to the expression plugin. The renderer will display the metric chart.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/chart_expressions/expression_pie/README.md[expressionPie]
|
||||
|Expression Pie plugin adds a pie renderer and function to the expression plugin. The renderer will display the Pie chart.
|
||||
|{kib-repo}blob/{branch}/src/plugins/chart_expressions/expression_partition_vis/README.md[expressionPartitionVis]
|
||||
|Expression Partition Visualization plugin adds a partitionVis renderer and pieVis, mosaicVis, treemapVis, waffleVis functions to the expression plugin. The renderer will display the pie, waffle, treemap and mosaic charts.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/src/plugins/expression_repeat_image/README.md[expressionRepeatImage]
|
||||
|
|
|
@ -119,6 +119,6 @@ pageLoadAssetSize:
|
|||
screenshotting: 17017
|
||||
expressionGauge: 25000
|
||||
controls: 34788
|
||||
expressionPie: 26338
|
||||
expressionPartitionVis: 26338
|
||||
sharedUX: 16225
|
||||
ux: 20784
|
||||
|
|
|
@ -24,7 +24,7 @@ export const storybookAliases = {
|
|||
expression_image: 'src/plugins/expression_image/.storybook',
|
||||
expression_metric_vis: 'src/plugins/chart_expressions/expression_metric/.storybook',
|
||||
expression_metric: 'src/plugins/expression_metric/.storybook',
|
||||
expression_pie: 'src/plugins/chart_expressions/expression_pie/.storybook',
|
||||
expression_partition_vis: 'src/plugins/chart_expressions/expression_partition_vis/.storybook',
|
||||
expression_repeat_image: 'src/plugins/expression_repeat_image/.storybook',
|
||||
expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook',
|
||||
expression_shape: 'src/plugins/expression_shape/.storybook',
|
||||
|
|
9
src/plugins/chart_expressions/expression_partition_vis/README.md
Executable file
9
src/plugins/chart_expressions/expression_partition_vis/README.md
Executable file
|
@ -0,0 +1,9 @@
|
|||
# expressionPartitionVis
|
||||
|
||||
Expression Partition Visualization plugin adds a `partitionVis` renderer and `pieVis`, `mosaicVis`, `treemapVis`, `waffleVis` functions to the expression plugin. The renderer will display the `pie`, `waffle`, `treemap` and `mosaic` charts.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment.
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const PLUGIN_ID = 'expressionPartitionVis';
|
||||
export const PLUGIN_NAME = 'expressionPartitionVis';
|
||||
|
||||
export const PIE_VIS_EXPRESSION_NAME = 'pieVis';
|
||||
export const TREEMAP_VIS_EXPRESSION_NAME = 'treemapVis';
|
||||
export const MOSAIC_VIS_EXPRESSION_NAME = 'mosaicVis';
|
||||
export const WAFFLE_VIS_EXPRESSION_NAME = 'waffleVis';
|
||||
export const PARTITION_VIS_RENDERER_NAME = 'partitionVis';
|
||||
export const PARTITION_LABELS_VALUE = 'partitionLabelsValue';
|
||||
export const PARTITION_LABELS_FUNCTION = 'partitionLabels';
|
||||
|
||||
export const DEFAULT_PERCENT_DECIMALS = 2;
|
|
@ -0,0 +1,188 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`interpreter/functions#mosaicVis logs correct datatable to inspector 1`] = `
|
||||
Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-3",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 3",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-4",
|
||||
"meta": Object {
|
||||
"dimensionName": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 4",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#mosaicVis returns an object with the correct structure 1`] = `
|
||||
Object {
|
||||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
"syncColors": false,
|
||||
"visConfig": Object {
|
||||
"addTooltip": true,
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 2,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"dimensions": Object {
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 2,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
},
|
||||
"labels": Object {
|
||||
"last_level": false,
|
||||
"percentDecimals": 2,
|
||||
"position": "default",
|
||||
"show": false,
|
||||
"truncate": 100,
|
||||
"type": "partitionLabelsValue",
|
||||
"values": true,
|
||||
"valuesFormat": "percent",
|
||||
},
|
||||
"legendDisplay": "show",
|
||||
"legendPosition": "right",
|
||||
"maxLegendLines": 2,
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"nestedLegend": true,
|
||||
"palette": Object {
|
||||
"name": "kibana_palette",
|
||||
"type": "system_palette",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
"truncateLegend": true,
|
||||
},
|
||||
"visData": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-3",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 3",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-4",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 4",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"visType": "mosaic",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#mosaicVis throws error if provided more than 2 buckets 1`] = `"More than 2 buckets are not supported"`;
|
||||
|
||||
exports[`interpreter/functions#mosaicVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`;
|
|
@ -0,0 +1,288 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`interpreter/functions#pieVis logs correct datatable to inspector 1`] = `
|
||||
Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Count",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#pieVis returns an object with the correct structure for donut 1`] = `
|
||||
Object {
|
||||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
"syncColors": false,
|
||||
"visConfig": Object {
|
||||
"addTooltip": true,
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 2,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 3,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"dimensions": Object {
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 2,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 3,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
},
|
||||
"distinctColors": false,
|
||||
"emptySizeRatio": 0.3,
|
||||
"isDonut": true,
|
||||
"labels": Object {
|
||||
"last_level": false,
|
||||
"percentDecimals": 2,
|
||||
"position": "default",
|
||||
"show": false,
|
||||
"truncate": 100,
|
||||
"type": "partitionLabelsValue",
|
||||
"values": true,
|
||||
"valuesFormat": "percent",
|
||||
},
|
||||
"legendDisplay": "show",
|
||||
"legendPosition": "right",
|
||||
"maxLegendLines": 2,
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"nestedLegend": true,
|
||||
"palette": Object {
|
||||
"name": "kibana_palette",
|
||||
"type": "system_palette",
|
||||
},
|
||||
"respectSourceOrder": true,
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
"startFromSecondLargestSlice": true,
|
||||
"truncateLegend": true,
|
||||
},
|
||||
"visData": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Count",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"visType": "donut",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#pieVis returns an object with the correct structure for pie 1`] = `
|
||||
Object {
|
||||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
"syncColors": false,
|
||||
"visConfig": Object {
|
||||
"addTooltip": true,
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 2,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 3,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"dimensions": Object {
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 2,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 3,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
},
|
||||
"distinctColors": false,
|
||||
"emptySizeRatio": 0.3,
|
||||
"isDonut": false,
|
||||
"labels": Object {
|
||||
"last_level": false,
|
||||
"percentDecimals": 2,
|
||||
"position": "default",
|
||||
"show": false,
|
||||
"truncate": 100,
|
||||
"type": "partitionLabelsValue",
|
||||
"values": true,
|
||||
"valuesFormat": "percent",
|
||||
},
|
||||
"legendDisplay": "show",
|
||||
"legendPosition": "right",
|
||||
"maxLegendLines": 2,
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"nestedLegend": true,
|
||||
"palette": Object {
|
||||
"name": "kibana_palette",
|
||||
"type": "system_palette",
|
||||
},
|
||||
"respectSourceOrder": true,
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
"startFromSecondLargestSlice": true,
|
||||
"truncateLegend": true,
|
||||
},
|
||||
"visData": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Count",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"visType": "pie",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#pieVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`;
|
|
@ -0,0 +1,188 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`interpreter/functions#treemapVis logs correct datatable to inspector 1`] = `
|
||||
Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-3",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 3",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-4",
|
||||
"meta": Object {
|
||||
"dimensionName": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 4",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#treemapVis returns an object with the correct structure 1`] = `
|
||||
Object {
|
||||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
"syncColors": false,
|
||||
"visConfig": Object {
|
||||
"addTooltip": true,
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 2,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"dimensions": Object {
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
Object {
|
||||
"accessor": 2,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
},
|
||||
"labels": Object {
|
||||
"last_level": false,
|
||||
"percentDecimals": 2,
|
||||
"position": "default",
|
||||
"show": false,
|
||||
"truncate": 100,
|
||||
"type": "partitionLabelsValue",
|
||||
"values": true,
|
||||
"valuesFormat": "percent",
|
||||
},
|
||||
"legendDisplay": "show",
|
||||
"legendPosition": "right",
|
||||
"maxLegendLines": 2,
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"nestedLegend": true,
|
||||
"palette": Object {
|
||||
"name": "kibana_palette",
|
||||
"type": "system_palette",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
"truncateLegend": true,
|
||||
},
|
||||
"visData": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-3",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 3",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-4",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 4",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"visType": "treemap",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#treemapVis throws error if provided more than 2 buckets 1`] = `"More than 2 buckets are not supported"`;
|
||||
|
||||
exports[`interpreter/functions#treemapVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`;
|
|
@ -0,0 +1,168 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`interpreter/functions#waffleVis logs correct datatable to inspector 1`] = `
|
||||
Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice",
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-3",
|
||||
"meta": Object {
|
||||
"dimensionName": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 3",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-4",
|
||||
"meta": Object {
|
||||
"dimensionName": undefined,
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 4",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#waffleVis returns an object with the correct structure 1`] = `
|
||||
Object {
|
||||
"as": "partitionVis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
"syncColors": false,
|
||||
"visConfig": Object {
|
||||
"addTooltip": true,
|
||||
"bucket": Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"dimensions": Object {
|
||||
"buckets": Array [
|
||||
Object {
|
||||
"accessor": 1,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
],
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
},
|
||||
"labels": Object {
|
||||
"last_level": false,
|
||||
"percentDecimals": 2,
|
||||
"position": "default",
|
||||
"show": false,
|
||||
"truncate": 100,
|
||||
"type": "partitionLabelsValue",
|
||||
"values": true,
|
||||
"valuesFormat": "percent",
|
||||
},
|
||||
"legendDisplay": "show",
|
||||
"legendPosition": "right",
|
||||
"maxLegendLines": 2,
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"palette": Object {
|
||||
"name": "kibana_palette",
|
||||
"type": "system_palette",
|
||||
},
|
||||
"showValuesInLegend": true,
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
"truncateLegend": true,
|
||||
},
|
||||
"visData": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 1",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-2",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 2",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-3",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 3",
|
||||
},
|
||||
Object {
|
||||
"id": "col-0-4",
|
||||
"meta": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"name": "Field 4",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
"col-0-2": 0,
|
||||
"col-0-3": 0,
|
||||
"col-0-4": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"visType": "waffle",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#waffleVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`;
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const strings = {
|
||||
getPieVisFunctionName: () =>
|
||||
i18n.translate('expressionPartitionVis.pieVis.function.help', {
|
||||
defaultMessage: 'Pie visualization',
|
||||
}),
|
||||
getMetricArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.metricHelpText', {
|
||||
defaultMessage: 'Metric dimensions config',
|
||||
}),
|
||||
getBucketsArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.bucketsHelpText', {
|
||||
defaultMessage: 'Buckets dimensions config',
|
||||
}),
|
||||
getBucketArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.waffle.function.args.bucketHelpText', {
|
||||
defaultMessage: 'Bucket dimensions config',
|
||||
}),
|
||||
getSplitColumnArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.splitColumnHelpText', {
|
||||
defaultMessage: 'Split by column dimension config',
|
||||
}),
|
||||
getSplitRowArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.splitRowHelpText', {
|
||||
defaultMessage: 'Split by row dimension config',
|
||||
}),
|
||||
getAddTooltipArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.addTooltipHelpText', {
|
||||
defaultMessage: 'Show tooltip on slice hover',
|
||||
}),
|
||||
getLegendDisplayArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.legendDisplayHelpText', {
|
||||
defaultMessage: 'Show legend chart legend',
|
||||
}),
|
||||
getLegendPositionArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.legendPositionHelpText', {
|
||||
defaultMessage: 'Position the legend on top, bottom, left, right of the chart',
|
||||
}),
|
||||
getNestedLegendArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.nestedLegendHelpText', {
|
||||
defaultMessage: 'Show a more detailed legend',
|
||||
}),
|
||||
getTruncateLegendArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.truncateLegendHelpText', {
|
||||
defaultMessage: 'Defines if the legend items will be truncated or not',
|
||||
}),
|
||||
getMaxLegendLinesArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.maxLegendLinesHelpText', {
|
||||
defaultMessage: 'Defines the number of lines per legend item',
|
||||
}),
|
||||
getDistinctColorsArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.pieVis.function.args.distinctColorsHelpText', {
|
||||
defaultMessage:
|
||||
'Maps different color per slice. Slices with the same value have the same color',
|
||||
}),
|
||||
getIsDonutArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.isDonutHelpText', {
|
||||
defaultMessage: 'Displays the pie chart as donut',
|
||||
}),
|
||||
getRespectSourceOrderArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.respectSourceOrderHelpText', {
|
||||
defaultMessage: 'Keeps an order of the elements, returned from the datasource',
|
||||
}),
|
||||
getStartFromSecondLargestSliceArgHelp: () =>
|
||||
i18n.translate(
|
||||
'expressionPartitionVis.reusable.function.args.startPlacementWithSecondLargestSliceHelpText',
|
||||
{
|
||||
defaultMessage: 'Starts placement with the second largest slice',
|
||||
}
|
||||
),
|
||||
getEmptySizeRatioArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.emptySizeRatioHelpText', {
|
||||
defaultMessage: 'Defines donut inner empty area size',
|
||||
}),
|
||||
getPaletteArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.paletteHelpText', {
|
||||
defaultMessage: 'Defines the chart palette name',
|
||||
}),
|
||||
getLabelsArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.args.labelsHelpText', {
|
||||
defaultMessage: 'Pie labels config',
|
||||
}),
|
||||
getShowValuesInLegendArgHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.waffle.function.args.showValuesInLegendHelpText', {
|
||||
defaultMessage: 'Show values in legend',
|
||||
}),
|
||||
|
||||
getSliceSizeHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.dimension.metric', {
|
||||
defaultMessage: 'Slice size',
|
||||
}),
|
||||
getSliceHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.dimension.buckets', {
|
||||
defaultMessage: 'Slice',
|
||||
}),
|
||||
getColumnSplitHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.dimension.splitcolumn', {
|
||||
defaultMessage: 'Column split',
|
||||
}),
|
||||
getRowSplitHelp: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.dimension.splitrow', {
|
||||
defaultMessage: 'Row split',
|
||||
}),
|
||||
};
|
||||
|
||||
export const errors = {
|
||||
moreThanNBucketsAreNotSupportedError: (maxLength: number) =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.errors.moreThenNumberBuckets', {
|
||||
defaultMessage: 'More than {maxLength} buckets are not supported',
|
||||
values: { maxLength },
|
||||
}),
|
||||
splitRowAndSplitColumnAreSpecifiedError: () =>
|
||||
i18n.translate('expressionPartitionVis.reusable.function.errors.splitRowAndColumnSpecified', {
|
||||
defaultMessage:
|
||||
'A split row and column are specified. Expression is supporting only one of them at once.',
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { pieVisFunction } from './pie_vis_function';
|
||||
export { treemapVisFunction } from './treemap_vis_function';
|
||||
export { mosaicVisFunction } from './mosaic_vis_function';
|
||||
export { waffleVisFunction } from './waffle_vis_function';
|
||||
export { partitionLabelsFunction } from './partition_labels_function';
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils';
|
||||
import {
|
||||
MosaicVisConfig,
|
||||
LabelPositions,
|
||||
ValueFormats,
|
||||
LegendDisplay,
|
||||
} from '../types/expression_renderers';
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { Datatable } from '../../../../expressions/common/expression_types/specs';
|
||||
import { mosaicVisFunction } from './mosaic_vis_function';
|
||||
import { PARTITION_LABELS_VALUE } from '../constants';
|
||||
|
||||
describe('interpreter/functions#mosaicVis', () => {
|
||||
const fn = functionWrapper(mosaicVisFunction());
|
||||
const context: Datatable = {
|
||||
type: 'datatable',
|
||||
rows: [{ 'col-0-1': 0, 'col-0-2': 0, 'col-0-3': 0, 'col-0-4': 0 }],
|
||||
columns: [
|
||||
{ id: 'col-0-1', name: 'Field 1', meta: { type: 'number' } },
|
||||
{ id: 'col-0-2', name: 'Field 2', meta: { type: 'number' } },
|
||||
{ id: 'col-0-3', name: 'Field 3', meta: { type: 'number' } },
|
||||
{ id: 'col-0-4', name: 'Field 4', meta: { type: 'number' } },
|
||||
],
|
||||
};
|
||||
|
||||
const visConfig: MosaicVisConfig = {
|
||||
addTooltip: true,
|
||||
legendDisplay: LegendDisplay.SHOW,
|
||||
legendPosition: 'right',
|
||||
nestedLegend: true,
|
||||
truncateLegend: true,
|
||||
maxLegendLines: 2,
|
||||
palette: {
|
||||
type: 'system_palette',
|
||||
name: 'kibana_palette',
|
||||
},
|
||||
labels: {
|
||||
type: PARTITION_LABELS_VALUE,
|
||||
show: false,
|
||||
values: true,
|
||||
position: LabelPositions.DEFAULT,
|
||||
valuesFormat: ValueFormats.PERCENT,
|
||||
percentDecimals: 2,
|
||||
truncate: 100,
|
||||
last_level: false,
|
||||
},
|
||||
metric: {
|
||||
type: 'vis_dimension',
|
||||
accessor: 0,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 2,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure', async () => {
|
||||
const actual = await fn(context, visConfig);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('throws error if provided more than 2 buckets', async () => {
|
||||
expect(() =>
|
||||
fn(context, {
|
||||
...visConfig,
|
||||
buckets: [
|
||||
...(visConfig.buckets ?? []),
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 3,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('throws error if provided split row and split column at once', async () => {
|
||||
const splitDimension: ExpressionValueVisDimension = {
|
||||
type: 'vis_dimension',
|
||||
accessor: 3,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
fn(context, {
|
||||
...visConfig,
|
||||
splitColumn: [splitDimension],
|
||||
splitRow: [splitDimension],
|
||||
})
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('logs correct datatable to inspector', async () => {
|
||||
let loggedTable: Datatable;
|
||||
const handlers = {
|
||||
inspectorAdapters: {
|
||||
tables: {
|
||||
logDatatable: (name: string, datatable: Datatable) => {
|
||||
loggedTable = datatable;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await fn(context, visConfig, handlers as any);
|
||||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
|
||||
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
|
||||
import { ChartTypes, MosaicVisExpressionFunctionDefinition } from '../types';
|
||||
import {
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
PARTITION_LABELS_VALUE,
|
||||
PARTITION_VIS_RENDERER_NAME,
|
||||
MOSAIC_VIS_EXPRESSION_NAME,
|
||||
} from '../constants';
|
||||
import { errors, strings } from './i18n';
|
||||
|
||||
export const mosaicVisFunction = (): MosaicVisExpressionFunctionDefinition => ({
|
||||
name: MOSAIC_VIS_EXPRESSION_NAME,
|
||||
type: 'render',
|
||||
inputTypes: ['datatable'],
|
||||
help: strings.getPieVisFunctionName(),
|
||||
args: {
|
||||
metric: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getMetricArgHelp(),
|
||||
required: true,
|
||||
},
|
||||
buckets: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getBucketsArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
splitColumn: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getSplitColumnArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
splitRow: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getSplitRowArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
addTooltip: {
|
||||
types: ['boolean'],
|
||||
help: strings.getAddTooltipArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
legendDisplay: {
|
||||
types: ['string'],
|
||||
help: strings.getLegendDisplayArgHelp(),
|
||||
options: [LegendDisplay.SHOW, LegendDisplay.HIDE, LegendDisplay.DEFAULT],
|
||||
default: LegendDisplay.HIDE,
|
||||
},
|
||||
legendPosition: {
|
||||
types: ['string'],
|
||||
help: strings.getLegendPositionArgHelp(),
|
||||
},
|
||||
nestedLegend: {
|
||||
types: ['boolean'],
|
||||
help: strings.getNestedLegendArgHelp(),
|
||||
default: false,
|
||||
},
|
||||
truncateLegend: {
|
||||
types: ['boolean'],
|
||||
help: strings.getTruncateLegendArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
maxLegendLines: {
|
||||
types: ['number'],
|
||||
help: strings.getMaxLegendLinesArgHelp(),
|
||||
},
|
||||
palette: {
|
||||
types: ['palette', 'system_palette'],
|
||||
help: strings.getPaletteArgHelp(),
|
||||
default: '{palette}',
|
||||
},
|
||||
labels: {
|
||||
types: [PARTITION_LABELS_VALUE],
|
||||
help: strings.getLabelsArgHelp(),
|
||||
default: `{${PARTITION_LABELS_FUNCTION}}`,
|
||||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
const maxSupportedBuckets = 2;
|
||||
if ((args.buckets ?? []).length > maxSupportedBuckets) {
|
||||
throw new Error(errors.moreThanNBucketsAreNotSupportedError(maxSupportedBuckets));
|
||||
}
|
||||
|
||||
if (args.splitColumn && args.splitRow) {
|
||||
throw new Error(errors.splitRowAndSplitColumnAreSpecifiedError());
|
||||
}
|
||||
|
||||
const visConfig: PartitionVisParams = {
|
||||
...args,
|
||||
palette: args.palette,
|
||||
dimensions: {
|
||||
metric: args.metric,
|
||||
buckets: args.buckets,
|
||||
splitColumn: args.splitColumn,
|
||||
splitRow: args.splitRow,
|
||||
},
|
||||
};
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
const logTable = prepareLogTable(context, [
|
||||
[[args.metric], strings.getSliceSizeHelp()],
|
||||
[args.buckets, strings.getSliceHelp()],
|
||||
[args.splitColumn, strings.getColumnSplitHelp()],
|
||||
[args.splitRow, strings.getRowSplitHelp()],
|
||||
]);
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', logTable);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: PARTITION_VIS_RENDERER_NAME,
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig,
|
||||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
visType: ChartTypes.MOSAIC,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ExpressionFunctionDefinition, Datatable } from '../../../../expressions/common';
|
||||
import { PARTITION_LABELS_FUNCTION, PARTITION_LABELS_VALUE } from '../constants';
|
||||
import {
|
||||
ExpressionValuePartitionLabels,
|
||||
LabelPositions,
|
||||
PartitionLabelsArguments,
|
||||
ValueFormats,
|
||||
} from '../types';
|
||||
|
||||
export const partitionLabelsFunction = (): ExpressionFunctionDefinition<
|
||||
typeof PARTITION_LABELS_FUNCTION,
|
||||
Datatable | null,
|
||||
PartitionLabelsArguments,
|
||||
ExpressionValuePartitionLabels
|
||||
> => ({
|
||||
name: PARTITION_LABELS_FUNCTION,
|
||||
help: i18n.translate('expressionPartitionVis.partitionLabels.function.help', {
|
||||
defaultMessage: 'Generates the partition labels object',
|
||||
}),
|
||||
type: PARTITION_LABELS_VALUE,
|
||||
args: {
|
||||
show: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.show.help', {
|
||||
defaultMessage: 'Displays the partition chart labels',
|
||||
}),
|
||||
default: true,
|
||||
},
|
||||
position: {
|
||||
types: ['string'],
|
||||
default: 'default',
|
||||
help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.position.help', {
|
||||
defaultMessage: 'Defines the label position',
|
||||
}),
|
||||
options: [LabelPositions.DEFAULT, LabelPositions.INSIDE],
|
||||
},
|
||||
values: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.values.help', {
|
||||
defaultMessage: 'Displays the values inside the slices',
|
||||
}),
|
||||
default: true,
|
||||
},
|
||||
percentDecimals: {
|
||||
types: ['number'],
|
||||
help: i18n.translate(
|
||||
'expressionPartitionVis.partitionLabels.function.args.percentDecimals.help',
|
||||
{
|
||||
defaultMessage:
|
||||
'Defines the number of decimals that will appear on the values as percent',
|
||||
}
|
||||
),
|
||||
default: 2,
|
||||
},
|
||||
// Deprecated
|
||||
last_level: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.last_level.help', {
|
||||
defaultMessage: 'Show top level labels only for multilayer pie/donut charts',
|
||||
}),
|
||||
default: false,
|
||||
},
|
||||
// Deprecated
|
||||
truncate: {
|
||||
types: ['number', 'null'],
|
||||
help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.truncate.help', {
|
||||
defaultMessage:
|
||||
'Defines the number of characters that the slice value will display only for multilayer pie/donut charts',
|
||||
}),
|
||||
default: null,
|
||||
},
|
||||
valuesFormat: {
|
||||
types: ['string'],
|
||||
default: 'percent',
|
||||
help: i18n.translate(
|
||||
'expressionPartitionVis.partitionLabels.function.args.valuesFormat.help',
|
||||
{
|
||||
defaultMessage: 'Defines the format of the values',
|
||||
}
|
||||
),
|
||||
options: [ValueFormats.PERCENT, ValueFormats.VALUE],
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: PARTITION_LABELS_VALUE,
|
||||
show: args.show,
|
||||
position: args.position,
|
||||
percentDecimals: args.percentDecimals,
|
||||
values: args.values,
|
||||
truncate: args.truncate,
|
||||
valuesFormat: args.valuesFormat,
|
||||
last_level: args.last_level,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -12,21 +12,24 @@ import {
|
|||
EmptySizeRatios,
|
||||
LabelPositions,
|
||||
ValueFormats,
|
||||
LegendDisplay,
|
||||
} from '../types/expression_renderers';
|
||||
import { pieVisFunction } from './pie_vis_function';
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { Datatable } from '../../../../expressions/common/expression_types/specs';
|
||||
import { pieVisFunction } from './pie_vis_function';
|
||||
import { PARTITION_LABELS_VALUE } from '../constants';
|
||||
|
||||
describe('interpreter/functions#pie', () => {
|
||||
describe('interpreter/functions#pieVis', () => {
|
||||
const fn = functionWrapper(pieVisFunction());
|
||||
const context = {
|
||||
const context: Datatable = {
|
||||
type: 'datatable',
|
||||
rows: [{ 'col-0-1': 0 }],
|
||||
columns: [{ id: 'col-0-1', name: 'Count' }],
|
||||
} as unknown as Datatable;
|
||||
columns: [{ id: 'col-0-1', name: 'Count', meta: { type: 'number' } }],
|
||||
};
|
||||
|
||||
const visConfig: PieVisConfig = {
|
||||
addTooltip: true,
|
||||
addLegend: true,
|
||||
legendDisplay: LegendDisplay.SHOW,
|
||||
legendPosition: 'right',
|
||||
isDonut: true,
|
||||
emptySizeRatio: EmptySizeRatios.SMALL,
|
||||
|
@ -39,7 +42,7 @@ describe('interpreter/functions#pie', () => {
|
|||
name: 'kibana_palette',
|
||||
},
|
||||
labels: {
|
||||
type: 'pie_labels_value',
|
||||
type: PARTITION_LABELS_VALUE,
|
||||
show: false,
|
||||
values: true,
|
||||
position: LabelPositions.DEFAULT,
|
||||
|
@ -56,17 +59,67 @@ describe('interpreter/functions#pie', () => {
|
|||
params: {},
|
||||
},
|
||||
},
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 2,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 3,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure', async () => {
|
||||
it('returns an object with the correct structure for pie', async () => {
|
||||
const actual = await fn(context, { ...visConfig, isDonut: false });
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure for donut', async () => {
|
||||
const actual = await fn(context, visConfig);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('throws error if provided split row and split column at once', async () => {
|
||||
const splitDimension: ExpressionValueVisDimension = {
|
||||
type: 'vis_dimension',
|
||||
accessor: 3,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
fn(context, {
|
||||
...visConfig,
|
||||
splitColumn: [splitDimension],
|
||||
splitRow: [splitDimension],
|
||||
})
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('logs correct datatable to inspector', async () => {
|
||||
let loggedTable: Datatable;
|
||||
const handlers = {
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Position } from '@elastic/charts';
|
||||
import { EmptySizeRatios, LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
|
||||
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
|
||||
import { ChartTypes, PieVisExpressionFunctionDefinition } from '../types';
|
||||
import {
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
PARTITION_LABELS_VALUE,
|
||||
PIE_VIS_EXPRESSION_NAME,
|
||||
PARTITION_VIS_RENDERER_NAME,
|
||||
} from '../constants';
|
||||
import { errors, strings } from './i18n';
|
||||
|
||||
export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({
|
||||
name: PIE_VIS_EXPRESSION_NAME,
|
||||
type: 'render',
|
||||
inputTypes: ['datatable'],
|
||||
help: strings.getPieVisFunctionName(),
|
||||
args: {
|
||||
metric: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getMetricArgHelp(),
|
||||
required: true,
|
||||
},
|
||||
buckets: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getBucketsArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
splitColumn: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getSplitColumnArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
splitRow: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getSplitRowArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
addTooltip: {
|
||||
types: ['boolean'],
|
||||
help: strings.getAddTooltipArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
legendDisplay: {
|
||||
types: ['string'],
|
||||
help: strings.getLegendDisplayArgHelp(),
|
||||
options: [LegendDisplay.SHOW, LegendDisplay.HIDE, LegendDisplay.DEFAULT],
|
||||
default: LegendDisplay.HIDE,
|
||||
},
|
||||
legendPosition: {
|
||||
types: ['string'],
|
||||
help: strings.getLegendPositionArgHelp(),
|
||||
options: [Position.Top, Position.Right, Position.Bottom, Position.Left],
|
||||
},
|
||||
nestedLegend: {
|
||||
types: ['boolean'],
|
||||
help: strings.getNestedLegendArgHelp(),
|
||||
default: false,
|
||||
},
|
||||
truncateLegend: {
|
||||
types: ['boolean'],
|
||||
help: strings.getTruncateLegendArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
maxLegendLines: {
|
||||
types: ['number'],
|
||||
help: strings.getMaxLegendLinesArgHelp(),
|
||||
},
|
||||
distinctColors: {
|
||||
types: ['boolean'],
|
||||
help: strings.getDistinctColorsArgHelp(),
|
||||
default: false,
|
||||
},
|
||||
respectSourceOrder: {
|
||||
types: ['boolean'],
|
||||
help: strings.getRespectSourceOrderArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
isDonut: {
|
||||
types: ['boolean'],
|
||||
help: strings.getIsDonutArgHelp(),
|
||||
default: false,
|
||||
},
|
||||
emptySizeRatio: {
|
||||
types: ['number'],
|
||||
help: strings.getEmptySizeRatioArgHelp(),
|
||||
default: EmptySizeRatios.SMALL,
|
||||
},
|
||||
palette: {
|
||||
types: ['palette', 'system_palette'],
|
||||
help: strings.getPaletteArgHelp(),
|
||||
default: '{palette}',
|
||||
},
|
||||
labels: {
|
||||
types: [PARTITION_LABELS_VALUE],
|
||||
help: strings.getLabelsArgHelp(),
|
||||
default: `{${PARTITION_LABELS_FUNCTION}}`,
|
||||
},
|
||||
startFromSecondLargestSlice: {
|
||||
types: ['boolean'],
|
||||
help: strings.getStartFromSecondLargestSliceArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
if (args.splitColumn && args.splitRow) {
|
||||
throw new Error(errors.splitRowAndSplitColumnAreSpecifiedError());
|
||||
}
|
||||
|
||||
const visConfig: PartitionVisParams = {
|
||||
...args,
|
||||
palette: args.palette,
|
||||
dimensions: {
|
||||
metric: args.metric,
|
||||
buckets: args.buckets,
|
||||
splitColumn: args.splitColumn,
|
||||
splitRow: args.splitRow,
|
||||
},
|
||||
};
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
const logTable = prepareLogTable(context, [
|
||||
[[args.metric], strings.getSliceSizeHelp()],
|
||||
[args.buckets, strings.getSliceHelp()],
|
||||
[args.splitColumn, strings.getColumnSplitHelp()],
|
||||
[args.splitRow, strings.getRowSplitHelp()],
|
||||
]);
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', logTable);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: PARTITION_VIS_RENDERER_NAME,
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig,
|
||||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
visType: args.isDonut ? ChartTypes.DONUT : ChartTypes.PIE,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils';
|
||||
import {
|
||||
TreemapVisConfig,
|
||||
LabelPositions,
|
||||
ValueFormats,
|
||||
LegendDisplay,
|
||||
} from '../types/expression_renderers';
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { Datatable } from '../../../../expressions/common/expression_types/specs';
|
||||
import { treemapVisFunction } from './treemap_vis_function';
|
||||
import { PARTITION_LABELS_VALUE } from '../constants';
|
||||
|
||||
describe('interpreter/functions#treemapVis', () => {
|
||||
const fn = functionWrapper(treemapVisFunction());
|
||||
const context: Datatable = {
|
||||
type: 'datatable',
|
||||
rows: [{ 'col-0-1': 0, 'col-0-2': 0, 'col-0-3': 0, 'col-0-4': 0 }],
|
||||
columns: [
|
||||
{ id: 'col-0-1', name: 'Field 1', meta: { type: 'number' } },
|
||||
{ id: 'col-0-2', name: 'Field 2', meta: { type: 'number' } },
|
||||
{ id: 'col-0-3', name: 'Field 3', meta: { type: 'number' } },
|
||||
{ id: 'col-0-4', name: 'Field 4', meta: { type: 'number' } },
|
||||
],
|
||||
};
|
||||
|
||||
const visConfig: TreemapVisConfig = {
|
||||
addTooltip: true,
|
||||
legendDisplay: LegendDisplay.SHOW,
|
||||
legendPosition: 'right',
|
||||
nestedLegend: true,
|
||||
truncateLegend: true,
|
||||
maxLegendLines: 2,
|
||||
palette: {
|
||||
type: 'system_palette',
|
||||
name: 'kibana_palette',
|
||||
},
|
||||
labels: {
|
||||
type: PARTITION_LABELS_VALUE,
|
||||
show: false,
|
||||
values: true,
|
||||
position: LabelPositions.DEFAULT,
|
||||
valuesFormat: ValueFormats.PERCENT,
|
||||
percentDecimals: 2,
|
||||
truncate: 100,
|
||||
last_level: false,
|
||||
},
|
||||
metric: {
|
||||
type: 'vis_dimension',
|
||||
accessor: 0,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 2,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure', async () => {
|
||||
const actual = await fn(context, visConfig);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('throws error if provided more than 2 buckets', async () => {
|
||||
expect(() =>
|
||||
fn(context, {
|
||||
...visConfig,
|
||||
buckets: [
|
||||
...(visConfig.buckets ?? []),
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 3,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('throws error if provided split row and split column at once', async () => {
|
||||
const splitDimension: ExpressionValueVisDimension = {
|
||||
type: 'vis_dimension',
|
||||
accessor: 3,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
fn(context, {
|
||||
...visConfig,
|
||||
splitColumn: [splitDimension],
|
||||
splitRow: [splitDimension],
|
||||
})
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('logs correct datatable to inspector', async () => {
|
||||
let loggedTable: Datatable;
|
||||
const handlers = {
|
||||
inspectorAdapters: {
|
||||
tables: {
|
||||
logDatatable: (name: string, datatable: Datatable) => {
|
||||
loggedTable = datatable;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await fn(context, visConfig, handlers as any);
|
||||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
|
||||
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
|
||||
import { ChartTypes, TreemapVisExpressionFunctionDefinition } from '../types';
|
||||
import {
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
PARTITION_LABELS_VALUE,
|
||||
PARTITION_VIS_RENDERER_NAME,
|
||||
TREEMAP_VIS_EXPRESSION_NAME,
|
||||
} from '../constants';
|
||||
import { errors, strings } from './i18n';
|
||||
|
||||
export const treemapVisFunction = (): TreemapVisExpressionFunctionDefinition => ({
|
||||
name: TREEMAP_VIS_EXPRESSION_NAME,
|
||||
type: 'render',
|
||||
inputTypes: ['datatable'],
|
||||
help: strings.getPieVisFunctionName(),
|
||||
args: {
|
||||
metric: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getMetricArgHelp(),
|
||||
required: true,
|
||||
},
|
||||
buckets: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getBucketsArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
splitColumn: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getSplitColumnArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
splitRow: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getSplitRowArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
addTooltip: {
|
||||
types: ['boolean'],
|
||||
help: strings.getAddTooltipArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
legendDisplay: {
|
||||
types: ['string'],
|
||||
help: strings.getLegendDisplayArgHelp(),
|
||||
options: [LegendDisplay.SHOW, LegendDisplay.HIDE, LegendDisplay.DEFAULT],
|
||||
default: LegendDisplay.HIDE,
|
||||
},
|
||||
legendPosition: {
|
||||
types: ['string'],
|
||||
help: strings.getLegendPositionArgHelp(),
|
||||
},
|
||||
nestedLegend: {
|
||||
types: ['boolean'],
|
||||
help: strings.getNestedLegendArgHelp(),
|
||||
default: false,
|
||||
},
|
||||
truncateLegend: {
|
||||
types: ['boolean'],
|
||||
help: strings.getTruncateLegendArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
maxLegendLines: {
|
||||
types: ['number'],
|
||||
help: strings.getMaxLegendLinesArgHelp(),
|
||||
},
|
||||
palette: {
|
||||
types: ['palette', 'system_palette'],
|
||||
help: strings.getPaletteArgHelp(),
|
||||
default: '{palette}',
|
||||
},
|
||||
labels: {
|
||||
types: [PARTITION_LABELS_VALUE],
|
||||
help: strings.getLabelsArgHelp(),
|
||||
default: `{${PARTITION_LABELS_FUNCTION}}`,
|
||||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
const maxSupportedBuckets = 2;
|
||||
if ((args.buckets ?? []).length > maxSupportedBuckets) {
|
||||
throw new Error(errors.moreThanNBucketsAreNotSupportedError(maxSupportedBuckets));
|
||||
}
|
||||
|
||||
if (args.splitColumn && args.splitRow) {
|
||||
throw new Error(errors.splitRowAndSplitColumnAreSpecifiedError());
|
||||
}
|
||||
|
||||
const visConfig: PartitionVisParams = {
|
||||
...args,
|
||||
palette: args.palette,
|
||||
dimensions: {
|
||||
metric: args.metric,
|
||||
buckets: args.buckets,
|
||||
splitColumn: args.splitColumn,
|
||||
splitRow: args.splitRow,
|
||||
},
|
||||
};
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
const logTable = prepareLogTable(context, [
|
||||
[[args.metric], strings.getSliceSizeHelp()],
|
||||
[args.buckets, strings.getSliceHelp()],
|
||||
[args.splitColumn, strings.getColumnSplitHelp()],
|
||||
[args.splitRow, strings.getRowSplitHelp()],
|
||||
]);
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', logTable);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: PARTITION_VIS_RENDERER_NAME,
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig,
|
||||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
visType: ChartTypes.TREEMAP,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils';
|
||||
import {
|
||||
WaffleVisConfig,
|
||||
LabelPositions,
|
||||
ValueFormats,
|
||||
LegendDisplay,
|
||||
} from '../types/expression_renderers';
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { Datatable } from '../../../../expressions/common/expression_types/specs';
|
||||
import { waffleVisFunction } from './waffle_vis_function';
|
||||
import { PARTITION_LABELS_VALUE } from '../constants';
|
||||
|
||||
describe('interpreter/functions#waffleVis', () => {
|
||||
const fn = functionWrapper(waffleVisFunction());
|
||||
const context: Datatable = {
|
||||
type: 'datatable',
|
||||
rows: [{ 'col-0-1': 0, 'col-0-2': 0, 'col-0-3': 0, 'col-0-4': 0 }],
|
||||
columns: [
|
||||
{ id: 'col-0-1', name: 'Field 1', meta: { type: 'number' } },
|
||||
{ id: 'col-0-2', name: 'Field 2', meta: { type: 'number' } },
|
||||
{ id: 'col-0-3', name: 'Field 3', meta: { type: 'number' } },
|
||||
{ id: 'col-0-4', name: 'Field 4', meta: { type: 'number' } },
|
||||
],
|
||||
};
|
||||
|
||||
const visConfig: WaffleVisConfig = {
|
||||
addTooltip: true,
|
||||
showValuesInLegend: true,
|
||||
legendDisplay: LegendDisplay.SHOW,
|
||||
legendPosition: 'right',
|
||||
truncateLegend: true,
|
||||
maxLegendLines: 2,
|
||||
palette: {
|
||||
type: 'system_palette',
|
||||
name: 'kibana_palette',
|
||||
},
|
||||
labels: {
|
||||
type: PARTITION_LABELS_VALUE,
|
||||
show: false,
|
||||
values: true,
|
||||
position: LabelPositions.DEFAULT,
|
||||
valuesFormat: ValueFormats.PERCENT,
|
||||
percentDecimals: 2,
|
||||
truncate: 100,
|
||||
last_level: false,
|
||||
},
|
||||
metric: {
|
||||
type: 'vis_dimension',
|
||||
accessor: 0,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
bucket: {
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns an object with the correct structure', async () => {
|
||||
const actual = await fn(context, visConfig);
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('throws error if provided split row and split column at once', async () => {
|
||||
const splitDimension: ExpressionValueVisDimension = {
|
||||
type: 'vis_dimension',
|
||||
accessor: 3,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
fn(context, {
|
||||
...visConfig,
|
||||
splitColumn: [splitDimension],
|
||||
splitRow: [splitDimension],
|
||||
})
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('logs correct datatable to inspector', async () => {
|
||||
let loggedTable: Datatable;
|
||||
const handlers = {
|
||||
inspectorAdapters: {
|
||||
tables: {
|
||||
logDatatable: (name: string, datatable: Datatable) => {
|
||||
loggedTable = datatable;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
await fn(context, visConfig, handlers as any);
|
||||
|
||||
expect(loggedTable!).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers';
|
||||
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
|
||||
import { ChartTypes, WaffleVisExpressionFunctionDefinition } from '../types';
|
||||
import {
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
PARTITION_LABELS_VALUE,
|
||||
PARTITION_VIS_RENDERER_NAME,
|
||||
WAFFLE_VIS_EXPRESSION_NAME,
|
||||
} from '../constants';
|
||||
import { errors, strings } from './i18n';
|
||||
|
||||
export const waffleVisFunction = (): WaffleVisExpressionFunctionDefinition => ({
|
||||
name: WAFFLE_VIS_EXPRESSION_NAME,
|
||||
type: 'render',
|
||||
inputTypes: ['datatable'],
|
||||
help: strings.getPieVisFunctionName(),
|
||||
args: {
|
||||
metric: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getMetricArgHelp(),
|
||||
required: true,
|
||||
},
|
||||
bucket: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getBucketArgHelp(),
|
||||
},
|
||||
splitColumn: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getSplitColumnArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
splitRow: {
|
||||
types: ['vis_dimension'],
|
||||
help: strings.getSplitRowArgHelp(),
|
||||
multi: true,
|
||||
},
|
||||
addTooltip: {
|
||||
types: ['boolean'],
|
||||
help: strings.getAddTooltipArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
legendDisplay: {
|
||||
types: ['string'],
|
||||
help: strings.getLegendDisplayArgHelp(),
|
||||
options: [LegendDisplay.SHOW, LegendDisplay.HIDE, LegendDisplay.DEFAULT],
|
||||
default: LegendDisplay.HIDE,
|
||||
},
|
||||
legendPosition: {
|
||||
types: ['string'],
|
||||
help: strings.getLegendPositionArgHelp(),
|
||||
},
|
||||
truncateLegend: {
|
||||
types: ['boolean'],
|
||||
help: strings.getTruncateLegendArgHelp(),
|
||||
default: true,
|
||||
},
|
||||
maxLegendLines: {
|
||||
types: ['number'],
|
||||
help: strings.getMaxLegendLinesArgHelp(),
|
||||
},
|
||||
palette: {
|
||||
types: ['palette', 'system_palette'],
|
||||
help: strings.getPaletteArgHelp(),
|
||||
default: '{palette}',
|
||||
},
|
||||
labels: {
|
||||
types: [PARTITION_LABELS_VALUE],
|
||||
help: strings.getLabelsArgHelp(),
|
||||
default: `{${PARTITION_LABELS_FUNCTION}}`,
|
||||
},
|
||||
showValuesInLegend: {
|
||||
types: ['boolean'],
|
||||
help: strings.getShowValuesInLegendArgHelp(),
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
if (args.splitColumn && args.splitRow) {
|
||||
throw new Error(errors.splitRowAndSplitColumnAreSpecifiedError());
|
||||
}
|
||||
|
||||
const buckets = args.bucket ? [args.bucket] : [];
|
||||
const visConfig: PartitionVisParams = {
|
||||
...args,
|
||||
palette: args.palette,
|
||||
dimensions: {
|
||||
metric: args.metric,
|
||||
buckets,
|
||||
splitColumn: args.splitColumn,
|
||||
splitRow: args.splitRow,
|
||||
},
|
||||
};
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
const logTable = prepareLogTable(context, [
|
||||
[[args.metric], strings.getSliceSizeHelp()],
|
||||
[buckets, strings.getSliceHelp()],
|
||||
[args.splitColumn, strings.getColumnSplitHelp()],
|
||||
[args.splitRow, strings.getRowSplitHelp()],
|
||||
]);
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', logTable);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: PARTITION_VIS_RENDERER_NAME,
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig,
|
||||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
visType: ChartTypes.WAFFLE,
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
52
src/plugins/chart_expressions/expression_partition_vis/common/index.ts
Executable file
52
src/plugins/chart_expressions/expression_partition_vis/common/index.ts
Executable file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export {
|
||||
PLUGIN_ID,
|
||||
PLUGIN_NAME,
|
||||
PIE_VIS_EXPRESSION_NAME,
|
||||
TREEMAP_VIS_EXPRESSION_NAME,
|
||||
MOSAIC_VIS_EXPRESSION_NAME,
|
||||
WAFFLE_VIS_EXPRESSION_NAME,
|
||||
PARTITION_LABELS_VALUE,
|
||||
PARTITION_LABELS_FUNCTION,
|
||||
} from './constants';
|
||||
|
||||
export {
|
||||
pieVisFunction,
|
||||
treemapVisFunction,
|
||||
waffleVisFunction,
|
||||
mosaicVisFunction,
|
||||
partitionLabelsFunction,
|
||||
} from './expression_functions';
|
||||
|
||||
export type {
|
||||
ExpressionValuePartitionLabels,
|
||||
PieVisExpressionFunctionDefinition,
|
||||
TreemapVisExpressionFunctionDefinition,
|
||||
MosaicVisExpressionFunctionDefinition,
|
||||
WaffleVisExpressionFunctionDefinition,
|
||||
} from './types/expression_functions';
|
||||
|
||||
export type {
|
||||
PartitionVisParams,
|
||||
PieVisConfig,
|
||||
TreemapVisConfig,
|
||||
MosaicVisConfig,
|
||||
WaffleVisConfig,
|
||||
LabelsParams,
|
||||
Dimension,
|
||||
Dimensions,
|
||||
} from './types/expression_renderers';
|
||||
|
||||
export {
|
||||
ValueFormats,
|
||||
LabelPositions,
|
||||
EmptySizeRatios,
|
||||
LegendDisplay,
|
||||
} from './types/expression_renderers';
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
PARTITION_LABELS_VALUE,
|
||||
PIE_VIS_EXPRESSION_NAME,
|
||||
TREEMAP_VIS_EXPRESSION_NAME,
|
||||
MOSAIC_VIS_EXPRESSION_NAME,
|
||||
WAFFLE_VIS_EXPRESSION_NAME,
|
||||
} from '../constants';
|
||||
import {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueRender,
|
||||
ExpressionValueBoxed,
|
||||
} from '../../../../expressions/common';
|
||||
import {
|
||||
RenderValue,
|
||||
PieVisConfig,
|
||||
LabelPositions,
|
||||
ValueFormats,
|
||||
TreemapVisConfig,
|
||||
MosaicVisConfig,
|
||||
WaffleVisConfig,
|
||||
} from './expression_renderers';
|
||||
|
||||
export interface PartitionLabelsArguments {
|
||||
show: boolean;
|
||||
position: LabelPositions;
|
||||
values: boolean;
|
||||
valuesFormat: ValueFormats;
|
||||
percentDecimals: number;
|
||||
/** @deprecated This field is deprecated and going to be removed in the futher release versions. */
|
||||
truncate?: number | null;
|
||||
/** @deprecated This field is deprecated and going to be removed in the futher release versions. */
|
||||
last_level?: boolean;
|
||||
}
|
||||
|
||||
export type ExpressionValuePartitionLabels = ExpressionValueBoxed<
|
||||
typeof PARTITION_LABELS_VALUE,
|
||||
{
|
||||
show: boolean;
|
||||
position: LabelPositions;
|
||||
values: boolean;
|
||||
valuesFormat: ValueFormats;
|
||||
percentDecimals: number;
|
||||
/** @deprecated This field is deprecated and going to be removed in the futher release versions. */
|
||||
truncate?: number | null;
|
||||
/** @deprecated This field is deprecated and going to be removed in the futher release versions. */
|
||||
last_level?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export type PieVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof PIE_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
PieVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
>;
|
||||
|
||||
export type TreemapVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof TREEMAP_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
TreemapVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
>;
|
||||
|
||||
export type MosaicVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof MOSAIC_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
MosaicVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
>;
|
||||
|
||||
export type WaffleVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof WAFFLE_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
WaffleVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
>;
|
||||
|
||||
export enum ChartTypes {
|
||||
PIE = 'pie',
|
||||
DONUT = 'donut',
|
||||
TREEMAP = 'treemap',
|
||||
MOSAIC = 'mosaic',
|
||||
WAFFLE = 'waffle',
|
||||
}
|
|
@ -11,7 +11,7 @@ import { Datatable, DatatableColumn } from '../../../../expressions/common';
|
|||
import { SerializedFieldFormat } from '../../../../field_formats/common';
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { PaletteOutput } from '../../../../charts/common';
|
||||
import { ExpressionValuePieLabels } from './expression_functions';
|
||||
import { ChartTypes, ExpressionValuePartitionLabels } from './expression_functions';
|
||||
|
||||
export enum EmptySizeRatios {
|
||||
SMALL = 0.3,
|
||||
|
@ -28,7 +28,7 @@ export interface Dimension {
|
|||
}
|
||||
|
||||
export interface Dimensions {
|
||||
metric: ExpressionValueVisDimension;
|
||||
metric?: ExpressionValueVisDimension;
|
||||
buckets?: ExpressionValueVisDimension[];
|
||||
splitRow?: ExpressionValueVisDimension[];
|
||||
splitColumn?: ExpressionValueVisDimension[];
|
||||
|
@ -36,45 +36,74 @@ export interface Dimensions {
|
|||
|
||||
export interface LabelsParams {
|
||||
show: boolean;
|
||||
last_level: boolean;
|
||||
position: LabelPositions;
|
||||
values: boolean;
|
||||
truncate: number | null;
|
||||
valuesFormat: ValueFormats;
|
||||
percentDecimals: number;
|
||||
/** @deprecated This field is deprecated and going to be removed in the futher release versions. */
|
||||
truncate?: number | null;
|
||||
/** @deprecated This field is deprecated and going to be removed in the futher release versions. */
|
||||
last_level?: boolean;
|
||||
}
|
||||
|
||||
interface PieCommonParams {
|
||||
interface VisCommonParams {
|
||||
addTooltip: boolean;
|
||||
addLegend: boolean;
|
||||
legendDisplay: LegendDisplay;
|
||||
legendPosition: Position;
|
||||
nestedLegend: boolean;
|
||||
truncateLegend: boolean;
|
||||
maxLegendLines: number;
|
||||
distinctColors: boolean;
|
||||
isDonut: boolean;
|
||||
emptySizeRatio?: EmptySizeRatios;
|
||||
}
|
||||
|
||||
export interface PieVisParams extends PieCommonParams {
|
||||
dimensions: Dimensions;
|
||||
labels: LabelsParams;
|
||||
palette: PaletteOutput;
|
||||
}
|
||||
|
||||
export interface PieVisConfig extends PieCommonParams {
|
||||
buckets?: ExpressionValueVisDimension[];
|
||||
interface VisCommonConfig extends VisCommonParams {
|
||||
metric: ExpressionValueVisDimension;
|
||||
splitColumn?: ExpressionValueVisDimension[];
|
||||
splitRow?: ExpressionValueVisDimension[];
|
||||
labels: ExpressionValuePieLabels;
|
||||
labels: ExpressionValuePartitionLabels;
|
||||
palette: PaletteOutput;
|
||||
}
|
||||
|
||||
export interface PartitionVisParams extends VisCommonParams {
|
||||
dimensions: Dimensions;
|
||||
labels: LabelsParams;
|
||||
palette: PaletteOutput;
|
||||
isDonut?: boolean;
|
||||
showValuesInLegend?: boolean;
|
||||
respectSourceOrder?: boolean;
|
||||
emptySizeRatio?: EmptySizeRatios;
|
||||
startFromSecondLargestSlice?: boolean;
|
||||
distinctColors?: boolean;
|
||||
nestedLegend?: boolean;
|
||||
}
|
||||
|
||||
export interface PieVisConfig extends VisCommonConfig {
|
||||
buckets?: ExpressionValueVisDimension[];
|
||||
isDonut: boolean;
|
||||
emptySizeRatio?: EmptySizeRatios;
|
||||
respectSourceOrder?: boolean;
|
||||
startFromSecondLargestSlice?: boolean;
|
||||
distinctColors?: boolean;
|
||||
nestedLegend: boolean;
|
||||
}
|
||||
|
||||
export interface TreemapVisConfig extends VisCommonConfig {
|
||||
buckets?: ExpressionValueVisDimension[];
|
||||
nestedLegend: boolean;
|
||||
}
|
||||
|
||||
export interface MosaicVisConfig extends VisCommonConfig {
|
||||
buckets?: ExpressionValueVisDimension[];
|
||||
nestedLegend: boolean;
|
||||
}
|
||||
|
||||
export interface WaffleVisConfig extends VisCommonConfig {
|
||||
bucket?: ExpressionValueVisDimension;
|
||||
showValuesInLegend: boolean;
|
||||
}
|
||||
|
||||
export interface RenderValue {
|
||||
visData: Datatable;
|
||||
visType: string;
|
||||
visConfig: PieVisParams;
|
||||
visType: ChartTypes;
|
||||
visConfig: PartitionVisParams;
|
||||
syncColors: boolean;
|
||||
}
|
||||
|
||||
|
@ -88,6 +117,12 @@ export enum ValueFormats {
|
|||
VALUE = 'value',
|
||||
}
|
||||
|
||||
export enum LegendDisplay {
|
||||
SHOW = 'show',
|
||||
HIDE = 'hide',
|
||||
DEFAULT = 'default',
|
||||
}
|
||||
|
||||
export interface BucketColumns extends DatatableColumn {
|
||||
format?: {
|
||||
id?: string;
|
|
@ -9,11 +9,11 @@
|
|||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../',
|
||||
roots: ['<rootDir>/src/plugins/chart_expressions/expression_pie'],
|
||||
roots: ['<rootDir>/src/plugins/chart_expressions/expression_partition_vis'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/src/plugins/chart_expressions/expression_pie',
|
||||
'<rootDir>/target/kibana-coverage/jest/src/plugins/chart_expressions/expression_partition_vis',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/src/plugins/chart_expressions/expression_pie/{common,public,server}/**/*.{ts,tsx}',
|
||||
'<rootDir>/src/plugins/chart_expressions/expression_partition_vis/{common,public,server}/**/*.{ts,tsx}',
|
||||
],
|
||||
};
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"id": "expressionPie",
|
||||
"id": "expressionPartitionVis",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"owner": {
|
||||
"name": "Vis Editors",
|
||||
"githubTeam": "kibana-vis-editors"
|
||||
},
|
||||
"description": "Expression Pie plugin adds a `pie` renderer and function to the expression plugin. The renderer will display the `pie` chart.",
|
||||
"description": "Expression Partition Visualization plugin adds a `partitionVis` renderer and `pieVis`, `mosaicVis`, `treemapVis`, `waffleVis` functions to the expression plugin. The renderer will display the `pie`, `waffle`, `treemap` and `mosaic` charts.",
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"extraPublicDirs": [
|
|
@ -8,6 +8,6 @@
|
|||
|
||||
export const getFormatService = () => ({
|
||||
deserialize: (target: any) => ({
|
||||
convert: (text: string, format: string) => text,
|
||||
convert: (text: string, format: string) => `${text}`,
|
||||
}),
|
||||
});
|
|
@ -21,10 +21,15 @@ export const getPaletteRegistry = () => {
|
|||
'#AA6556',
|
||||
'#E7664C',
|
||||
];
|
||||
let counter = 0;
|
||||
const mockPalette: PaletteDefinition = {
|
||||
id: 'default',
|
||||
title: 'My Palette',
|
||||
getCategoricalColor: (_: SeriesLayer[]) => colors[0],
|
||||
getCategoricalColor: (_: SeriesLayer[]) => {
|
||||
counter++;
|
||||
if (counter > colors.length - 1) counter = 0;
|
||||
return colors[counter];
|
||||
},
|
||||
getCategoricalColors: (num: number) => colors,
|
||||
toExpression: () => ({
|
||||
type: 'expression',
|
|
@ -8,5 +8,9 @@
|
|||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ThemeService } from '../../../../charts/public/services';
|
||||
import { uiSettings } from './ui_settings';
|
||||
|
||||
export const theme = new ThemeService();
|
||||
const theme = new ThemeService();
|
||||
theme.init(uiSettings);
|
||||
|
||||
export { theme };
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { IUiSettingsClient, PublicUiSettingsParams, UserProvidedValues } from 'kibana/public';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export const uiSettings: IUiSettingsClient = {
|
||||
set: (key: string, value: any) => Promise.resolve(true),
|
||||
remove: (key: string) => Promise.resolve(true),
|
||||
isCustom: (key: string) => false,
|
||||
isOverridden: (key: string) => Boolean(uiSettings.getAll()[key].isOverridden),
|
||||
getUpdate$: () =>
|
||||
new Observable<{
|
||||
key: string;
|
||||
newValue: any;
|
||||
oldValue: any;
|
||||
}>(),
|
||||
isDeclared: (key: string) => true,
|
||||
isDefault: (key: string) => true,
|
||||
getUpdateErrors$: () => new Observable<Error>(),
|
||||
get: (key: string, defaultOverride?: any): any => uiSettings.getAll()[key] || defaultOverride,
|
||||
get$: (key: string) => new Observable<any>(uiSettings.get(key)),
|
||||
getAll: (): Readonly<Record<string, PublicUiSettingsParams & UserProvidedValues>> => {
|
||||
return {};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { Render } from '../../../../presentation_util/public/__stories__';
|
||||
import { getPartitionVisRenderer } from '../expression_renderers';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
import { palettes, theme, getStartDeps } from '../__mocks__';
|
||||
import { mosaicArgTypes, treemapMosaicConfig, data } from './shared';
|
||||
|
||||
const containerSize = {
|
||||
width: '700px',
|
||||
height: '700px',
|
||||
};
|
||||
|
||||
const PartitionVisRenderer = () => getPartitionVisRenderer({ palettes, theme, getStartDeps });
|
||||
|
||||
type Props = {
|
||||
visType: RenderValue['visType'];
|
||||
syncColors: RenderValue['syncColors'];
|
||||
} & RenderValue['visConfig'];
|
||||
|
||||
const PartitionVis: ComponentStory<FC<Props>> = ({
|
||||
visType,
|
||||
syncColors,
|
||||
children,
|
||||
...visConfig
|
||||
}) => (
|
||||
<Render
|
||||
renderer={PartitionVisRenderer}
|
||||
config={{ visType, syncColors, visConfig, visData: data }}
|
||||
{...containerSize}
|
||||
/>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'renderers/mosaicVis',
|
||||
component: PartitionVis,
|
||||
argTypes: mosaicArgTypes,
|
||||
};
|
||||
|
||||
const Default = PartitionVis.bind({});
|
||||
Default.args = { ...treemapMosaicConfig, visType: ChartTypes.MOSAIC, syncColors: false };
|
||||
|
||||
export { Default };
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { Render } from '../../../../presentation_util/public/__stories__';
|
||||
import { getPartitionVisRenderer } from '../expression_renderers';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
import { palettes, theme, getStartDeps } from '../__mocks__';
|
||||
import { pieDonutArgTypes, pieConfig, data } from './shared';
|
||||
|
||||
const containerSize = {
|
||||
width: '700px',
|
||||
height: '700px',
|
||||
};
|
||||
|
||||
const PartitionVisRenderer = () => getPartitionVisRenderer({ palettes, theme, getStartDeps });
|
||||
|
||||
type Props = {
|
||||
visType: RenderValue['visType'];
|
||||
syncColors: RenderValue['syncColors'];
|
||||
} & RenderValue['visConfig'];
|
||||
|
||||
const PartitionVis: ComponentStory<FC<Props>> = ({
|
||||
visType,
|
||||
syncColors,
|
||||
children,
|
||||
...visConfig
|
||||
}) => (
|
||||
<Render
|
||||
renderer={PartitionVisRenderer}
|
||||
config={{ visType, syncColors, visConfig, visData: data }}
|
||||
{...containerSize}
|
||||
/>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'renderers/pieVis',
|
||||
component: PartitionVis,
|
||||
argTypes: pieDonutArgTypes,
|
||||
};
|
||||
|
||||
const Default = PartitionVis.bind({});
|
||||
Default.args = { ...pieConfig, visType: ChartTypes.PIE, syncColors: false };
|
||||
|
||||
export { Default };
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Position } from '@elastic/charts';
|
||||
import { ArgTypes } from '@storybook/addons';
|
||||
import { EmptySizeRatios, LegendDisplay } from '../../../common';
|
||||
import { ChartTypes } from '../../../common/types';
|
||||
|
||||
const visConfigName = 'visConfig';
|
||||
|
||||
export const argTypes: ArgTypes = {
|
||||
addTooltip: {
|
||||
name: `${visConfigName}.addTooltip`,
|
||||
description: 'Add tooltip on hover',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: true } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
legendDisplay: {
|
||||
name: `${visConfigName}.legendDisplay`,
|
||||
description: 'Legend mode of displaying',
|
||||
type: { name: 'string', required: false },
|
||||
table: { type: { summary: 'string' }, defaultValue: { summary: LegendDisplay.HIDE } },
|
||||
options: Object.values(LegendDisplay),
|
||||
control: { type: 'select' },
|
||||
},
|
||||
legendPosition: {
|
||||
name: `${visConfigName}.legendPosition`,
|
||||
description: 'Legend position',
|
||||
type: { name: 'string', required: false },
|
||||
table: { type: { summary: 'string' }, defaultValue: { summary: Position.Bottom } },
|
||||
options: Object.values(Position),
|
||||
control: { type: 'select' },
|
||||
},
|
||||
truncateLegend: {
|
||||
name: `${visConfigName}.truncateLegend`,
|
||||
description: 'Truncate too long legend',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: true } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
maxLegendLines: {
|
||||
name: `${visConfigName}.maxLegendLines`,
|
||||
description: 'Legend maximum number of lines',
|
||||
type: { name: 'number', required: false },
|
||||
table: { type: { summary: 'number' } },
|
||||
control: { type: 'number' },
|
||||
},
|
||||
palette: {
|
||||
name: `${visConfigName}.palette`,
|
||||
description: 'Palette',
|
||||
type: { name: 'palette', required: false },
|
||||
table: { type: { summary: 'object' } },
|
||||
control: { type: 'object' },
|
||||
},
|
||||
labels: {
|
||||
name: `${visConfigName}.labels`,
|
||||
description: 'Labels configuration',
|
||||
type: { name: 'object', required: false },
|
||||
table: {
|
||||
type: {
|
||||
summary: 'object',
|
||||
detail: `Labels configuration consists of further fields:
|
||||
- show: boolean. Default: true.
|
||||
- position: string. Options: 'default', 'inside'. Default: 'default'.
|
||||
- values: boolean. Default: true.
|
||||
- percentDecimals: number. Default: 2.
|
||||
- last_level: boolean. Default: false. DEPRECATED.
|
||||
- truncate: number. Default: null.
|
||||
- valuesFormat: string. Options: 'percent', 'value'. Default: percent.
|
||||
`,
|
||||
},
|
||||
},
|
||||
control: { type: 'object' },
|
||||
},
|
||||
dimensions: {
|
||||
name: `${visConfigName}.dimensions`,
|
||||
description: 'dimensions configuration',
|
||||
type: { name: 'object', required: false },
|
||||
table: {
|
||||
type: {
|
||||
summary: 'object',
|
||||
detail: `Dimensions configuration consists of two fields:
|
||||
- metric: visdimension.
|
||||
- buckets: visdimension[].
|
||||
`,
|
||||
},
|
||||
},
|
||||
control: { type: 'object' },
|
||||
},
|
||||
};
|
||||
|
||||
export const pieDonutArgTypes: ArgTypes = {
|
||||
...argTypes,
|
||||
visType: {
|
||||
name: `visType`,
|
||||
description: 'Type of the chart',
|
||||
type: { name: 'string', required: false },
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: `${ChartTypes.PIE} | ${ChartTypes.DONUT}` },
|
||||
},
|
||||
control: { type: 'text', disable: true },
|
||||
},
|
||||
isDonut: {
|
||||
name: `${visConfigName}.isDonut`,
|
||||
description: 'Render a donut chart',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: false } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
emptySizeRatio: {
|
||||
name: `${visConfigName}.emptySizeRatio`,
|
||||
description: 'The hole size of the donut chart',
|
||||
type: { name: 'number', required: false },
|
||||
table: { type: { summary: 'number' }, defaultValue: { summary: EmptySizeRatios.SMALL } },
|
||||
options: [EmptySizeRatios.SMALL, EmptySizeRatios.MEDIUM, EmptySizeRatios.LARGE],
|
||||
control: { type: 'select' },
|
||||
},
|
||||
distinctColors: {
|
||||
name: `${visConfigName}.distinctColors`,
|
||||
description: 'Enable distinct colors',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: false } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
respectSourceOrder: {
|
||||
name: `${visConfigName}.respectSourceOrder`,
|
||||
description: 'Save default order of the incomming data',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: true } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
startFromSecondLargestSlice: {
|
||||
name: `${visConfigName}.startFromSecondLargestSlice`,
|
||||
description: 'Start placement of slices from the second largest slice',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: true } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
nestedLegend: {
|
||||
name: `${visConfigName}.nestedLegend`,
|
||||
description: 'Enable nested legend',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: false } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
};
|
||||
|
||||
export const treemapArgTypes: ArgTypes = {
|
||||
visType: {
|
||||
name: `visType`,
|
||||
description: 'Type of the chart',
|
||||
type: { name: 'string', required: false },
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: `${ChartTypes.TREEMAP}` },
|
||||
},
|
||||
control: { type: 'text', disable: true },
|
||||
},
|
||||
...argTypes,
|
||||
nestedLegend: {
|
||||
name: `${visConfigName}.nestedLegend`,
|
||||
description: 'Enable nested legend',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: false } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
};
|
||||
|
||||
export const mosaicArgTypes: ArgTypes = {
|
||||
visType: {
|
||||
name: `visType`,
|
||||
description: 'Type of the chart',
|
||||
type: { name: 'string', required: false },
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: `${ChartTypes.MOSAIC}` },
|
||||
},
|
||||
control: { type: 'text', disable: true },
|
||||
},
|
||||
...argTypes,
|
||||
nestedLegend: {
|
||||
name: `${visConfigName}.nestedLegend`,
|
||||
description: 'Enable nested legend',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: false } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
};
|
||||
|
||||
export const waffleArgTypes: ArgTypes = {
|
||||
visType: {
|
||||
name: `visType`,
|
||||
description: 'Type of the chart',
|
||||
type: { name: 'string', required: false },
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: `${ChartTypes.WAFFLE}` },
|
||||
},
|
||||
control: { type: 'text', disable: true },
|
||||
},
|
||||
...argTypes,
|
||||
showValuesInLegend: {
|
||||
name: `${visConfigName}.nestedLegend`,
|
||||
description: 'Enable displaying values in the legend',
|
||||
type: { name: 'boolean', required: false },
|
||||
table: { type: { summary: 'boolean' }, defaultValue: { summary: false } },
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
};
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { Position } from '@elastic/charts';
|
||||
import {
|
||||
LabelPositions,
|
||||
LegendDisplay,
|
||||
RenderValue,
|
||||
PartitionVisParams,
|
||||
ValueFormats,
|
||||
} from '../../../common/types';
|
||||
|
||||
export const config: RenderValue['visConfig'] = {
|
||||
addTooltip: true,
|
||||
legendDisplay: LegendDisplay.HIDE,
|
||||
truncateLegend: true,
|
||||
respectSourceOrder: true,
|
||||
legendPosition: Position.Bottom,
|
||||
maxLegendLines: 1,
|
||||
palette: {
|
||||
type: 'palette',
|
||||
name: 'system_palette',
|
||||
},
|
||||
labels: {
|
||||
show: true,
|
||||
position: LabelPositions.DEFAULT,
|
||||
percentDecimals: 2,
|
||||
values: true,
|
||||
truncate: 0,
|
||||
valuesFormat: ValueFormats.PERCENT,
|
||||
last_level: false,
|
||||
},
|
||||
dimensions: {
|
||||
metric: {
|
||||
type: 'vis_dimension',
|
||||
accessor: {
|
||||
id: 'percent_uptime',
|
||||
name: 'percent_uptime',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
format: {
|
||||
id: 'string',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const pieConfig: PartitionVisParams = {
|
||||
...config,
|
||||
isDonut: false,
|
||||
emptySizeRatio: 0,
|
||||
distinctColors: false,
|
||||
nestedLegend: false,
|
||||
dimensions: {
|
||||
...config.dimensions,
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: {
|
||||
id: 'project',
|
||||
name: 'project',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
format: {
|
||||
id: 'string',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
startFromSecondLargestSlice: true,
|
||||
};
|
||||
|
||||
export const treemapMosaicConfig: PartitionVisParams = {
|
||||
...config,
|
||||
nestedLegend: false,
|
||||
dimensions: {
|
||||
...config.dimensions,
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: {
|
||||
id: 'project',
|
||||
name: 'project',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
format: {
|
||||
id: 'string',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const waffleConfig: PartitionVisParams = {
|
||||
...config,
|
||||
dimensions: {
|
||||
...config.dimensions,
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: {
|
||||
id: 'project',
|
||||
name: 'project',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
format: {
|
||||
id: 'string',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
showValuesInLegend: false,
|
||||
};
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { RenderValue } from '../../../common/types';
|
||||
|
||||
export const data: RenderValue['visData'] = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: '@timestamp',
|
||||
name: '@timestamp',
|
||||
meta: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'time',
|
||||
name: 'time',
|
||||
meta: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cost',
|
||||
name: 'cost',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'username',
|
||||
name: 'username',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'price',
|
||||
name: 'price',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'age',
|
||||
name: 'age',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'country',
|
||||
name: 'country',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'state',
|
||||
name: 'state',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'project',
|
||||
name: 'project',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'percent_uptime',
|
||||
name: 'percent_uptime',
|
||||
meta: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
age: 63,
|
||||
cost: 32.15,
|
||||
country: 'US',
|
||||
price: 53,
|
||||
project: 'elasticsearch',
|
||||
state: 'running',
|
||||
time: 1546334211208,
|
||||
'@timestamp': 1546334211208,
|
||||
username: 'aevans2e',
|
||||
percent_uptime: 0.83,
|
||||
},
|
||||
{
|
||||
age: 68,
|
||||
cost: 20.52,
|
||||
country: 'JP',
|
||||
price: 33,
|
||||
project: 'beats',
|
||||
state: 'done',
|
||||
time: 1546351551031,
|
||||
'@timestamp': 1546351551031,
|
||||
username: 'aking2c',
|
||||
percent_uptime: 0.9,
|
||||
},
|
||||
{
|
||||
age: 57,
|
||||
cost: 21.15,
|
||||
country: 'UK',
|
||||
price: 59,
|
||||
project: 'apm',
|
||||
state: 'running',
|
||||
time: 1546352631083,
|
||||
'@timestamp': 1546352631083,
|
||||
username: 'mmoore2o',
|
||||
percent_uptime: 0.96,
|
||||
},
|
||||
{
|
||||
age: 73,
|
||||
cost: 35.64,
|
||||
country: 'CN',
|
||||
price: 71,
|
||||
project: 'machine-learning',
|
||||
state: 'start',
|
||||
time: 1546402490956,
|
||||
'@timestamp': 1546402490956,
|
||||
username: 'wrodriguez1r',
|
||||
percent_uptime: 0.61,
|
||||
},
|
||||
{
|
||||
age: 38,
|
||||
cost: 27.19,
|
||||
country: 'TZ',
|
||||
price: 36,
|
||||
project: 'kibana',
|
||||
state: 'done',
|
||||
time: 1546467111351,
|
||||
'@timestamp': 1546467111351,
|
||||
username: 'wrodriguez1r',
|
||||
percent_uptime: 0.72,
|
||||
},
|
||||
{
|
||||
age: 61,
|
||||
cost: 49.95,
|
||||
country: 'NL',
|
||||
price: 65,
|
||||
project: 'machine-learning',
|
||||
state: 'start',
|
||||
time: 1546473771019,
|
||||
'@timestamp': 1546473771019,
|
||||
username: 'mmoore2o',
|
||||
percent_uptime: 0.72,
|
||||
},
|
||||
{
|
||||
age: 53,
|
||||
cost: 27.36,
|
||||
country: 'JP',
|
||||
price: 60,
|
||||
project: 'x-pack',
|
||||
state: 'running',
|
||||
time: 1546482171310,
|
||||
'@timestamp': 1546482171310,
|
||||
username: 'hcrawford2h',
|
||||
percent_uptime: 0.65,
|
||||
},
|
||||
{
|
||||
age: 31,
|
||||
cost: 33.77,
|
||||
country: 'AZ',
|
||||
price: 77,
|
||||
project: 'kibana',
|
||||
state: 'start',
|
||||
time: 1546493451206,
|
||||
'@timestamp': 1546493451206,
|
||||
username: 'aking2c',
|
||||
percent_uptime: 0.92,
|
||||
},
|
||||
{
|
||||
age: 71,
|
||||
cost: 20.2,
|
||||
country: 'TZ',
|
||||
price: 57,
|
||||
project: 'swiftype',
|
||||
state: 'running',
|
||||
time: 1546494651235,
|
||||
'@timestamp': 1546494651235,
|
||||
username: 'jlawson2p',
|
||||
percent_uptime: 0.59,
|
||||
},
|
||||
{
|
||||
age: 54,
|
||||
cost: 36.65,
|
||||
country: 'TZ',
|
||||
price: 72,
|
||||
project: 'apm',
|
||||
state: 'done',
|
||||
time: 1546498431195,
|
||||
'@timestamp': 1546498431195,
|
||||
username: 'aking2c',
|
||||
percent_uptime: 1,
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { data } from './data';
|
||||
export { config, pieConfig, treemapMosaicConfig, waffleConfig } from './config';
|
||||
export {
|
||||
argTypes,
|
||||
pieDonutArgTypes,
|
||||
treemapArgTypes,
|
||||
mosaicArgTypes,
|
||||
waffleArgTypes,
|
||||
} from './arg_types';
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { Render } from '../../../../presentation_util/public/__stories__';
|
||||
import { getPartitionVisRenderer } from '../expression_renderers';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
import { palettes, theme, getStartDeps } from '../__mocks__';
|
||||
import { treemapArgTypes, treemapMosaicConfig, data } from './shared';
|
||||
|
||||
const containerSize = {
|
||||
width: '700px',
|
||||
height: '700px',
|
||||
};
|
||||
|
||||
const PartitionVisRenderer = () => getPartitionVisRenderer({ palettes, theme, getStartDeps });
|
||||
|
||||
type Props = {
|
||||
visType: RenderValue['visType'];
|
||||
syncColors: RenderValue['syncColors'];
|
||||
} & RenderValue['visConfig'];
|
||||
|
||||
const PartitionVis: ComponentStory<FC<Props>> = ({
|
||||
visType,
|
||||
syncColors,
|
||||
children,
|
||||
...visConfig
|
||||
}) => (
|
||||
<Render
|
||||
renderer={PartitionVisRenderer}
|
||||
config={{ visType, syncColors, visConfig, visData: data }}
|
||||
{...containerSize}
|
||||
/>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'renderers/treemapVis',
|
||||
component: PartitionVis,
|
||||
argTypes: treemapArgTypes,
|
||||
};
|
||||
|
||||
const Default = PartitionVis.bind({});
|
||||
Default.args = { ...treemapMosaicConfig, visType: ChartTypes.TREEMAP, syncColors: false };
|
||||
|
||||
export { Default };
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import { Render } from '../../../../presentation_util/public/__stories__';
|
||||
import { getPartitionVisRenderer } from '../expression_renderers';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
import { palettes, theme, getStartDeps } from '../__mocks__';
|
||||
import { waffleArgTypes, waffleConfig, data } from './shared';
|
||||
|
||||
const containerSize = {
|
||||
width: '700px',
|
||||
height: '700px',
|
||||
};
|
||||
|
||||
const PartitionVisRenderer = () => getPartitionVisRenderer({ palettes, theme, getStartDeps });
|
||||
|
||||
type Props = {
|
||||
visType: RenderValue['visType'];
|
||||
syncColors: RenderValue['syncColors'];
|
||||
} & RenderValue['visConfig'];
|
||||
|
||||
const PartitionVis: ComponentStory<FC<Props>> = ({
|
||||
visType,
|
||||
syncColors,
|
||||
children,
|
||||
...visConfig
|
||||
}) => (
|
||||
<Render
|
||||
renderer={PartitionVisRenderer}
|
||||
config={{ visType, syncColors, visConfig, visData: data }}
|
||||
{...containerSize}
|
||||
/>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'renderers/waffleVis',
|
||||
component: PartitionVis,
|
||||
argTypes: waffleArgTypes,
|
||||
};
|
||||
|
||||
const Default = PartitionVis.bind({});
|
||||
Default.args = { ...waffleConfig, visType: ChartTypes.WAFFLE, syncColors: false };
|
||||
|
||||
export { Default };
|
File diff suppressed because it is too large
Load diff
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { getPieVisRenderer } from './pie_vis_renderer';
|
||||
export * from './partition_vis_component';
|
|
@ -9,15 +9,15 @@
|
|||
import { EuiThemeComputed } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
export const pieChartWrapperStyle = css({
|
||||
export const partitionVisWrapperStyle = css({
|
||||
display: 'flex',
|
||||
flex: '1 1 auto',
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
});
|
||||
|
||||
export const pieChartContainerStyleFactory = (theme: EuiThemeComputed) => css`
|
||||
${pieChartWrapperStyle};
|
||||
export const partitionVisContainerStyleFactory = (theme: EuiThemeComputed) => css`
|
||||
${partitionVisWrapperStyle};
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -27,4 +27,5 @@ export const pieChartContainerStyleFactory = (theme: EuiThemeComputed) => css`
|
|||
padding: ${theme.size.s};
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
overflow: hidden;
|
||||
`;
|
|
@ -15,8 +15,15 @@ import type { Datatable } from '../../../../expressions/public';
|
|||
import { shallow, mount } from 'enzyme';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import PieComponent, { PieComponentProps } from './pie_vis_component';
|
||||
import { createMockPieParams, createMockVisData } from '../mocks';
|
||||
import PartitionVisComponent, { PartitionVisComponentProps } from './partition_vis_component';
|
||||
import {
|
||||
createMockDonutParams,
|
||||
createMockPieParams,
|
||||
createMockTreemapMosaicParams,
|
||||
createMockVisData,
|
||||
createMockWaffleParams,
|
||||
} from '../mocks';
|
||||
import { ChartTypes } from '../../common/types';
|
||||
|
||||
jest.mock('@elastic/charts', () => {
|
||||
const original = jest.requireActual('@elastic/charts');
|
||||
|
@ -42,8 +49,8 @@ const uiState = {
|
|||
setSilent: jest.fn(),
|
||||
} as any;
|
||||
|
||||
describe('PieComponent', function () {
|
||||
let wrapperProps: PieComponentProps;
|
||||
describe('PartitionVisComponent', function () {
|
||||
let wrapperProps: PartitionVisComponentProps;
|
||||
|
||||
beforeAll(() => {
|
||||
wrapperProps = {
|
||||
|
@ -51,6 +58,7 @@ describe('PieComponent', function () {
|
|||
palettesRegistry,
|
||||
visParams,
|
||||
visData,
|
||||
visType: ChartTypes.PIE,
|
||||
uiState,
|
||||
syncColors: false,
|
||||
fireEvent: jest.fn(),
|
||||
|
@ -62,20 +70,81 @@ describe('PieComponent', function () {
|
|||
};
|
||||
});
|
||||
|
||||
it('should render correct structure for pie', function () {
|
||||
const component = shallow(<PartitionVisComponent {...wrapperProps} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render correct structure for donut', function () {
|
||||
const donutVisParams = createMockDonutParams();
|
||||
const component = shallow(
|
||||
<PartitionVisComponent
|
||||
{...{
|
||||
...wrapperProps,
|
||||
visType: ChartTypes.DONUT,
|
||||
visParams: donutVisParams,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render correct structure for treemap', function () {
|
||||
const treemapVisParams = createMockTreemapMosaicParams();
|
||||
const component = shallow(
|
||||
<PartitionVisComponent
|
||||
{...{
|
||||
...wrapperProps,
|
||||
visType: ChartTypes.TREEMAP,
|
||||
visParams: treemapVisParams,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render correct structure for mosaic', function () {
|
||||
const mosaicVisParams = createMockTreemapMosaicParams();
|
||||
const component = shallow(
|
||||
<PartitionVisComponent
|
||||
{...{
|
||||
...wrapperProps,
|
||||
visType: ChartTypes.MOSAIC,
|
||||
visParams: mosaicVisParams,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render correct structure for waffle', function () {
|
||||
const waffleVisParams = createMockWaffleParams();
|
||||
const component = shallow(
|
||||
<PartitionVisComponent
|
||||
{...{
|
||||
...wrapperProps,
|
||||
visType: ChartTypes.MOSAIC,
|
||||
visParams: waffleVisParams,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the legend on the correct position', () => {
|
||||
const component = shallow(<PieComponent {...wrapperProps} />);
|
||||
const component = shallow(<PartitionVisComponent {...wrapperProps} />);
|
||||
expect(component.find(Settings).prop('legendPosition')).toEqual('right');
|
||||
});
|
||||
|
||||
it('renders the legend toggle component', async () => {
|
||||
const component = mount(<PieComponent {...wrapperProps} />);
|
||||
const component = mount(<PartitionVisComponent {...wrapperProps} />);
|
||||
await act(async () => {
|
||||
expect(findTestSubject(component, 'vislibToggleLegend').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('hides the legend if the legend toggle is clicked', async () => {
|
||||
const component = mount(<PieComponent {...wrapperProps} />);
|
||||
const component = mount(<PartitionVisComponent {...wrapperProps} />);
|
||||
findTestSubject(component, 'vislibToggleLegend').simulate('click');
|
||||
await act(async () => {
|
||||
expect(component.find(Settings).prop('showLegend')).toEqual(false);
|
||||
|
@ -83,31 +152,31 @@ describe('PieComponent', function () {
|
|||
});
|
||||
|
||||
it('defaults on showing the legend for the inner cicle', () => {
|
||||
const component = shallow(<PieComponent {...wrapperProps} />);
|
||||
const component = shallow(<PartitionVisComponent {...wrapperProps} />);
|
||||
expect(component.find(Settings).prop('legendMaxDepth')).toBe(1);
|
||||
});
|
||||
|
||||
it('shows the nested legend when the user requests it', () => {
|
||||
const newParams = { ...visParams, nestedLegend: true };
|
||||
const newProps = { ...wrapperProps, visParams: newParams };
|
||||
const component = shallow(<PieComponent {...newProps} />);
|
||||
const component = shallow(<PartitionVisComponent {...newProps} />);
|
||||
expect(component.find(Settings).prop('legendMaxDepth')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('defaults on displaying the tooltip', () => {
|
||||
const component = shallow(<PieComponent {...wrapperProps} />);
|
||||
const component = shallow(<PartitionVisComponent {...wrapperProps} />);
|
||||
expect(component.find(Settings).prop('tooltip')).toStrictEqual({ type: TooltipType.Follow });
|
||||
});
|
||||
|
||||
it('doesnt show the tooltip when the user requests it', () => {
|
||||
const newParams = { ...visParams, addTooltip: false };
|
||||
const newProps = { ...wrapperProps, visParams: newParams };
|
||||
const component = shallow(<PieComponent {...newProps} />);
|
||||
const component = shallow(<PartitionVisComponent {...newProps} />);
|
||||
expect(component.find(Settings).prop('tooltip')).toStrictEqual({ type: TooltipType.None });
|
||||
});
|
||||
|
||||
it('calls filter callback', () => {
|
||||
const component = shallow(<PieComponent {...wrapperProps} />);
|
||||
const component = shallow(<PartitionVisComponent {...wrapperProps} />);
|
||||
component.find(Settings).first().prop('onElementClick')!([
|
||||
[
|
||||
[
|
||||
|
@ -130,14 +199,14 @@ describe('PieComponent', function () {
|
|||
const newVisData = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: 'col-1-1',
|
||||
name: 'Count',
|
||||
},
|
||||
{
|
||||
id: 'col-0-2',
|
||||
name: 'filters',
|
||||
},
|
||||
{
|
||||
id: 'col-1-1',
|
||||
name: 'Count',
|
||||
},
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
|
@ -151,7 +220,7 @@ describe('PieComponent', function () {
|
|||
],
|
||||
} as unknown as Datatable;
|
||||
const newProps = { ...wrapperProps, visData: newVisData };
|
||||
const component = mount(<PieComponent {...newProps} />);
|
||||
const component = mount(<PartitionVisComponent {...newProps} />);
|
||||
expect(findTestSubject(component, 'pieVisualizationError').text()).toEqual('No results found');
|
||||
});
|
||||
|
||||
|
@ -159,14 +228,14 @@ describe('PieComponent', function () {
|
|||
const newVisData = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{
|
||||
id: 'col-1-1',
|
||||
name: 'Count',
|
||||
},
|
||||
{
|
||||
id: 'col-0-2',
|
||||
name: 'filters',
|
||||
},
|
||||
{
|
||||
id: 'col-1-1',
|
||||
name: 'Count',
|
||||
},
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
|
@ -180,7 +249,7 @@ describe('PieComponent', function () {
|
|||
],
|
||||
} as unknown as Datatable;
|
||||
const newProps = { ...wrapperProps, visData: newVisData };
|
||||
const component = mount(<PieComponent {...newProps} />);
|
||||
const component = mount(<PartitionVisComponent {...newProps} />);
|
||||
expect(findTestSubject(component, 'pieVisualizationError').text()).toEqual(
|
||||
"Pie/donut charts can't render with negative values."
|
||||
);
|
|
@ -18,7 +18,6 @@ import {
|
|||
TooltipProps,
|
||||
TooltipType,
|
||||
SeriesIdentifier,
|
||||
PartitionLayout,
|
||||
} from '@elastic/charts';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
import {
|
||||
|
@ -36,7 +35,7 @@ import {
|
|||
import type { FieldFormat } from '../../../../field_formats/common';
|
||||
import { DEFAULT_PERCENT_DECIMALS } from '../../common/constants';
|
||||
import {
|
||||
PieVisParams,
|
||||
PartitionVisParams,
|
||||
BucketColumns,
|
||||
ValueFormats,
|
||||
PieContainerDimensions,
|
||||
|
@ -53,11 +52,21 @@ import {
|
|||
getColumns,
|
||||
getSplitDimensionAccessor,
|
||||
getColumnByAccessor,
|
||||
isLegendFlat,
|
||||
shouldShowLegend,
|
||||
generateFormatters,
|
||||
getFormatter,
|
||||
getPartitionType,
|
||||
} from '../utils';
|
||||
import { ChartSplit, SMALL_MULTIPLES_ID } from './chart_split';
|
||||
import { VisualizationNoResults } from './visualization_noresults';
|
||||
import { VisTypePiePluginStartDependencies } from '../plugin';
|
||||
import { pieChartWrapperStyle, pieChartContainerStyleFactory } from './pie_vis_component.styles';
|
||||
import {
|
||||
partitionVisWrapperStyle,
|
||||
partitionVisContainerStyleFactory,
|
||||
} from './partition_vis_component.styles';
|
||||
import { ChartTypes } from '../../common/types';
|
||||
import { filterOutConfig } from '../utils/filter_out_config';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -67,9 +76,10 @@ declare global {
|
|||
_echDebugStateFlag?: boolean;
|
||||
}
|
||||
}
|
||||
export interface PieComponentProps {
|
||||
visParams: PieVisParams;
|
||||
export interface PartitionVisComponentProps {
|
||||
visParams: PartitionVisParams;
|
||||
visData: Datatable;
|
||||
visType: ChartTypes;
|
||||
uiState: PersistedState;
|
||||
fireEvent: IInterpreterRenderHandlers['event'];
|
||||
renderComplete: IInterpreterRenderHandlers['done'];
|
||||
|
@ -79,15 +89,31 @@ export interface PieComponentProps {
|
|||
syncColors: boolean;
|
||||
}
|
||||
|
||||
const PieComponent = (props: PieComponentProps) => {
|
||||
const PartitionVisComponent = (props: PartitionVisComponentProps) => {
|
||||
const { visData, visParams: preVisParams, visType, services, syncColors } = props;
|
||||
const visParams = useMemo(() => filterOutConfig(visType, preVisParams), [preVisParams, visType]);
|
||||
|
||||
const theme = useEuiTheme();
|
||||
const chartTheme = props.chartsThemeService.useChartsTheme();
|
||||
const chartBaseTheme = props.chartsThemeService.useChartsBaseTheme();
|
||||
const [showLegend, setShowLegend] = useState<boolean>(() => {
|
||||
const bwcLegendStateDefault =
|
||||
props.visParams.addLegend == null ? false : props.visParams.addLegend;
|
||||
return props.uiState?.get('vis.legendOpen', bwcLegendStateDefault) ?? bwcLegendStateDefault;
|
||||
});
|
||||
|
||||
const { bucketColumns, metricColumn } = useMemo(
|
||||
() => getColumns(props.visParams, props.visData),
|
||||
[props.visData, props.visParams]
|
||||
);
|
||||
|
||||
const formatters = useMemo(
|
||||
() => generateFormatters(visParams, visData, services.fieldFormats.deserialize),
|
||||
[services.fieldFormats.deserialize, visData, visParams]
|
||||
);
|
||||
|
||||
const showLegendDefault = useCallback(() => {
|
||||
const showLegendDef = shouldShowLegend(visType, visParams.legendDisplay, bucketColumns);
|
||||
return props.uiState?.get('vis.legendOpen', showLegendDef) ?? showLegendDef;
|
||||
}, [bucketColumns, props.uiState, visParams.legendDisplay, visType]);
|
||||
|
||||
const [showLegend, setShowLegend] = useState<boolean>(() => showLegendDefault());
|
||||
|
||||
const [dimensions, setDimensions] = useState<undefined | PieContainerDimensions>();
|
||||
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -100,6 +126,12 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
}
|
||||
}, [parentRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const legendShow = showLegendDefault();
|
||||
setShowLegend(legendShow);
|
||||
props.uiState?.set('vis.legendOpen', legendShow);
|
||||
}, [showLegendDefault, props.uiState]);
|
||||
|
||||
const onRenderChange = useCallback<RenderChangeListener>(
|
||||
(isRendered) => {
|
||||
if (isRendered) {
|
||||
|
@ -113,15 +145,15 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
const handleSliceClick = useCallback(
|
||||
(
|
||||
clickedLayers: LayerValue[],
|
||||
bucketColumns: Array<Partial<BucketColumns>>,
|
||||
visData: Datatable,
|
||||
buckets: Array<Partial<BucketColumns>>,
|
||||
vData: Datatable,
|
||||
splitChartDimension?: DatatableColumn,
|
||||
splitChartFormatter?: FieldFormat
|
||||
): void => {
|
||||
const data = getFilterClickData(
|
||||
clickedLayers,
|
||||
bucketColumns,
|
||||
visData,
|
||||
buckets,
|
||||
vData,
|
||||
splitChartDimension,
|
||||
splitChartFormatter
|
||||
);
|
||||
|
@ -136,9 +168,9 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
|
||||
// handles legend action event data
|
||||
const getLegendActionEventData = useCallback(
|
||||
(visData: Datatable) =>
|
||||
(vData: Datatable) =>
|
||||
(series: SeriesIdentifier): ClickTriggerEvent | null => {
|
||||
const data = getFilterEventData(visData, series);
|
||||
const data = getFilterEventData(vData, series);
|
||||
|
||||
return {
|
||||
name: 'filterBucket',
|
||||
|
@ -172,11 +204,6 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
});
|
||||
}, [props.uiState]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowLegend(props.visParams.addLegend);
|
||||
props.uiState?.set('vis.legendOpen', props.visParams.addLegend);
|
||||
}, [props.uiState, props.visParams.addLegend]);
|
||||
|
||||
const setColor = useCallback(
|
||||
(newColor: string | null, seriesLabel: string | number) => {
|
||||
const colors = props.uiState?.get('vis.colors') || {};
|
||||
|
@ -192,22 +219,22 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
[props.uiState]
|
||||
);
|
||||
|
||||
const { visData, visParams, services, syncColors } = props;
|
||||
|
||||
function getSliceValue(d: Datum, metricColumn: DatatableColumn) {
|
||||
const value = d[metricColumn.id];
|
||||
const getSliceValue = useCallback((d: Datum, metric: DatatableColumn) => {
|
||||
const value = d[metric.id];
|
||||
return Number.isFinite(value) && value >= 0 ? value : 0;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const defaultFormatter = services.fieldFormats.deserialize;
|
||||
// formatters
|
||||
const metricFieldFormatter = services.fieldFormats.deserialize(
|
||||
visParams.dimensions.metric.format
|
||||
);
|
||||
const splitChartFormatter = visParams.dimensions.splitColumn
|
||||
? services.fieldFormats.deserialize(visParams.dimensions.splitColumn[0].format)
|
||||
: visParams.dimensions.splitRow
|
||||
? services.fieldFormats.deserialize(visParams.dimensions.splitRow[0].format)
|
||||
const metricFieldFormatter = getFormatter(metricColumn, formatters, defaultFormatter);
|
||||
const { splitColumn, splitRow } = visParams.dimensions;
|
||||
|
||||
const splitChartFormatter = splitColumn
|
||||
? getFormatter(splitColumn[0], formatters, defaultFormatter)
|
||||
: splitRow
|
||||
? getFormatter(splitRow[0], formatters, defaultFormatter)
|
||||
: undefined;
|
||||
|
||||
const percentFormatter = services.fieldFormats.deserialize({
|
||||
id: 'percent',
|
||||
params: {
|
||||
|
@ -215,70 +242,71 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
},
|
||||
});
|
||||
|
||||
const { bucketColumns, metricColumn } = useMemo(
|
||||
() => getColumns(visParams, visData),
|
||||
[visData, visParams]
|
||||
);
|
||||
|
||||
const isDarkMode = props.chartsThemeService.useDarkMode();
|
||||
const layers = useMemo(
|
||||
() =>
|
||||
getLayers(
|
||||
visType,
|
||||
bucketColumns,
|
||||
visParams,
|
||||
visData,
|
||||
props.uiState?.get('vis.colors', {}),
|
||||
visData.rows,
|
||||
props.palettesRegistry,
|
||||
formatters,
|
||||
services.fieldFormats,
|
||||
syncColors
|
||||
syncColors,
|
||||
isDarkMode
|
||||
),
|
||||
[
|
||||
visType,
|
||||
bucketColumns,
|
||||
visParams,
|
||||
visData,
|
||||
props.uiState,
|
||||
props.palettesRegistry,
|
||||
visData.rows,
|
||||
formatters,
|
||||
services.fieldFormats,
|
||||
syncColors,
|
||||
isDarkMode,
|
||||
]
|
||||
);
|
||||
|
||||
const rescaleFactor = useMemo(() => {
|
||||
const overallSum = visData.rows.reduce((sum, row) => sum + row[metricColumn.id], 0);
|
||||
const slices = visData.rows.map((row) => row[metricColumn.id] / overallSum);
|
||||
const smallSlices = slices.filter((value) => value < 0.02).length;
|
||||
if (smallSlices) {
|
||||
const smallSlices = slices.filter((value) => value < 0.02) ?? [];
|
||||
if (smallSlices.length) {
|
||||
// shrink up to 20% to give some room for the linked values
|
||||
return 1 / (1 + Math.min(smallSlices * 0.05, 0.2));
|
||||
return 1 / (1 + Math.min(smallSlices.length * 0.05, 0.2));
|
||||
}
|
||||
return 1;
|
||||
}, [visData.rows, metricColumn]);
|
||||
|
||||
const themeOverrides = useMemo(
|
||||
() => getPartitionTheme(visParams, chartTheme, dimensions, rescaleFactor),
|
||||
[chartTheme, visParams, dimensions, rescaleFactor]
|
||||
() => getPartitionTheme(visType, visParams, chartTheme, dimensions, rescaleFactor),
|
||||
[visType, visParams, chartTheme, dimensions, rescaleFactor]
|
||||
);
|
||||
|
||||
const fixedViewPort = document.getElementById('app-fixed-viewport');
|
||||
const tooltip: TooltipProps = {
|
||||
...(fixedViewPort ? { boundary: fixedViewPort } : {}),
|
||||
type: visParams.addTooltip ? TooltipType.Follow : TooltipType.None,
|
||||
};
|
||||
const legendPosition = visParams.legendPosition ?? Position.Right;
|
||||
|
||||
const splitChartColumnAccessor = visParams.dimensions.splitColumn
|
||||
? getSplitDimensionAccessor(
|
||||
services.fieldFormats,
|
||||
visData.columns
|
||||
)(visParams.dimensions.splitColumn[0])
|
||||
: undefined;
|
||||
const splitChartRowAccessor = visParams.dimensions.splitRow
|
||||
? getSplitDimensionAccessor(
|
||||
services.fieldFormats,
|
||||
visData.columns
|
||||
)(visParams.dimensions.splitRow[0])
|
||||
const splitChartColumnAccessor = splitColumn
|
||||
? getSplitDimensionAccessor(visData.columns, splitColumn[0], formatters, defaultFormatter)
|
||||
: undefined;
|
||||
|
||||
const splitChartDimension = visParams.dimensions.splitColumn
|
||||
? getColumnByAccessor(visParams.dimensions.splitColumn[0].accessor, visData.columns)
|
||||
: visParams.dimensions.splitRow
|
||||
? getColumnByAccessor(visParams.dimensions.splitRow[0].accessor, visData.columns)
|
||||
const splitChartRowAccessor = splitRow
|
||||
? getSplitDimensionAccessor(visData.columns, splitRow[0], formatters, defaultFormatter)
|
||||
: undefined;
|
||||
|
||||
const splitChartDimension = splitColumn
|
||||
? getColumnByAccessor(splitColumn[0].accessor, visData.columns)
|
||||
: splitRow
|
||||
? getColumnByAccessor(splitRow[0].accessor, visData.columns)
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
|
@ -302,15 +330,16 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
}),
|
||||
[visData.rows, metricColumn]
|
||||
);
|
||||
|
||||
const flatLegend = isLegendFlat(visType, splitChartDimension);
|
||||
const canShowPieChart = !isAllZeros && !hasNegative;
|
||||
const partitionType = getPartitionType(visType);
|
||||
|
||||
return (
|
||||
<div css={pieChartContainerStyleFactory(theme.euiTheme)} data-test-subj="visTypePieChart">
|
||||
<div css={partitionVisContainerStyleFactory(theme.euiTheme)} data-test-subj="visTypePieChart">
|
||||
{!canShowPieChart ? (
|
||||
<VisualizationNoResults hasNegativeValues={hasNegative} />
|
||||
) : (
|
||||
<div css={pieChartWrapperStyle} ref={parentRef}>
|
||||
<div css={partitionVisWrapperStyle} ref={parentRef}>
|
||||
<LegendColorPickerWrapperContext.Provider
|
||||
value={{
|
||||
legendPosition,
|
||||
|
@ -319,7 +348,7 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
palette: visParams.palette.name,
|
||||
data: visData.rows,
|
||||
uiState: props.uiState,
|
||||
distinctColors: visParams.distinctColors,
|
||||
distinctColors: visParams.distinctColors ?? false,
|
||||
}}
|
||||
>
|
||||
<LegendToggle
|
||||
|
@ -338,8 +367,9 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
legendPosition={legendPosition}
|
||||
legendMaxDepth={visParams.nestedLegend ? undefined : 1}
|
||||
legendColorPicker={props.uiState ? LegendColorPickerWrapper : undefined}
|
||||
flatLegend={Boolean(splitChartDimension)}
|
||||
flatLegend={flatLegend}
|
||||
tooltip={tooltip}
|
||||
showLegendExtra={visParams.showValuesInLegend}
|
||||
onElementClick={(args) => {
|
||||
handleSliceClick(
|
||||
args[0][0] as LayerValue[],
|
||||
|
@ -374,11 +404,11 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
onRenderChange={onRenderChange}
|
||||
/>
|
||||
<Partition
|
||||
id="pie"
|
||||
id={visType}
|
||||
smallMultiples={SMALL_MULTIPLES_ID}
|
||||
data={visData.rows}
|
||||
layout={PartitionLayout.sunburst}
|
||||
specialFirstInnermostSector={false}
|
||||
layout={partitionType}
|
||||
specialFirstInnermostSector={visParams.startFromSecondLargestSlice}
|
||||
valueAccessor={(d: Datum) => getSliceValue(d, metricColumn)}
|
||||
percentFormatter={(d: number) => percentFormatter.convert(d / 100)}
|
||||
valueGetter={
|
||||
|
@ -386,7 +416,7 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
visParams.labels.valuesFormat === ValueFormats.VALUE ||
|
||||
!visParams.labels.values
|
||||
? undefined
|
||||
: 'percent'
|
||||
: ValueFormats.PERCENT
|
||||
}
|
||||
valueFormatter={(d: number) =>
|
||||
!visParams.labels.show || !visParams.labels.values
|
||||
|
@ -405,4 +435,4 @@ const PieComponent = (props: PieComponentProps) => {
|
|||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default memo(PieComponent);
|
||||
export default memo(PartitionVisComponent);
|
|
@ -19,10 +19,10 @@ export const VisualizationNoResults = ({ hasNegativeValues = false }) => {
|
|||
body={
|
||||
<EuiText size="xs">
|
||||
{hasNegativeValues
|
||||
? i18n.translate('expressionPie.negativeValuesFound', {
|
||||
? i18n.translate('expressionPartitionVis.negativeValuesFound', {
|
||||
defaultMessage: "Pie/donut charts can't render with negative values.",
|
||||
})
|
||||
: i18n.translate('expressionPie.noResultsFoundTitle', {
|
||||
: i18n.translate('expressionPartitionVis.noResultsFoundTitle', {
|
||||
defaultMessage: 'No results found',
|
||||
})}
|
||||
</EuiText>
|
|
@ -6,5 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { pieVisFunction } from './pie_vis_function';
|
||||
export { pieLabelsFunction } from './pie_labels_function';
|
||||
export { getPartitionVisRenderer } from './partition_vis_renderer';
|
|
@ -15,23 +15,23 @@ import { VisualizationContainer } from '../../../../visualizations/public';
|
|||
import type { PersistedState } from '../../../../visualizations/public';
|
||||
import { KibanaThemeProvider } from '../../../../kibana_react/public';
|
||||
|
||||
import { PIE_VIS_EXPRESSION_NAME } from '../../common/constants';
|
||||
import { RenderValue } from '../../common/types';
|
||||
import { PARTITION_VIS_RENDERER_NAME } from '../../common/constants';
|
||||
import { ChartTypes, RenderValue } from '../../common/types';
|
||||
|
||||
import { VisTypePieDependencies } from '../plugin';
|
||||
|
||||
export const strings = {
|
||||
getDisplayName: () =>
|
||||
i18n.translate('expressionPie.renderer.pieVis.displayName', {
|
||||
i18n.translate('expressionPartitionVis.renderer.pieVis.displayName', {
|
||||
defaultMessage: 'Pie visualization',
|
||||
}),
|
||||
getHelpDescription: () =>
|
||||
i18n.translate('expressionPie.renderer.pieVis.helpDescription', {
|
||||
i18n.translate('expressionPartitionVis.renderer.pieVis.helpDescription', {
|
||||
defaultMessage: 'Render a pie',
|
||||
}),
|
||||
};
|
||||
|
||||
const PieComponent = lazy(() => import('../components/pie_vis_component'));
|
||||
const PartitionVisComponent = lazy(() => import('../components/partition_vis_component'));
|
||||
|
||||
function shouldShowNoResultsMessage(visData: Datatable | undefined): boolean {
|
||||
const rows: object[] | undefined = visData?.rows;
|
||||
|
@ -40,14 +40,14 @@ function shouldShowNoResultsMessage(visData: Datatable | undefined): boolean {
|
|||
return Boolean(isZeroHits);
|
||||
}
|
||||
|
||||
export const getPieVisRenderer: (
|
||||
export const getPartitionVisRenderer: (
|
||||
deps: VisTypePieDependencies
|
||||
) => ExpressionRenderDefinition<RenderValue> = ({ theme, palettes, getStartDeps }) => ({
|
||||
name: PIE_VIS_EXPRESSION_NAME,
|
||||
name: PARTITION_VIS_RENDERER_NAME,
|
||||
displayName: strings.getDisplayName(),
|
||||
help: strings.getHelpDescription(),
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, { visConfig, visData, syncColors }, handlers) => {
|
||||
render: async (domNode, { visConfig, visData, visType, syncColors }, handlers) => {
|
||||
const showNoResult = shouldShowNoResultsMessage(visData);
|
||||
|
||||
handlers.onDestroy(() => {
|
||||
|
@ -61,11 +61,12 @@ export const getPieVisRenderer: (
|
|||
<I18nProvider>
|
||||
<KibanaThemeProvider theme$={services.kibanaTheme.theme$}>
|
||||
<VisualizationContainer handlers={handlers} showNoResult={showNoResult}>
|
||||
<PieComponent
|
||||
<PartitionVisComponent
|
||||
chartsThemeService={theme}
|
||||
palettesRegistry={palettesRegistry}
|
||||
visParams={visConfig}
|
||||
visData={visData}
|
||||
visType={visConfig.isDonut ? ChartTypes.DONUT : visType}
|
||||
renderComplete={handlers.done}
|
||||
fireEvent={handlers.event}
|
||||
uiState={handlers.uiState as PersistedState}
|
|
@ -6,10 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExpressionPiePlugin } from './plugin';
|
||||
import { ExpressionPartitionVisPlugin } from './plugin';
|
||||
|
||||
export function plugin() {
|
||||
return new ExpressionPiePlugin();
|
||||
return new ExpressionPartitionVisPlugin();
|
||||
}
|
||||
|
||||
export type { ExpressionPiePluginSetup, ExpressionPiePluginStart } from './types';
|
||||
export type { ExpressionPartitionVisPluginSetup, ExpressionPartitionVisPluginStart } from './types';
|
|
@ -9,10 +9,11 @@
|
|||
import { Datatable } from '../../../expressions/public';
|
||||
import {
|
||||
BucketColumns,
|
||||
PieVisParams,
|
||||
PartitionVisParams,
|
||||
LabelPositions,
|
||||
ValueFormats,
|
||||
} from '../common/types/expression_renderers';
|
||||
LegendDisplay,
|
||||
} from '../common/types';
|
||||
|
||||
export const createMockBucketColumns = (): BucketColumns[] => {
|
||||
return [
|
||||
|
@ -107,50 +108,50 @@ export const createMockVisData = (): Datatable => {
|
|||
rows: [
|
||||
{
|
||||
'col-0-2': 'Logstash Airways',
|
||||
'col-2-3': 0,
|
||||
'col-1-1': 797,
|
||||
'col-2-3': 0,
|
||||
'col-3-1': 689,
|
||||
},
|
||||
{
|
||||
'col-0-2': 'Logstash Airways',
|
||||
'col-2-3': 1,
|
||||
'col-1-1': 797,
|
||||
'col-2-3': 1,
|
||||
'col-3-1': 108,
|
||||
},
|
||||
{
|
||||
'col-0-2': 'JetBeats',
|
||||
'col-2-3': 0,
|
||||
'col-1-1': 766,
|
||||
'col-2-3': 0,
|
||||
'col-3-1': 654,
|
||||
},
|
||||
{
|
||||
'col-0-2': 'JetBeats',
|
||||
'col-2-3': 1,
|
||||
'col-1-1': 766,
|
||||
'col-2-3': 1,
|
||||
'col-3-1': 112,
|
||||
},
|
||||
{
|
||||
'col-0-2': 'ES-Air',
|
||||
'col-2-3': 0,
|
||||
'col-1-1': 744,
|
||||
'col-2-3': 0,
|
||||
'col-3-1': 665,
|
||||
},
|
||||
{
|
||||
'col-0-2': 'ES-Air',
|
||||
'col-2-3': 1,
|
||||
'col-1-1': 744,
|
||||
'col-2-3': 1,
|
||||
'col-3-1': 79,
|
||||
},
|
||||
{
|
||||
'col-0-2': 'Kibana Airlines',
|
||||
'col-2-3': 0,
|
||||
'col-1-1': 731,
|
||||
'col-2-3': 0,
|
||||
'col-3-1': 655,
|
||||
},
|
||||
{
|
||||
'col-0-2': 'Kibana Airlines',
|
||||
'col-2-3': 1,
|
||||
'col-1-1': 731,
|
||||
'col-2-3': 1,
|
||||
'col-3-1': 76,
|
||||
},
|
||||
],
|
||||
|
@ -269,9 +270,9 @@ export const createMockVisData = (): Datatable => {
|
|||
};
|
||||
};
|
||||
|
||||
export const createMockPieParams = (): PieVisParams => {
|
||||
export const createMockPartitionVisParams = (): PartitionVisParams => {
|
||||
return {
|
||||
addLegend: true,
|
||||
legendDisplay: LegendDisplay.SHOW,
|
||||
addTooltip: true,
|
||||
isDonut: true,
|
||||
labels: {
|
||||
|
@ -291,19 +292,20 @@ export const createMockPieParams = (): PieVisParams => {
|
|||
name: 'default',
|
||||
type: 'palette',
|
||||
},
|
||||
type: 'pie',
|
||||
dimensions: {
|
||||
metric: {
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {
|
||||
id: 'number',
|
||||
},
|
||||
},
|
||||
params: {},
|
||||
label: 'Count',
|
||||
aggType: 'count',
|
||||
},
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 0,
|
||||
format: {
|
||||
id: 'terms',
|
||||
|
@ -313,10 +315,9 @@ export const createMockPieParams = (): PieVisParams => {
|
|||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
label: 'Carrier: Descending',
|
||||
aggType: 'terms',
|
||||
},
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 2,
|
||||
format: {
|
||||
id: 'terms',
|
||||
|
@ -326,10 +327,56 @@ export const createMockPieParams = (): PieVisParams => {
|
|||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
label: 'Cancelled: Descending',
|
||||
aggType: 'terms',
|
||||
},
|
||||
],
|
||||
},
|
||||
} as unknown as PieVisParams;
|
||||
};
|
||||
};
|
||||
|
||||
export const createMockPieParams = (): PartitionVisParams => {
|
||||
return {
|
||||
...createMockPartitionVisParams(),
|
||||
isDonut: false,
|
||||
distinctColors: false,
|
||||
};
|
||||
};
|
||||
|
||||
export const createMockDonutParams = (): PartitionVisParams => {
|
||||
return {
|
||||
...createMockPartitionVisParams(),
|
||||
isDonut: true,
|
||||
emptySizeRatio: 0.3,
|
||||
};
|
||||
};
|
||||
|
||||
export const createMockTreemapMosaicParams = (): PartitionVisParams => {
|
||||
return {
|
||||
...createMockPartitionVisParams(),
|
||||
nestedLegend: true,
|
||||
};
|
||||
};
|
||||
|
||||
export const createMockWaffleParams = (): PartitionVisParams => {
|
||||
const visParams = createMockPartitionVisParams();
|
||||
return {
|
||||
...visParams,
|
||||
dimensions: {
|
||||
...visParams.dimensions,
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 0,
|
||||
format: {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'string',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
showValuesInLegend: true,
|
||||
};
|
||||
};
|
|
@ -10,9 +10,20 @@ import { FieldFormatsStart } from '../../../field_formats/public';
|
|||
import { CoreSetup, CoreStart, ThemeServiceStart } from '../../../../core/public';
|
||||
import { ChartsPluginSetup } from '../../../charts/public';
|
||||
import { DataPublicPluginStart } from '../../../data/public';
|
||||
import { pieLabelsFunction, pieVisFunction } from '../common';
|
||||
import { getPieVisRenderer } from './expression_renderers';
|
||||
import { ExpressionPiePluginSetup, ExpressionPiePluginStart, SetupDeps, StartDeps } from './types';
|
||||
import {
|
||||
partitionLabelsFunction,
|
||||
pieVisFunction,
|
||||
treemapVisFunction,
|
||||
mosaicVisFunction,
|
||||
waffleVisFunction,
|
||||
} from '../common';
|
||||
import { getPartitionVisRenderer } from './expression_renderers';
|
||||
import {
|
||||
ExpressionPartitionVisPluginSetup,
|
||||
ExpressionPartitionVisPluginStart,
|
||||
SetupDeps,
|
||||
StartDeps,
|
||||
} from './types';
|
||||
|
||||
/** @internal */
|
||||
export interface VisTypePieDependencies {
|
||||
|
@ -30,13 +41,16 @@ export interface VisTypePiePluginStartDependencies {
|
|||
fieldFormats: FieldFormatsStart;
|
||||
}
|
||||
|
||||
export class ExpressionPiePlugin {
|
||||
export class ExpressionPartitionVisPlugin {
|
||||
public setup(
|
||||
core: CoreSetup<VisTypePiePluginStartDependencies>,
|
||||
{ expressions, charts }: SetupDeps
|
||||
): ExpressionPiePluginSetup {
|
||||
expressions.registerFunction(pieLabelsFunction);
|
||||
): ExpressionPartitionVisPluginSetup {
|
||||
expressions.registerFunction(partitionLabelsFunction);
|
||||
expressions.registerFunction(pieVisFunction);
|
||||
expressions.registerFunction(treemapVisFunction);
|
||||
expressions.registerFunction(mosaicVisFunction);
|
||||
expressions.registerFunction(waffleVisFunction);
|
||||
|
||||
const getStartDeps = async () => {
|
||||
const [coreStart, deps] = await core.getStartServices();
|
||||
|
@ -46,11 +60,11 @@ export class ExpressionPiePlugin {
|
|||
};
|
||||
|
||||
expressions.registerRenderer(
|
||||
getPieVisRenderer({ theme: charts.theme, palettes: charts.palettes, getStartDeps })
|
||||
getPartitionVisRenderer({ theme: charts.theme, palettes: charts.palettes, getStartDeps })
|
||||
);
|
||||
}
|
||||
|
||||
public start(core: CoreStart, deps: StartDeps): ExpressionPiePluginStart {}
|
||||
public start(core: CoreStart, deps: StartDeps): ExpressionPartitionVisPluginStart {}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -8,8 +8,8 @@
|
|||
import { ChartsPluginSetup } from '../../../charts/public';
|
||||
import { ExpressionsPublicPlugin, ExpressionsServiceStart } from '../../../expressions/public';
|
||||
|
||||
export type ExpressionPiePluginSetup = void;
|
||||
export type ExpressionPiePluginStart = void;
|
||||
export type ExpressionPartitionVisPluginSetup = void;
|
||||
export type ExpressionPartitionVisPluginStart = void;
|
||||
|
||||
export interface SetupDeps {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { createMockVisData } from '../mocks';
|
||||
import { getColumnByAccessor } from './accessor';
|
||||
|
||||
const visData = createMockVisData();
|
||||
|
||||
describe('getColumnByAccessor', () => {
|
||||
it('returns column by the index', () => {
|
||||
const index = 1;
|
||||
const column = getColumnByAccessor(index, visData.columns);
|
||||
expect(column).toEqual(visData.columns[index]);
|
||||
});
|
||||
|
||||
it('returns undefiend if the index is higher then amount of columns', () => {
|
||||
const index = visData.columns.length;
|
||||
const column = getColumnByAccessor(index, visData.columns);
|
||||
expect(column).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns column by id', () => {
|
||||
const column = visData.columns[1];
|
||||
const accessor: ExpressionValueVisDimension['accessor'] = {
|
||||
id: column.id,
|
||||
name: '',
|
||||
meta: { type: column.meta.type },
|
||||
};
|
||||
|
||||
const foundColumn = getColumnByAccessor(accessor, visData.columns);
|
||||
expect(foundColumn).toEqual(column);
|
||||
});
|
||||
|
||||
it('returns undefined for the accessor to non-existent column', () => {
|
||||
const accessor: ExpressionValueVisDimension['accessor'] = {
|
||||
id: 'non-existent-column',
|
||||
name: '',
|
||||
meta: { type: 'number' },
|
||||
};
|
||||
|
||||
const column = getColumnByAccessor(accessor, visData.columns);
|
||||
expect(column).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { ChartTypes } from '../../common/types';
|
||||
import { createMockDonutParams, createMockPieParams } from '../mocks';
|
||||
import { filterOutConfig } from './filter_out_config';
|
||||
|
||||
describe('filterOutConfig', () => {
|
||||
const config = createMockPieParams();
|
||||
const { last_level: lastLevel, truncate, ...restLabels } = config.labels;
|
||||
const configWithoutTruncateAndLastLevel = { ...config, labels: restLabels };
|
||||
|
||||
it('returns full configuration for pie visualization', () => {
|
||||
const fullConfig = filterOutConfig(ChartTypes.PIE, config);
|
||||
|
||||
expect(fullConfig).toEqual(config);
|
||||
});
|
||||
|
||||
it('returns full configuration for donut visualization', () => {
|
||||
const donutConfig = createMockDonutParams();
|
||||
const fullDonutConfig = filterOutConfig(ChartTypes.DONUT, donutConfig);
|
||||
|
||||
expect(fullDonutConfig).toEqual(donutConfig);
|
||||
});
|
||||
|
||||
it('excludes truncate and last_level from labels for treemap', () => {
|
||||
const filteredOutConfig = filterOutConfig(ChartTypes.TREEMAP, config);
|
||||
|
||||
expect(filteredOutConfig).toEqual(configWithoutTruncateAndLastLevel);
|
||||
});
|
||||
|
||||
it('excludes truncate and last_level from labels for mosaic', () => {
|
||||
const filteredOutConfig = filterOutConfig(ChartTypes.MOSAIC, config);
|
||||
|
||||
expect(filteredOutConfig).toEqual(configWithoutTruncateAndLastLevel);
|
||||
});
|
||||
|
||||
it('excludes truncate and last_level from labels for waffle', () => {
|
||||
const filteredOutConfig = filterOutConfig(ChartTypes.WAFFLE, config);
|
||||
|
||||
expect(filteredOutConfig).toEqual(configWithoutTruncateAndLastLevel);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PartitionVisParams, ChartTypes } from '../../common/types';
|
||||
|
||||
export const filterOutConfig = (visType: ChartTypes, visConfig: PartitionVisParams) => {
|
||||
if ([ChartTypes.PIE, ChartTypes.DONUT].includes(visType)) {
|
||||
return visConfig;
|
||||
}
|
||||
|
||||
const { last_level: lastLevel, truncate, ...restLabelsConfig } = visConfig.labels;
|
||||
|
||||
return {
|
||||
...visConfig,
|
||||
labels: restLabelsConfig,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { fieldFormatsMock } from '../../../../field_formats/common/mocks';
|
||||
import { Datatable } from '../../../../expressions';
|
||||
import { createMockPieParams, createMockVisData } from '../mocks';
|
||||
import { generateFormatters, getAvailableFormatter, getFormatter } from './formatters';
|
||||
import { BucketColumns } from '../../common/types';
|
||||
|
||||
describe('generateFormatters', () => {
|
||||
const visParams = createMockPieParams();
|
||||
const visData = createMockVisData();
|
||||
const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
beforeEach(() => {
|
||||
defaultFormatter.mockClear();
|
||||
});
|
||||
|
||||
it('returns empty object, if labels should not be should ', () => {
|
||||
const formatters = generateFormatters(
|
||||
{ ...visParams, labels: { ...visParams.labels, show: false } },
|
||||
visData,
|
||||
defaultFormatter
|
||||
);
|
||||
|
||||
expect(formatters).toEqual({});
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('returns formatters, if columns have meta parameters', () => {
|
||||
const formatters = generateFormatters(visParams, visData, defaultFormatter);
|
||||
const formattingResult = fieldFormatsMock.deserialize();
|
||||
|
||||
const serializedFormatters = Object.keys(formatters).reduce(
|
||||
(serialized, formatterId) => ({
|
||||
...serialized,
|
||||
[formatterId]: formatters[formatterId]?.toJSON(),
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
expect(serializedFormatters).toEqual({
|
||||
'col-0-2': formattingResult.toJSON(),
|
||||
'col-1-1': formattingResult.toJSON(),
|
||||
'col-2-3': formattingResult.toJSON(),
|
||||
'col-3-1': formattingResult.toJSON(),
|
||||
});
|
||||
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(visData.columns.length);
|
||||
visData.columns.forEach((col) => {
|
||||
expect(defaultFormatter).toHaveBeenCalledWith(col.meta.params);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns undefined formatters for columns without meta parameters', () => {
|
||||
const newVisData: Datatable = {
|
||||
...visData,
|
||||
columns: visData.columns.map(({ meta, ...col }) => ({ ...col, meta: { type: 'string' } })),
|
||||
};
|
||||
|
||||
const formatters = generateFormatters(visParams, newVisData, defaultFormatter);
|
||||
|
||||
expect(formatters).toEqual({
|
||||
'col-0-2': undefined,
|
||||
'col-1-1': undefined,
|
||||
'col-2-3': undefined,
|
||||
'col-3-1': undefined,
|
||||
});
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvailableFormatter', () => {
|
||||
const visData = createMockVisData();
|
||||
|
||||
const preparedFormatter1 = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
const preparedFormatter2 = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
|
||||
beforeEach(() => {
|
||||
defaultFormatter.mockClear();
|
||||
preparedFormatter1.mockClear();
|
||||
preparedFormatter2.mockClear();
|
||||
});
|
||||
|
||||
const formatters: Record<string, any> = {
|
||||
[visData.columns[0].id]: preparedFormatter1(),
|
||||
[visData.columns[1].id]: preparedFormatter2(),
|
||||
};
|
||||
|
||||
it('returns formatter from formatters, if meta.params are present ', () => {
|
||||
const formatter = getAvailableFormatter(visData.columns[1], formatters, defaultFormatter);
|
||||
|
||||
expect(formatter).toEqual(formatters[visData.columns[1].id]);
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('returns formatter from defaultFormatter factory, if meta.params are not present and format is present at column', () => {
|
||||
const column: Partial<BucketColumns> = {
|
||||
...visData.columns[1],
|
||||
meta: { type: 'string' },
|
||||
format: {
|
||||
id: 'string',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
const formatter = getAvailableFormatter(column, formatters, defaultFormatter);
|
||||
|
||||
expect(formatter).not.toBeNull();
|
||||
expect(typeof formatter).toBe('object');
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(1);
|
||||
expect(defaultFormatter).toHaveBeenCalledWith(column.format);
|
||||
});
|
||||
|
||||
it('returns undefined, if meta.params and format are not present', () => {
|
||||
const column: Partial<BucketColumns> = {
|
||||
...visData.columns[1],
|
||||
meta: { type: 'string' },
|
||||
};
|
||||
const formatter = getAvailableFormatter(column, formatters, defaultFormatter);
|
||||
|
||||
expect(formatter).toBeUndefined();
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFormatter', () => {
|
||||
const visData = createMockVisData();
|
||||
|
||||
const preparedFormatter1 = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
const preparedFormatter2 = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
|
||||
beforeEach(() => {
|
||||
defaultFormatter.mockClear();
|
||||
preparedFormatter1.mockClear();
|
||||
preparedFormatter2.mockClear();
|
||||
});
|
||||
|
||||
const formatters: Record<string, any> = {
|
||||
[visData.columns[0].id]: preparedFormatter1(),
|
||||
[visData.columns[1].id]: preparedFormatter2(),
|
||||
};
|
||||
|
||||
it('returns formatter from formatters, if meta.params are present ', () => {
|
||||
const formatter = getFormatter(visData.columns[1], formatters, defaultFormatter);
|
||||
|
||||
expect(formatter).toEqual(formatters[visData.columns[1].id]);
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('returns formatter from defaultFormatter factory, if meta.params are not present and format is present at column', () => {
|
||||
const column: Partial<BucketColumns> = {
|
||||
...visData.columns[1],
|
||||
meta: { type: 'string' },
|
||||
format: {
|
||||
id: 'string',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
const formatter = getFormatter(column, formatters, defaultFormatter);
|
||||
|
||||
expect(formatter).not.toBeNull();
|
||||
expect(typeof formatter).toBe('object');
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(1);
|
||||
expect(defaultFormatter).toHaveBeenCalledWith(column.format);
|
||||
});
|
||||
|
||||
it('returns defaultFormatter, if meta.params and format are not present', () => {
|
||||
const column: Partial<BucketColumns> = {
|
||||
...visData.columns[1],
|
||||
meta: { type: 'string' },
|
||||
};
|
||||
|
||||
const formatter = getFormatter(column, formatters, defaultFormatter);
|
||||
|
||||
expect(formatter).not.toBeNull();
|
||||
expect(typeof formatter).toBe('object');
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(1);
|
||||
expect(defaultFormatter).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { FieldFormat, FormatFactory } from '../../../../field_formats/common';
|
||||
import type { Datatable } from '../../../../expressions/public';
|
||||
import { BucketColumns, PartitionVisParams } from '../../common/types';
|
||||
|
||||
export const generateFormatters = (
|
||||
visParams: PartitionVisParams,
|
||||
visData: Datatable,
|
||||
formatFactory: FormatFactory
|
||||
) => {
|
||||
if (!visParams.labels.show) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return visData.columns.reduce<Record<string, ReturnType<FormatFactory> | undefined>>(
|
||||
(newFormatters, column) => ({
|
||||
...newFormatters,
|
||||
[column.id]: column?.meta?.params ? formatFactory(column.meta.params) : undefined,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
export const getAvailableFormatter = (
|
||||
column: Partial<BucketColumns>,
|
||||
formatters: Record<string, FieldFormat | undefined>,
|
||||
defaultFormatFactory: FormatFactory
|
||||
) => {
|
||||
if (column?.meta?.params) {
|
||||
const formatter = column?.id ? formatters[column?.id] : undefined;
|
||||
if (formatter) {
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
|
||||
if (column?.format) {
|
||||
return defaultFormatFactory(column.format);
|
||||
}
|
||||
};
|
||||
|
||||
export const getFormatter = (
|
||||
column: Partial<BucketColumns>,
|
||||
formatters: Record<string, FieldFormat | undefined>,
|
||||
defaultFormatFactory: FormatFactory
|
||||
) => getAvailableFormatter(column, formatters, defaultFormatFactory) ?? defaultFormatFactory();
|
|
@ -7,7 +7,12 @@
|
|||
*/
|
||||
|
||||
import { getColumns } from './get_columns';
|
||||
import { PieVisParams } from '../../common/types';
|
||||
import {
|
||||
LabelPositions,
|
||||
LegendDisplay,
|
||||
PartitionVisParams,
|
||||
ValueFormats,
|
||||
} from '../../common/types';
|
||||
import { createMockPieParams, createMockVisData } from '../mocks';
|
||||
|
||||
const visParams = createMockPieParams();
|
||||
|
@ -108,7 +113,16 @@ describe('getColumns', () => {
|
|||
});
|
||||
|
||||
it('should return the correct metric column if visParams returns dimensions', () => {
|
||||
const { metricColumn } = getColumns(visParams, visData);
|
||||
const { metricColumn } = getColumns(
|
||||
{
|
||||
...visParams,
|
||||
dimensions: {
|
||||
...visParams.dimensions,
|
||||
metric: undefined,
|
||||
},
|
||||
},
|
||||
visData
|
||||
);
|
||||
expect(metricColumn).toEqual({
|
||||
id: 'col-3-1',
|
||||
meta: {
|
||||
|
@ -130,39 +144,38 @@ describe('getColumns', () => {
|
|||
});
|
||||
|
||||
it('should return the first data column if no buckets specified', () => {
|
||||
const visParamsOnlyMetric = {
|
||||
addLegend: true,
|
||||
const visParamsOnlyMetric: PartitionVisParams = {
|
||||
legendDisplay: LegendDisplay.SHOW,
|
||||
addTooltip: true,
|
||||
isDonut: true,
|
||||
labels: {
|
||||
position: 'default',
|
||||
position: LabelPositions.DEFAULT,
|
||||
show: true,
|
||||
truncate: 100,
|
||||
values: true,
|
||||
valuesFormat: 'percent',
|
||||
valuesFormat: ValueFormats.PERCENT,
|
||||
percentDecimals: 2,
|
||||
last_level: false,
|
||||
},
|
||||
legendPosition: 'right',
|
||||
nestedLegend: false,
|
||||
maxLegendLines: 1,
|
||||
truncateLegend: false,
|
||||
distinctColors: false,
|
||||
palette: {
|
||||
name: 'default',
|
||||
type: 'palette',
|
||||
},
|
||||
type: 'pie',
|
||||
dimensions: {
|
||||
metric: {
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
params: {},
|
||||
label: 'Count',
|
||||
aggType: 'count',
|
||||
},
|
||||
},
|
||||
} as unknown as PieVisParams;
|
||||
};
|
||||
const { metricColumn } = getColumns(visParamsOnlyMetric, visData);
|
||||
expect(metricColumn).toEqual({
|
||||
id: 'col-1-1',
|
||||
|
@ -187,37 +200,39 @@ describe('getColumns', () => {
|
|||
});
|
||||
|
||||
it('should return an object with the name of the metric if no buckets specified', () => {
|
||||
const visParamsOnlyMetric = {
|
||||
addLegend: true,
|
||||
const visParamsOnlyMetric: PartitionVisParams = {
|
||||
legendDisplay: LegendDisplay.SHOW,
|
||||
addTooltip: true,
|
||||
isDonut: true,
|
||||
labels: {
|
||||
position: 'default',
|
||||
position: LabelPositions.DEFAULT,
|
||||
show: true,
|
||||
truncate: 100,
|
||||
values: true,
|
||||
valuesFormat: 'percent',
|
||||
valuesFormat: ValueFormats.PERCENT,
|
||||
percentDecimals: 2,
|
||||
last_level: false,
|
||||
},
|
||||
truncateLegend: false,
|
||||
maxLegendLines: 100,
|
||||
distinctColors: false,
|
||||
legendPosition: 'right',
|
||||
nestedLegend: false,
|
||||
palette: {
|
||||
name: 'default',
|
||||
type: 'palette',
|
||||
},
|
||||
type: 'pie',
|
||||
dimensions: {
|
||||
metric: {
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
params: {},
|
||||
label: 'Count',
|
||||
aggType: 'count',
|
||||
},
|
||||
},
|
||||
} as unknown as PieVisParams;
|
||||
};
|
||||
const { bucketColumns, metricColumn } = getColumns(visParamsOnlyMetric, visData);
|
||||
expect(bucketColumns).toEqual([{ name: metricColumn.name }]);
|
||||
});
|
|
@ -6,39 +6,43 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getColumnByAccessor } from './accessor';
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { DatatableColumn, Datatable } from '../../../../expressions/public';
|
||||
import { BucketColumns, PieVisParams } from '../../common/types';
|
||||
import { BucketColumns, PartitionVisParams } from '../../common/types';
|
||||
import { getColumnByAccessor } from './accessor';
|
||||
|
||||
const getMetricColumn = (
|
||||
metricAccessor: ExpressionValueVisDimension['accessor'],
|
||||
visData: Datatable
|
||||
) => {
|
||||
return getColumnByAccessor(metricAccessor, visData.columns);
|
||||
};
|
||||
|
||||
export const getColumns = (
|
||||
visParams: PieVisParams,
|
||||
visParams: PartitionVisParams,
|
||||
visData: Datatable
|
||||
): {
|
||||
metricColumn: DatatableColumn;
|
||||
bucketColumns: Array<Partial<BucketColumns>>;
|
||||
} => {
|
||||
if (visParams.dimensions.buckets && visParams.dimensions.buckets.length > 0) {
|
||||
const bucketColumns: Array<Partial<BucketColumns>> = visParams.dimensions.buckets.map(
|
||||
({ accessor, format }) => ({
|
||||
...getColumnByAccessor(accessor, visData.columns),
|
||||
format,
|
||||
})
|
||||
);
|
||||
const { metric, buckets } = visParams.dimensions;
|
||||
if (buckets && buckets.length > 0) {
|
||||
const bucketColumns: Array<Partial<BucketColumns>> = buckets.map(({ accessor, format }) => ({
|
||||
...getColumnByAccessor(accessor, visData.columns),
|
||||
format,
|
||||
}));
|
||||
|
||||
const lastBucketId = bucketColumns[bucketColumns.length - 1].id;
|
||||
const matchingIndex = visData.columns.findIndex((col) => col.id === lastBucketId);
|
||||
|
||||
return {
|
||||
bucketColumns,
|
||||
metricColumn: visData.columns[matchingIndex + 1],
|
||||
metricColumn: getMetricColumn(metric?.accessor ?? matchingIndex + 1, visData),
|
||||
};
|
||||
}
|
||||
const metricAccessor = visParams?.dimensions?.metric.accessor ?? 0;
|
||||
const metricColumn = getColumnByAccessor(metricAccessor, visData.columns);
|
||||
const metricColumn = getMetricColumn(metric?.accessor ?? 0, visData);
|
||||
return {
|
||||
metricColumn,
|
||||
bucketColumns: [
|
||||
{
|
||||
name: metricColumn.name,
|
||||
},
|
||||
],
|
||||
bucketColumns: [{ name: metricColumn.name }],
|
||||
};
|
||||
};
|
|
@ -8,7 +8,15 @@
|
|||
import { DatatableRow } from '../../../../expressions/public';
|
||||
import { BucketColumns } from '../../common/types';
|
||||
|
||||
export const getDistinctSeries = (rows: DatatableRow[], buckets: Array<Partial<BucketColumns>>) => {
|
||||
export interface DistinctSeries {
|
||||
allSeries: string[];
|
||||
parentSeries: string[];
|
||||
}
|
||||
|
||||
export const getDistinctSeries = (
|
||||
rows: DatatableRow[],
|
||||
buckets: Array<Partial<BucketColumns>>
|
||||
): DistinctSeries => {
|
||||
const parentBucketId = buckets[0].id;
|
||||
const parentSeries: string[] = [];
|
||||
const allSeries: string[] = [];
|
||||
|
@ -24,8 +32,5 @@ export const getDistinctSeries = (rows: DatatableRow[], buckets: Array<Partial<B
|
|||
}
|
||||
});
|
||||
});
|
||||
return {
|
||||
allSeries,
|
||||
parentSeries,
|
||||
};
|
||||
return { allSeries, parentSeries };
|
||||
};
|
|
@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiContextMenuPanelDescriptor, EuiIcon, EuiPopover, EuiContextMenu } from '@elastic/eui';
|
||||
import { LegendAction, SeriesIdentifier, useLegendAction } from '@elastic/charts';
|
||||
import { DataPublicPluginStart } from '../../../../data/public';
|
||||
import { PieVisParams } from '../../common/types';
|
||||
import { PartitionVisParams } from '../../common/types';
|
||||
import { ClickTriggerEvent } from '../../../../charts/public';
|
||||
import { FieldFormatsStart } from '../../../../field_formats/public';
|
||||
|
||||
|
@ -23,7 +23,7 @@ export const getLegendActions = (
|
|||
) => Promise<boolean>,
|
||||
getFilterEventData: (series: SeriesIdentifier) => ClickTriggerEvent | null,
|
||||
onFilter: (data: ClickTriggerEvent, negate?: any) => void,
|
||||
visParams: PieVisParams,
|
||||
visParams: PartitionVisParams,
|
||||
actions: DataPublicPluginStart['actions'],
|
||||
formatter: FieldFormatsStart
|
||||
): LegendAction => {
|
||||
|
@ -56,7 +56,7 @@ export const getLegendActions = (
|
|||
title: `${title}`,
|
||||
items: [
|
||||
{
|
||||
name: i18n.translate('expressionPie.legend.filterForValueButtonAriaLabel', {
|
||||
name: i18n.translate('expressionPartitionVis.legend.filterForValueButtonAriaLabel', {
|
||||
defaultMessage: 'Filter for value',
|
||||
}),
|
||||
'data-test-subj': `legend-${title}-filterIn`,
|
||||
|
@ -67,7 +67,7 @@ export const getLegendActions = (
|
|||
},
|
||||
},
|
||||
{
|
||||
name: i18n.translate('expressionPie.legend.filterOutValueButtonAriaLabel', {
|
||||
name: i18n.translate('expressionPartitionVis.legend.filterOutValueButtonAriaLabel', {
|
||||
defaultMessage: 'Filter out value',
|
||||
}),
|
||||
'data-test-subj': `legend-${title}-filterOut`,
|
||||
|
@ -114,7 +114,7 @@ export const getLegendActions = (
|
|||
}}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="upLeft"
|
||||
title={i18n.translate('expressionPie.legend.filterOptionsLegend', {
|
||||
title={i18n.translate('expressionPartitionVis.legend.filterOptionsLegend', {
|
||||
defaultMessage: '{legendDataLabel}, filter options',
|
||||
values: { legendDataLabel: title },
|
||||
})}
|
|
@ -0,0 +1,496 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { getPartitionTheme } from './get_partition_theme';
|
||||
import { createMockPieParams, createMockDonutParams, createMockPartitionVisParams } from '../mocks';
|
||||
import { ChartTypes, LabelPositions, PartitionVisParams } from '../../common/types';
|
||||
import { RecursivePartial } from '@elastic/eui';
|
||||
import { Theme } from '@elastic/charts';
|
||||
|
||||
const column: ExpressionValueVisDimension = {
|
||||
type: 'vis_dimension',
|
||||
accessor: { id: 'col-1-1', name: 'Count', meta: { type: 'number' } },
|
||||
format: {
|
||||
id: 'terms',
|
||||
params: {
|
||||
id: 'string',
|
||||
otherBucketLabel: 'Other',
|
||||
missingBucketLabel: 'Missing',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const splitRows = [column];
|
||||
const splitColumns = [column];
|
||||
const chartTheme: RecursivePartial<Theme> = {
|
||||
barSeriesStyle: { displayValue: { fontFamily: 'Arial' } },
|
||||
lineSeriesStyle: { point: { fill: '#fff' } },
|
||||
axes: { axisTitle: { fill: '#000' } },
|
||||
};
|
||||
|
||||
const linkLabelWithEnoughSpace = (visParams: PartitionVisParams) => ({
|
||||
maxCount: Number.POSITIVE_INFINITY,
|
||||
maximumSection: Number.POSITIVE_INFINITY,
|
||||
maxTextLength: visParams.labels.truncate ?? undefined,
|
||||
});
|
||||
|
||||
const linkLabelsWithoutSpaceForOuterLabels = { maxCount: 0 };
|
||||
|
||||
const linkLabelsWithoutSpaceForLabels = {
|
||||
maxCount: 0,
|
||||
maximumSection: Number.POSITIVE_INFINITY,
|
||||
};
|
||||
|
||||
const getStaticThemePartition = (
|
||||
theme: RecursivePartial<Theme>,
|
||||
visParams: PartitionVisParams
|
||||
) => ({
|
||||
fontFamily: theme.barSeriesStyle?.displayValue?.fontFamily,
|
||||
outerSizeRatio: 1,
|
||||
minFontSize: 10,
|
||||
maxFontSize: 16,
|
||||
emptySizeRatio: visParams.emptySizeRatio ?? 0,
|
||||
sectorLineStroke: theme.lineSeriesStyle?.point?.fill,
|
||||
sectorLineWidth: 1.5,
|
||||
circlePadding: 4,
|
||||
});
|
||||
|
||||
const getStaticThemeOptions = (theme: RecursivePartial<Theme>, visParams: PartitionVisParams) => ({
|
||||
partition: getStaticThemePartition(theme, visParams),
|
||||
chartMargins: { top: 0, left: 0, bottom: 0, right: 0 },
|
||||
});
|
||||
|
||||
const getDefaultLinkLabel = (visParams: PartitionVisParams, theme: RecursivePartial<Theme>) => ({
|
||||
maxCount: 5,
|
||||
fontSize: 11,
|
||||
textColor: theme.axes?.axisTitle?.fill,
|
||||
maxTextLength: visParams.labels.truncate ?? undefined,
|
||||
});
|
||||
|
||||
const dimensions = undefined;
|
||||
|
||||
const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: PartitionVisParams) => {
|
||||
const vParamsSplitRows = {
|
||||
...visParams,
|
||||
dimensions: { ...visParams.dimensions, splitRow: splitRows },
|
||||
};
|
||||
const vParamsSplitColumns = {
|
||||
...visParams,
|
||||
dimensions: { ...visParams.dimensions, splitColumn: splitColumns },
|
||||
};
|
||||
|
||||
it('should return correct default theme options', () => {
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions);
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return padding settings if dimensions are not specified', () => {
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return padding settings if split column or row are specified', () => {
|
||||
const themeForSplitColumns = getPartitionTheme(
|
||||
chartType,
|
||||
vParamsSplitColumns,
|
||||
chartTheme,
|
||||
dimensions
|
||||
);
|
||||
|
||||
expect(themeForSplitColumns).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParamsSplitColumns),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParamsSplitColumns),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: linkLabelsWithoutSpaceForOuterLabels,
|
||||
},
|
||||
});
|
||||
|
||||
const themeForSplitRows = getPartitionTheme(
|
||||
chartType,
|
||||
vParamsSplitRows,
|
||||
chartTheme,
|
||||
dimensions
|
||||
);
|
||||
|
||||
expect(themeForSplitRows).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParamsSplitRows),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParamsSplitRows),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: linkLabelsWithoutSpaceForOuterLabels,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return adjusted padding settings if dimensions are specified', () => {
|
||||
const specifiedDimensions = { width: 2000, height: 2000 };
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, specifiedDimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 },
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return right settings for the theme related fields', () => {
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions);
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined outerSizeRatio for split chart and show labels', () => {
|
||||
const specifiedDimensions = { width: 2000, height: 2000 };
|
||||
const theme = getPartitionTheme(chartType, vParamsSplitRows, chartTheme, specifiedDimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParamsSplitRows),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParamsSplitRows),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: linkLabelsWithoutSpaceForOuterLabels,
|
||||
},
|
||||
});
|
||||
|
||||
const themeForSplitColumns = getPartitionTheme(
|
||||
chartType,
|
||||
vParamsSplitColumns,
|
||||
chartTheme,
|
||||
specifiedDimensions
|
||||
);
|
||||
|
||||
expect(themeForSplitColumns).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParamsSplitColumns),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParamsSplitColumns),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: linkLabelsWithoutSpaceForOuterLabels,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
'should return undefined outerSizeRatio for not specified dimensions, visible labels,' +
|
||||
'and default labels position and not split chart',
|
||||
() => {
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it(
|
||||
'should return rescaleFactor value for outerSizeRatio if dimensions are specified,' +
|
||||
' is not split chart, labels are shown and labels position is not `inside`',
|
||||
() => {
|
||||
const specifiedDimensions = { width: 2000, height: 2000 };
|
||||
const rescaleFactor = 2;
|
||||
const theme = getPartitionTheme(
|
||||
chartType,
|
||||
visParams,
|
||||
chartTheme,
|
||||
specifiedDimensions,
|
||||
rescaleFactor
|
||||
);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 },
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
outerSizeRatio: rescaleFactor,
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
it(
|
||||
'should return adjusted rescaleFactor for outerSizeRatio if dimensions are specified,' +
|
||||
' is not split chart, labels position is `inside` and labels are shown',
|
||||
() => {
|
||||
const specifiedDimensions = { width: 2000, height: 2000 };
|
||||
const rescaleFactor = 1;
|
||||
const vParams = {
|
||||
...visParams,
|
||||
labels: { ...visParams.labels, position: LabelPositions.INSIDE },
|
||||
};
|
||||
|
||||
const theme = getPartitionTheme(
|
||||
chartType,
|
||||
vParams,
|
||||
chartTheme,
|
||||
specifiedDimensions,
|
||||
rescaleFactor
|
||||
);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParams),
|
||||
chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 },
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParams),
|
||||
outerSizeRatio: 0.5,
|
||||
linkLabel: linkLabelsWithoutSpaceForOuterLabels,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
it(
|
||||
'should return linkLabel with enough space if labels are shown,' +
|
||||
' labels position is `default` and need to show the last level only.',
|
||||
() => {
|
||||
const specifiedDimensions = { width: 2000, height: 2000 };
|
||||
const vParams = {
|
||||
...visParams,
|
||||
labels: { ...visParams.labels, last_level: true },
|
||||
};
|
||||
const theme = getPartitionTheme(chartType, vParams, chartTheme, specifiedDimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParams),
|
||||
chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 },
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParams),
|
||||
linkLabel: linkLabelWithEnoughSpace(vParams),
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('should hide links if position is `inside` or is split chart, and labels are shown', () => {
|
||||
const vParams = {
|
||||
...visParams,
|
||||
labels: { ...visParams.labels, position: LabelPositions.INSIDE },
|
||||
};
|
||||
const theme = getPartitionTheme(chartType, vParams, chartTheme, dimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParams),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: linkLabelsWithoutSpaceForOuterLabels,
|
||||
},
|
||||
});
|
||||
|
||||
const themeSplitColumns = getPartitionTheme(
|
||||
chartType,
|
||||
vParamsSplitColumns,
|
||||
chartTheme,
|
||||
dimensions
|
||||
);
|
||||
|
||||
expect(themeSplitColumns).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParamsSplitColumns),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParamsSplitColumns),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: linkLabelsWithoutSpaceForOuterLabels,
|
||||
},
|
||||
});
|
||||
|
||||
const themeSplitRows = getPartitionTheme(chartType, vParamsSplitRows, chartTheme, dimensions);
|
||||
|
||||
expect(themeSplitRows).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParamsSplitRows),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParamsSplitRows),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: linkLabelsWithoutSpaceForOuterLabels,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide links if labels are not shown', () => {
|
||||
const vParams = { ...visParams, labels: { ...visParams.labels, show: false } };
|
||||
const theme = getPartitionTheme(chartType, vParams, chartTheme, dimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParams),
|
||||
outerSizeRatio: undefined,
|
||||
linkLabel: linkLabelsWithoutSpaceForLabels,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const runTreemapMosaicTestSuites = (chartType: ChartTypes, visParams: PartitionVisParams) => {
|
||||
const vParamsSplitRows = {
|
||||
...visParams,
|
||||
dimensions: { ...visParams.dimensions, splitRow: splitRows },
|
||||
};
|
||||
const vParamsSplitColumns = {
|
||||
...visParams,
|
||||
dimensions: { ...visParams.dimensions, splitColumn: splitColumns },
|
||||
};
|
||||
|
||||
it('should return correct theme options', () => {
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions);
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty padding settings if dimensions are not specified', () => {
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return padding settings if split column or row are specified', () => {
|
||||
const themeForSplitColumns = getPartitionTheme(
|
||||
chartType,
|
||||
vParamsSplitColumns,
|
||||
chartTheme,
|
||||
dimensions
|
||||
);
|
||||
|
||||
expect(themeForSplitColumns).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParamsSplitColumns),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParamsSplitColumns),
|
||||
linkLabel: getDefaultLinkLabel(vParamsSplitColumns, chartTheme),
|
||||
},
|
||||
});
|
||||
|
||||
const themeForSplitRows = getPartitionTheme(
|
||||
chartType,
|
||||
vParamsSplitRows,
|
||||
chartTheme,
|
||||
dimensions
|
||||
);
|
||||
|
||||
expect(themeForSplitRows).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParamsSplitRows),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParamsSplitRows),
|
||||
linkLabel: getDefaultLinkLabel(vParamsSplitRows, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return fullfilled padding settings if dimensions are specified', () => {
|
||||
const specifiedDimensions = { width: 2000, height: 2000 };
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, specifiedDimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 },
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return settings for the theme related fields', () => {
|
||||
const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions);
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should make color transparent if labels are hidden', () => {
|
||||
const vParams = { ...visParams, labels: { ...visParams.labels, show: false } };
|
||||
const theme = getPartitionTheme(chartType, vParams, chartTheme, dimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, vParams),
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, vParams),
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
fillLabel: { textColor: 'rgba(0,0,0,0)' },
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('Pie getPartitionTheme', () => {
|
||||
runPieDonutWaffleTestSuites(ChartTypes.PIE, createMockPieParams());
|
||||
});
|
||||
|
||||
describe('Donut getPartitionTheme', () => {
|
||||
const visParams = createMockDonutParams();
|
||||
const chartType = ChartTypes.DONUT;
|
||||
|
||||
runPieDonutWaffleTestSuites(chartType, visParams);
|
||||
|
||||
it('should return correct empty size ratio and partitionLayout', () => {
|
||||
const theme = getPartitionTheme(ChartTypes.DONUT, visParams, chartTheme, dimensions);
|
||||
|
||||
expect(theme).toEqual({
|
||||
...getStaticThemeOptions(chartTheme, visParams),
|
||||
outerSizeRatio: undefined,
|
||||
partition: {
|
||||
...getStaticThemePartition(chartTheme, visParams),
|
||||
linkLabel: getDefaultLinkLabel(visParams, chartTheme),
|
||||
outerSizeRatio: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Waffle getPartitionTheme', () => {
|
||||
runPieDonutWaffleTestSuites(ChartTypes.WAFFLE, createMockPartitionVisParams());
|
||||
});
|
||||
|
||||
describe('Mosaic getPartitionTheme', () => {
|
||||
runTreemapMosaicTestSuites(ChartTypes.MOSAIC, createMockPartitionVisParams());
|
||||
});
|
||||
|
||||
describe('Treemap getPartitionTheme', () => {
|
||||
runTreemapMosaicTestSuites(ChartTypes.TREEMAP, createMockPartitionVisParams());
|
||||
});
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { RecursivePartial, Theme, PartialTheme } from '@elastic/charts';
|
||||
import {
|
||||
ChartTypes,
|
||||
LabelPositions,
|
||||
PartitionVisParams,
|
||||
PieContainerDimensions,
|
||||
} from '../../common/types';
|
||||
|
||||
type GetThemeByTypeFn = (
|
||||
chartType: ChartTypes,
|
||||
visParams: PartitionVisParams,
|
||||
dimensions?: PieContainerDimensions,
|
||||
rescaleFactor?: number
|
||||
) => PartialTheme;
|
||||
|
||||
type GetThemeFn = (
|
||||
chartType: ChartTypes,
|
||||
visParams: PartitionVisParams,
|
||||
chartTheme: RecursivePartial<Theme>,
|
||||
dimensions?: PieContainerDimensions,
|
||||
rescaleFactor?: number
|
||||
) => PartialTheme;
|
||||
|
||||
type GetPieDonutWaffleThemeFn = (
|
||||
visParams: PartitionVisParams,
|
||||
dimensions?: PieContainerDimensions,
|
||||
rescaleFactor?: number
|
||||
) => PartialTheme;
|
||||
|
||||
type GetTreemapMosaicThemeFn = (visParams: PartitionVisParams) => PartialTheme;
|
||||
|
||||
const MAX_SIZE = 1000;
|
||||
|
||||
const getPieDonutWaffleCommonTheme: GetPieDonutWaffleThemeFn = (
|
||||
visParams,
|
||||
dimensions,
|
||||
rescaleFactor = 1
|
||||
) => {
|
||||
const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow);
|
||||
const preventLinksFromShowing =
|
||||
(visParams.labels.position === LabelPositions.INSIDE || isSplitChart) && visParams.labels.show;
|
||||
|
||||
const usingOuterSizeRatio =
|
||||
dimensions && !isSplitChart
|
||||
? {
|
||||
outerSizeRatio:
|
||||
// Cap the ratio to 1 and then rescale
|
||||
rescaleFactor * Math.min(MAX_SIZE / Math.min(dimensions?.width, dimensions?.height), 1),
|
||||
}
|
||||
: { outerSizeRatio: undefined };
|
||||
|
||||
const theme: PartialTheme = {};
|
||||
theme.partition = { ...(usingOuterSizeRatio ?? {}) };
|
||||
|
||||
if (
|
||||
visParams.labels.show &&
|
||||
visParams.labels.position === LabelPositions.DEFAULT &&
|
||||
visParams.labels.last_level
|
||||
) {
|
||||
theme.partition.linkLabel = {
|
||||
maxCount: Number.POSITIVE_INFINITY,
|
||||
maximumSection: Number.POSITIVE_INFINITY,
|
||||
maxTextLength: visParams.labels.truncate ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (preventLinksFromShowing || !visParams.labels.show) {
|
||||
// Prevent links from showing
|
||||
theme.partition.linkLabel = {
|
||||
maxCount: 0,
|
||||
...(!visParams.labels.show ? { maximumSection: Number.POSITIVE_INFINITY } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
if (!preventLinksFromShowing && dimensions && !isSplitChart) {
|
||||
// shrink up to 20% to give some room for the linked values
|
||||
theme.partition.outerSizeRatio = rescaleFactor;
|
||||
}
|
||||
|
||||
return theme;
|
||||
};
|
||||
|
||||
const getDonutSpecificTheme: GetPieDonutWaffleThemeFn = (visParams, ...args) => {
|
||||
const { partition, ...restTheme } = getPieDonutWaffleCommonTheme(visParams, ...args);
|
||||
return { ...restTheme, partition: { ...partition, emptySizeRatio: visParams.emptySizeRatio } };
|
||||
};
|
||||
|
||||
const getTreemapMosaicCommonTheme: GetTreemapMosaicThemeFn = (visParams) => {
|
||||
if (!visParams.labels.show) {
|
||||
return {
|
||||
partition: {
|
||||
fillLabel: { textColor: 'rgba(0,0,0,0)' },
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const getSpecificTheme: GetThemeByTypeFn = (chartType, visParams, dimensions, rescaleFactor) =>
|
||||
({
|
||||
[ChartTypes.PIE]: () => getPieDonutWaffleCommonTheme(visParams, dimensions, rescaleFactor),
|
||||
[ChartTypes.DONUT]: () => getDonutSpecificTheme(visParams, dimensions, rescaleFactor),
|
||||
[ChartTypes.TREEMAP]: () => getTreemapMosaicCommonTheme(visParams),
|
||||
[ChartTypes.MOSAIC]: () => getTreemapMosaicCommonTheme(visParams),
|
||||
[ChartTypes.WAFFLE]: () => getPieDonutWaffleCommonTheme(visParams, dimensions, rescaleFactor),
|
||||
}[chartType]());
|
||||
|
||||
export const getPartitionTheme: GetThemeFn = (
|
||||
chartType,
|
||||
visParams,
|
||||
chartTheme,
|
||||
dimensions,
|
||||
rescaleFactor = 1
|
||||
) => {
|
||||
// On small multiples we want the labels to only appear inside
|
||||
const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow);
|
||||
const paddingProps: PartialTheme | null =
|
||||
dimensions && !isSplitChart
|
||||
? {
|
||||
chartPaddings: {
|
||||
top: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height,
|
||||
bottom: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height,
|
||||
left: ((1 - Math.min(1, MAX_SIZE / dimensions?.width)) / 2) * dimensions?.height,
|
||||
right: ((1 - Math.min(1, MAX_SIZE / dimensions?.width)) / 2) * dimensions?.height,
|
||||
},
|
||||
}
|
||||
: null;
|
||||
const partition = {
|
||||
fontFamily: chartTheme.barSeriesStyle?.displayValue?.fontFamily,
|
||||
outerSizeRatio: 1,
|
||||
minFontSize: 10,
|
||||
maxFontSize: 16,
|
||||
emptySizeRatio: 0,
|
||||
sectorLineStroke: chartTheme.lineSeriesStyle?.point?.fill,
|
||||
sectorLineWidth: 1.5,
|
||||
circlePadding: 4,
|
||||
linkLabel: {
|
||||
maxCount: 5,
|
||||
fontSize: 11,
|
||||
textColor: chartTheme.axes?.axisTitle?.fill,
|
||||
maxTextLength: visParams.labels.truncate ?? undefined,
|
||||
},
|
||||
};
|
||||
const { partition: specificPartition = {}, ...restSpecificTheme } = getSpecificTheme(
|
||||
chartType,
|
||||
visParams,
|
||||
dimensions,
|
||||
rescaleFactor
|
||||
);
|
||||
|
||||
return {
|
||||
partition: { ...partition, ...specificPartition },
|
||||
chartMargins: { top: 0, bottom: 0, left: 0, right: 0 },
|
||||
...(paddingProps ?? {}),
|
||||
...restSpecificTheme,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PartitionLayout } from '@elastic/charts';
|
||||
import { ChartTypes } from '../../common/types';
|
||||
|
||||
export const getPartitionType = (chartType: ChartTypes) =>
|
||||
({
|
||||
[ChartTypes.PIE]: PartitionLayout.sunburst,
|
||||
[ChartTypes.DONUT]: PartitionLayout.sunburst,
|
||||
[ChartTypes.TREEMAP]: PartitionLayout.treemap,
|
||||
[ChartTypes.MOSAIC]: PartitionLayout.mosaic,
|
||||
[ChartTypes.WAFFLE]: PartitionLayout.waffle,
|
||||
}[chartType]);
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { fieldFormatsMock } from '../../../../field_formats/common/mocks';
|
||||
import { DatatableColumn } from '../../../../expressions';
|
||||
import { createMockVisData } from '../mocks';
|
||||
import { getSplitDimensionAccessor } from './get_split_dimension_accessor';
|
||||
import { BucketColumns } from '../../common/types';
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
|
||||
describe('getSplitDimensionAccessor', () => {
|
||||
const visData = createMockVisData();
|
||||
|
||||
const preparedFormatter1 = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
const preparedFormatter2 = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args));
|
||||
|
||||
beforeEach(() => {
|
||||
defaultFormatter.mockClear();
|
||||
preparedFormatter1.mockClear();
|
||||
preparedFormatter2.mockClear();
|
||||
});
|
||||
|
||||
const formatters: Record<string, any> = {
|
||||
[visData.columns[0].id]: preparedFormatter1(),
|
||||
[visData.columns[1].id]: preparedFormatter2(),
|
||||
};
|
||||
|
||||
const splitDimension: ExpressionValueVisDimension = {
|
||||
type: 'vis_dimension',
|
||||
accessor: {
|
||||
id: visData.columns[1].id,
|
||||
name: visData.columns[1].name,
|
||||
meta: visData.columns[1].meta,
|
||||
},
|
||||
format: {
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
|
||||
it('returns accessor which is using formatter from formatters, if meta.params are present at accessing column', () => {
|
||||
const accessor = getSplitDimensionAccessor(
|
||||
visData.columns,
|
||||
splitDimension,
|
||||
formatters,
|
||||
defaultFormatter
|
||||
);
|
||||
const formatter = formatters[visData.columns[1].id];
|
||||
const spyOnFormatterConvert = jest.spyOn(formatter, 'convert');
|
||||
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(0);
|
||||
expect(typeof accessor).toBe('function');
|
||||
accessor(visData.rows[0]);
|
||||
expect(spyOnFormatterConvert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns accessor which is using default formatter, if meta.params are not present and format is present at accessing column', () => {
|
||||
const column: Partial<BucketColumns> = {
|
||||
...visData.columns[1],
|
||||
meta: { type: 'string' },
|
||||
format: {
|
||||
id: 'string',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
const columns = [visData.columns[0], column, visData.columns[2]] as DatatableColumn[];
|
||||
const defaultFormatterReturnedVal = fieldFormatsMock.deserialize();
|
||||
const spyOnDefaultFormatterConvert = jest.spyOn(defaultFormatterReturnedVal, 'convert');
|
||||
|
||||
defaultFormatter.mockReturnValueOnce(defaultFormatterReturnedVal);
|
||||
|
||||
const accessor = getSplitDimensionAccessor(
|
||||
columns,
|
||||
splitDimension,
|
||||
formatters,
|
||||
defaultFormatter
|
||||
);
|
||||
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(1);
|
||||
expect(defaultFormatter).toHaveBeenCalledWith(column.format);
|
||||
|
||||
expect(typeof accessor).toBe('function');
|
||||
accessor(visData.rows[0]);
|
||||
expect(spyOnDefaultFormatterConvert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns accessor which is using default formatter, if meta.params and format are not present', () => {
|
||||
const column: Partial<BucketColumns> = {
|
||||
...visData.columns[1],
|
||||
meta: { type: 'string' },
|
||||
};
|
||||
const columns = [visData.columns[0], column, visData.columns[2]] as DatatableColumn[];
|
||||
const defaultFormatterReturnedVal = fieldFormatsMock.deserialize();
|
||||
const spyOnDefaultFormatterConvert = jest.spyOn(defaultFormatterReturnedVal, 'convert');
|
||||
|
||||
defaultFormatter.mockReturnValueOnce(defaultFormatterReturnedVal);
|
||||
|
||||
const accessor = getSplitDimensionAccessor(
|
||||
columns,
|
||||
splitDimension,
|
||||
formatters,
|
||||
defaultFormatter
|
||||
);
|
||||
|
||||
expect(defaultFormatter).toHaveBeenCalledTimes(1);
|
||||
expect(defaultFormatter).toHaveBeenCalledWith();
|
||||
|
||||
expect(typeof accessor).toBe('function');
|
||||
accessor(visData.rows[0]);
|
||||
expect(spyOnDefaultFormatterConvert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns accessor which returns undefined, if such column is not present', () => {
|
||||
const accessor1 = getSplitDimensionAccessor(
|
||||
visData.columns,
|
||||
splitDimension,
|
||||
formatters,
|
||||
defaultFormatter
|
||||
);
|
||||
|
||||
expect(typeof accessor1).toBe('function');
|
||||
const result1 = accessor1({});
|
||||
expect(result1).toBeUndefined();
|
||||
|
||||
const column2: Partial<BucketColumns> = {
|
||||
...visData.columns[1],
|
||||
meta: { type: 'string' },
|
||||
};
|
||||
const columns2 = [visData.columns[0], column2, visData.columns[2]] as DatatableColumn[];
|
||||
const accessor2 = getSplitDimensionAccessor(
|
||||
columns2,
|
||||
splitDimension,
|
||||
formatters,
|
||||
defaultFormatter
|
||||
);
|
||||
|
||||
expect(typeof accessor2).toBe('function');
|
||||
const result2 = accessor1({});
|
||||
expect(result2).toBeUndefined();
|
||||
|
||||
const column3: Partial<BucketColumns> = {
|
||||
...visData.columns[1],
|
||||
meta: { type: 'string' },
|
||||
format: {
|
||||
id: 'string',
|
||||
params: {},
|
||||
},
|
||||
};
|
||||
const columns3 = [visData.columns[0], column3, visData.columns[2]] as DatatableColumn[];
|
||||
|
||||
const accessor3 = getSplitDimensionAccessor(
|
||||
columns3,
|
||||
splitDimension,
|
||||
formatters,
|
||||
defaultFormatter
|
||||
);
|
||||
expect(typeof accessor3).toBe('function');
|
||||
const result3 = accessor3({});
|
||||
expect(result3).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { AccessorFn } from '@elastic/charts';
|
||||
import { getColumnByAccessor } from './accessor';
|
||||
import { DatatableColumn } from '../../../../expressions/public';
|
||||
import { FieldFormat, FormatFactory } from '../../../../field_formats/common';
|
||||
import { ExpressionValueVisDimension } from '../../../../visualizations/common';
|
||||
import { getFormatter } from './formatters';
|
||||
|
||||
export const getSplitDimensionAccessor = (
|
||||
columns: DatatableColumn[],
|
||||
splitDimension: ExpressionValueVisDimension,
|
||||
formatters: Record<string, FieldFormat | undefined>,
|
||||
defaultFormatFactory: FormatFactory
|
||||
): AccessorFn => {
|
||||
const splitChartColumn = getColumnByAccessor(splitDimension.accessor, columns);
|
||||
const accessor = splitChartColumn.id;
|
||||
const formatter = getFormatter(splitChartColumn, formatters, defaultFormatFactory);
|
||||
|
||||
const fn: AccessorFn = (d) => {
|
||||
const v = d[accessor];
|
||||
if (v === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const f = formatter.convert(v);
|
||||
return f;
|
||||
};
|
||||
|
||||
return fn;
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { getLayers } from './get_layers';
|
||||
export { getLayers } from './layers';
|
||||
export { LegendColorPickerWrapper, LegendColorPickerWrapperContext } from './get_color_picker';
|
||||
export { getLegendActions } from './get_legend_actions';
|
||||
export { canFilter, getFilterClickData, getFilterEventData } from './filter_helpers';
|
||||
|
@ -15,3 +15,6 @@ export { getColumns } from './get_columns';
|
|||
export { getSplitDimensionAccessor } from './get_split_dimension_accessor';
|
||||
export { getDistinctSeries } from './get_distinct_series';
|
||||
export { getColumnByAccessor } from './accessor';
|
||||
export { isLegendFlat, shouldShowLegend } from './legend';
|
||||
export { generateFormatters, getAvailableFormatter, getFormatter } from './formatters';
|
||||
export { getPartitionType } from './get_partition_type';
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { ShapeTreeNode } from '@elastic/charts';
|
||||
import { isEqual } from 'lodash';
|
||||
import type { FieldFormatsStart } from '../../../../../field_formats/public';
|
||||
import {
|
||||
SeriesLayer,
|
||||
PaletteRegistry,
|
||||
lightenColor,
|
||||
PaletteDefinition,
|
||||
PaletteOutput,
|
||||
} from '../../../../../charts/public';
|
||||
import type { Datatable, DatatableRow } from '../../../../../expressions/public';
|
||||
import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types';
|
||||
import { DistinctSeries, getDistinctSeries } from '../get_distinct_series';
|
||||
|
||||
const isTreemapOrMosaicChart = (shape: ChartTypes) =>
|
||||
[ChartTypes.MOSAIC, ChartTypes.TREEMAP].includes(shape);
|
||||
|
||||
export const byDataColorPaletteMap = (
|
||||
rows: Datatable['rows'],
|
||||
columnId: string,
|
||||
paletteDefinition: PaletteDefinition,
|
||||
{ params }: PaletteOutput
|
||||
) => {
|
||||
const colorMap = new Map<string, string | undefined>(
|
||||
rows.map((item) => [String(item[columnId]), undefined])
|
||||
);
|
||||
let rankAtDepth = 0;
|
||||
|
||||
return {
|
||||
getColor: (item: unknown) => {
|
||||
const key = String(item);
|
||||
if (!colorMap.has(key)) return;
|
||||
|
||||
let color = colorMap.get(key);
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
color =
|
||||
paletteDefinition.getCategoricalColor(
|
||||
[
|
||||
{
|
||||
name: key,
|
||||
totalSeriesAtDepth: colorMap.size,
|
||||
rankAtDepth: rankAtDepth++,
|
||||
},
|
||||
],
|
||||
{ behindText: false },
|
||||
params
|
||||
) || undefined;
|
||||
|
||||
colorMap.set(key, color);
|
||||
return color;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const getDistinctColor = (
|
||||
d: ShapeTreeNode,
|
||||
isSplitChart: boolean,
|
||||
overwriteColors: { [key: string]: string } = {},
|
||||
visParams: PartitionVisParams,
|
||||
palettes: PaletteRegistry | null,
|
||||
syncColors: boolean,
|
||||
{ parentSeries, allSeries }: DistinctSeries,
|
||||
name: string
|
||||
) => {
|
||||
let overwriteColor;
|
||||
// this is for supporting old visualizations (created by vislib plugin)
|
||||
// it seems that there for some aggs, the uiState saved from vislib is
|
||||
// different than the es-charts handle it
|
||||
if (overwriteColors.hasOwnProperty(name)) {
|
||||
overwriteColor = overwriteColors[name];
|
||||
}
|
||||
|
||||
if (Object.keys(overwriteColors).includes(d.dataName.toString())) {
|
||||
overwriteColor = overwriteColors[d.dataName];
|
||||
}
|
||||
|
||||
if (overwriteColor) {
|
||||
return overwriteColor;
|
||||
}
|
||||
|
||||
const index = allSeries.findIndex((dataName) => isEqual(dataName, d.dataName));
|
||||
const isSplitParentLayer = isSplitChart && parentSeries.includes(d.dataName);
|
||||
return palettes?.get(visParams.palette.name).getCategoricalColor(
|
||||
[
|
||||
{
|
||||
name: d.dataName,
|
||||
rankAtDepth: isSplitParentLayer
|
||||
? parentSeries.findIndex((dataName) => dataName === d.dataName)
|
||||
: index > -1
|
||||
? index
|
||||
: 0,
|
||||
totalSeriesAtDepth: isSplitParentLayer ? parentSeries.length : allSeries.length || 1,
|
||||
},
|
||||
],
|
||||
{
|
||||
maxDepth: 1,
|
||||
totalSeries: allSeries.length || 1,
|
||||
behindText: visParams.labels.show,
|
||||
syncColors,
|
||||
},
|
||||
visParams.palette?.params ?? { colors: [] }
|
||||
);
|
||||
};
|
||||
|
||||
const createSeriesLayers = (
|
||||
d: ShapeTreeNode,
|
||||
parentSeries: DistinctSeries['parentSeries'],
|
||||
isSplitChart: boolean
|
||||
) => {
|
||||
const seriesLayers: SeriesLayer[] = [];
|
||||
let tempParent: typeof d | typeof d['parent'] = d;
|
||||
while (tempParent.parent && tempParent.depth > 0) {
|
||||
const seriesName = String(tempParent.parent.children[tempParent.sortIndex][0]);
|
||||
const isSplitParentLayer = isSplitChart && parentSeries.includes(seriesName);
|
||||
seriesLayers.unshift({
|
||||
name: seriesName,
|
||||
rankAtDepth: isSplitParentLayer
|
||||
? parentSeries.findIndex((name) => name === seriesName)
|
||||
: tempParent.sortIndex,
|
||||
totalSeriesAtDepth: isSplitParentLayer
|
||||
? parentSeries.length
|
||||
: tempParent.parent.children.length,
|
||||
});
|
||||
tempParent = tempParent.parent;
|
||||
}
|
||||
return seriesLayers;
|
||||
};
|
||||
|
||||
const overrideColorForOldVisualization = (
|
||||
seriesLayers: SeriesLayer[],
|
||||
overwriteColors: { [key: string]: string },
|
||||
name: string
|
||||
) => {
|
||||
let overwriteColor;
|
||||
// this is for supporting old visualizations (created by vislib plugin)
|
||||
// it seems that there for some aggs, the uiState saved from vislib is
|
||||
// different than the es-charts handle it
|
||||
if (overwriteColors.hasOwnProperty(name)) {
|
||||
overwriteColor = overwriteColors[name];
|
||||
}
|
||||
|
||||
seriesLayers.forEach((layer) => {
|
||||
if (Object.keys(overwriteColors).includes(layer.name)) {
|
||||
overwriteColor = overwriteColors[layer.name];
|
||||
}
|
||||
});
|
||||
|
||||
return overwriteColor;
|
||||
};
|
||||
|
||||
export const getColor = (
|
||||
chartType: ChartTypes,
|
||||
d: ShapeTreeNode,
|
||||
layerIndex: number,
|
||||
isSplitChart: boolean,
|
||||
overwriteColors: { [key: string]: string } = {},
|
||||
columns: Array<Partial<BucketColumns>>,
|
||||
rows: DatatableRow[],
|
||||
visParams: PartitionVisParams,
|
||||
palettes: PaletteRegistry | null,
|
||||
byDataPalette: ReturnType<typeof byDataColorPaletteMap>,
|
||||
syncColors: boolean,
|
||||
isDarkMode: boolean,
|
||||
formatter: FieldFormatsStart,
|
||||
format?: BucketColumns['format']
|
||||
) => {
|
||||
const distinctSeries = getDistinctSeries(rows, columns);
|
||||
const { parentSeries } = distinctSeries;
|
||||
const dataName = d.dataName;
|
||||
|
||||
// Mind the difference here: the contrast computation for the text ignores the alpha/opacity
|
||||
// therefore change it for dask mode
|
||||
const defaultColor = isDarkMode ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)';
|
||||
|
||||
let name = '';
|
||||
if (format) {
|
||||
name = formatter.deserialize(format).convert(dataName) ?? '';
|
||||
}
|
||||
|
||||
if (visParams.distinctColors) {
|
||||
return (
|
||||
getDistinctColor(
|
||||
d,
|
||||
isSplitChart,
|
||||
overwriteColors,
|
||||
visParams,
|
||||
palettes,
|
||||
syncColors,
|
||||
distinctSeries,
|
||||
name
|
||||
) || defaultColor
|
||||
);
|
||||
}
|
||||
|
||||
const seriesLayers = createSeriesLayers(d, parentSeries, isSplitChart);
|
||||
|
||||
const overwriteColor = overrideColorForOldVisualization(seriesLayers, overwriteColors, name);
|
||||
if (overwriteColor) {
|
||||
return lightenColor(overwriteColor, seriesLayers.length, columns.length);
|
||||
}
|
||||
|
||||
if (chartType === ChartTypes.MOSAIC && byDataPalette && seriesLayers[1]) {
|
||||
return byDataPalette.getColor(seriesLayers[1].name) || defaultColor;
|
||||
}
|
||||
|
||||
if (isTreemapOrMosaicChart(chartType)) {
|
||||
if (layerIndex < columns.length - 1) {
|
||||
return defaultColor;
|
||||
}
|
||||
// only use the top level series layer for coloring
|
||||
if (seriesLayers.length > 1) {
|
||||
seriesLayers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
const outputColor = palettes?.get(visParams.palette.name).getCategoricalColor(
|
||||
seriesLayers,
|
||||
{
|
||||
behindText: visParams.labels.show || isTreemapOrMosaicChart(chartType),
|
||||
maxDepth: columns.length,
|
||||
totalSeries: rows.length,
|
||||
syncColors,
|
||||
},
|
||||
visParams.palette?.params ?? { colors: [] }
|
||||
);
|
||||
|
||||
return outputColor || defaultColor;
|
||||
};
|
|
@ -6,11 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { ShapeTreeNode } from '@elastic/charts';
|
||||
import { PaletteDefinition, SeriesLayer } from '../../../../charts/public';
|
||||
import { dataPluginMock } from '../../../../data/public/mocks';
|
||||
import type { DataPublicPluginStart } from '../../../../data/public';
|
||||
import { computeColor } from './get_layers';
|
||||
import { createMockVisData, createMockBucketColumns, createMockPieParams } from '../mocks';
|
||||
import { PaletteDefinition, SeriesLayer } from '../../../../../charts/public';
|
||||
import { dataPluginMock } from '../../../../../data/public/mocks';
|
||||
import type { DataPublicPluginStart } from '../../../../../data/public';
|
||||
import { getColor } from './get_color';
|
||||
import { createMockVisData, createMockBucketColumns, createMockPieParams } from '../../mocks';
|
||||
import { ChartTypes } from '../../../common/types';
|
||||
|
||||
const visData = createMockVisData();
|
||||
const buckets = createMockBucketColumns();
|
||||
|
@ -68,14 +69,18 @@ describe('computeColor', () => {
|
|||
sortIndex: 0,
|
||||
},
|
||||
} as unknown as ShapeTreeNode;
|
||||
const color = computeColor(
|
||||
const color = getColor(
|
||||
ChartTypes.PIE,
|
||||
d,
|
||||
0,
|
||||
false,
|
||||
{},
|
||||
buckets,
|
||||
visData.rows,
|
||||
visParams,
|
||||
getPaletteRegistry(),
|
||||
{ getColor: () => undefined },
|
||||
false,
|
||||
false,
|
||||
dataMock.fieldFormats
|
||||
);
|
||||
|
@ -93,14 +98,18 @@ describe('computeColor', () => {
|
|||
sortIndex: 0,
|
||||
},
|
||||
} as unknown as ShapeTreeNode;
|
||||
const color = computeColor(
|
||||
const color = getColor(
|
||||
ChartTypes.PIE,
|
||||
d,
|
||||
0,
|
||||
true,
|
||||
{},
|
||||
buckets,
|
||||
visData.rows,
|
||||
visParams,
|
||||
getPaletteRegistry(),
|
||||
{ getColor: () => undefined },
|
||||
false,
|
||||
false,
|
||||
dataMock.fieldFormats
|
||||
);
|
||||
|
@ -117,14 +126,18 @@ describe('computeColor', () => {
|
|||
sortIndex: 0,
|
||||
},
|
||||
} as unknown as ShapeTreeNode;
|
||||
const color = computeColor(
|
||||
const color = getColor(
|
||||
ChartTypes.PIE,
|
||||
d,
|
||||
0,
|
||||
true,
|
||||
{ 'ES-Air': '#000028' },
|
||||
buckets,
|
||||
visData.rows,
|
||||
visParams,
|
||||
getPaletteRegistry(),
|
||||
{ getColor: () => undefined },
|
||||
false,
|
||||
false,
|
||||
dataMock.fieldFormats
|
||||
);
|
||||
|
@ -162,14 +175,18 @@ describe('computeColor', () => {
|
|||
...visParams,
|
||||
distinctColors: true,
|
||||
};
|
||||
const color = computeColor(
|
||||
const color = getColor(
|
||||
ChartTypes.PIE,
|
||||
d,
|
||||
0,
|
||||
true,
|
||||
{ '≥ 1000 and < 2000': '#3F6833' },
|
||||
buckets,
|
||||
visData.rows,
|
||||
visParamsNew,
|
||||
getPaletteRegistry(),
|
||||
{ getColor: () => undefined },
|
||||
false,
|
||||
false,
|
||||
dataMock.fieldFormats,
|
||||
{
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Datum, PartitionLayer } from '@elastic/charts';
|
||||
import { FieldFormat } from '../../../../../field_formats/common';
|
||||
import type { FieldFormatsStart } from '../../../../../field_formats/public';
|
||||
import { PaletteRegistry } from '../../../../../charts/public';
|
||||
import type { Datatable, DatatableRow } from '../../../../../expressions/public';
|
||||
import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types';
|
||||
import { sortPredicateByType } from './sort_predicate';
|
||||
import { byDataColorPaletteMap, getColor } from './get_color';
|
||||
import { getNodeLabel } from './get_node_labels';
|
||||
|
||||
const EMPTY_SLICE = Symbol('empty_slice');
|
||||
|
||||
export const getLayers = (
|
||||
chartType: ChartTypes,
|
||||
columns: Array<Partial<BucketColumns>>,
|
||||
visParams: PartitionVisParams,
|
||||
visData: Datatable,
|
||||
overwriteColors: { [key: string]: string } = {},
|
||||
rows: DatatableRow[],
|
||||
palettes: PaletteRegistry | null,
|
||||
formatters: Record<string, FieldFormat | undefined>,
|
||||
formatter: FieldFormatsStart,
|
||||
syncColors: boolean,
|
||||
isDarkMode: boolean
|
||||
): PartitionLayer[] => {
|
||||
const fillLabel: PartitionLayer['fillLabel'] = {
|
||||
valueFont: {
|
||||
fontWeight: 700,
|
||||
},
|
||||
};
|
||||
|
||||
if (!visParams.labels.values) {
|
||||
fillLabel.valueFormatter = () => '';
|
||||
}
|
||||
|
||||
const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow);
|
||||
let byDataPalette: ReturnType<typeof byDataColorPaletteMap>;
|
||||
if (!syncColors && columns[1]?.id && palettes && visParams.palette) {
|
||||
byDataPalette = byDataColorPaletteMap(
|
||||
rows,
|
||||
columns[1].id,
|
||||
palettes?.get(visParams.palette.name),
|
||||
visParams.palette
|
||||
);
|
||||
}
|
||||
|
||||
const sortPredicate = sortPredicateByType(chartType, visParams, visData, columns);
|
||||
return columns.map((col, layerIndex) => {
|
||||
return {
|
||||
groupByRollup: (d: Datum) => (col.id ? d[col.id] ?? EMPTY_SLICE : col.name),
|
||||
showAccessor: (d: Datum) => d !== EMPTY_SLICE,
|
||||
nodeLabel: (d: unknown) => getNodeLabel(d, col, formatters, formatter.deserialize),
|
||||
fillLabel,
|
||||
sortPredicate,
|
||||
shape: {
|
||||
fillColor: (d) =>
|
||||
getColor(
|
||||
chartType,
|
||||
d,
|
||||
layerIndex,
|
||||
isSplitChart,
|
||||
overwriteColors,
|
||||
columns,
|
||||
rows,
|
||||
visParams,
|
||||
palettes,
|
||||
byDataPalette,
|
||||
syncColors,
|
||||
isDarkMode,
|
||||
formatter,
|
||||
col.format
|
||||
),
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { FieldFormat, FormatFactory } from '../../../../../field_formats/common';
|
||||
import { BucketColumns } from '../../../common/types';
|
||||
import { getAvailableFormatter } from '../formatters';
|
||||
|
||||
export const getNodeLabel = (
|
||||
nodeName: unknown,
|
||||
column: Partial<BucketColumns>,
|
||||
formatters: Record<string, FieldFormat | undefined>,
|
||||
defaultFormatFactory: FormatFactory
|
||||
) => {
|
||||
const formatter = getAvailableFormatter(column, formatters, defaultFormatFactory);
|
||||
if (formatter) {
|
||||
return formatter.convert(nodeName) ?? '';
|
||||
}
|
||||
|
||||
return String(nodeName);
|
||||
};
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './pie_vis_component';
|
||||
export { getLayers } from './get_layers';
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ArrayEntry } from '@elastic/charts';
|
||||
import { Datatable } from '../../../../../../../src/plugins/expressions';
|
||||
import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types';
|
||||
|
||||
type SortFn = (([name1, node1]: ArrayEntry, [name2, node2]: ArrayEntry) => number) | undefined;
|
||||
|
||||
type SortPredicateDefaultFn = (
|
||||
visData: Datatable,
|
||||
columns: Array<Partial<BucketColumns>>
|
||||
) => SortFn;
|
||||
|
||||
type SortPredicatePieDonutFn = (visParams: PartitionVisParams) => SortFn;
|
||||
|
||||
type SortPredicatePureFn = () => SortFn;
|
||||
|
||||
export const extractUniqTermsMap = (dataTable: Datatable, columnId: string) =>
|
||||
[...new Set(dataTable.rows.map((item) => item[columnId]))].reduce(
|
||||
(acc, item, index) => ({
|
||||
...acc,
|
||||
[item]: index,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const sortPredicateSaveSourceOrder: SortPredicatePureFn =
|
||||
() =>
|
||||
([, node1], [, node2]) => {
|
||||
const [index1] = node1.inputIndex ?? [];
|
||||
if (index1 !== undefined) {
|
||||
return index1;
|
||||
}
|
||||
return node2.value - node1.value;
|
||||
};
|
||||
|
||||
const sortPredicatePieDonut: SortPredicatePieDonutFn = (visParams) =>
|
||||
visParams.respectSourceOrder ? sortPredicateSaveSourceOrder() : undefined;
|
||||
|
||||
const sortPredicateMosaic: SortPredicateDefaultFn = (visData, columns) => {
|
||||
const sortingMap = columns[0]?.id ? extractUniqTermsMap(visData, columns[0].id) : {};
|
||||
|
||||
return ([name1, node1], [, node2]) => {
|
||||
// Sorting for first group
|
||||
if (columns.length === 1 || (node1.children.length && name1 in sortingMap)) {
|
||||
return sortingMap[name1];
|
||||
}
|
||||
|
||||
// Sorting for second group
|
||||
return node2.value - node1.value;
|
||||
};
|
||||
};
|
||||
|
||||
const sortPredicateWaffle: SortPredicatePureFn =
|
||||
() =>
|
||||
([, node1], [, node2]) =>
|
||||
node2.value - node1.value;
|
||||
|
||||
export const sortPredicateByType = (
|
||||
chartType: ChartTypes,
|
||||
visParams: PartitionVisParams,
|
||||
visData: Datatable,
|
||||
columns: Array<Partial<BucketColumns>>
|
||||
) =>
|
||||
({
|
||||
[ChartTypes.PIE]: () => sortPredicatePieDonut(visParams),
|
||||
[ChartTypes.DONUT]: () => sortPredicatePieDonut(visParams),
|
||||
[ChartTypes.WAFFLE]: () => sortPredicateWaffle(),
|
||||
[ChartTypes.TREEMAP]: () => undefined,
|
||||
[ChartTypes.MOSAIC]: () => sortPredicateMosaic(visData, columns),
|
||||
}[chartType]());
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ChartTypes, LegendDisplay } from '../../common/types';
|
||||
import { createMockVisData } from '../mocks';
|
||||
import { isLegendFlat, shouldShowLegend } from './legend';
|
||||
|
||||
describe('isLegendFlat', () => {
|
||||
const visData = createMockVisData();
|
||||
const splitChartDimension = visData.columns[0];
|
||||
|
||||
const runIsFlatCommonScenario = (chartType: ChartTypes) => {
|
||||
it(`legend should be flat for ${chartType} if split dimension is specified`, () => {
|
||||
const flat = isLegendFlat(chartType, splitChartDimension);
|
||||
expect(flat).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`legend should be not flat for ${chartType} if split dimension is not specified`, () => {
|
||||
const flat = isLegendFlat(chartType, undefined);
|
||||
expect(flat).toBeFalsy();
|
||||
});
|
||||
};
|
||||
|
||||
runIsFlatCommonScenario(ChartTypes.PIE);
|
||||
runIsFlatCommonScenario(ChartTypes.DONUT);
|
||||
runIsFlatCommonScenario(ChartTypes.TREEMAP);
|
||||
runIsFlatCommonScenario(ChartTypes.MOSAIC);
|
||||
|
||||
it('legend should be flat for Waffle if split dimension is specified', () => {
|
||||
const flat = isLegendFlat(ChartTypes.WAFFLE, splitChartDimension);
|
||||
expect(flat).toBeTruthy();
|
||||
});
|
||||
|
||||
it('legend should be flat for Waffle if split dimension is not specified', () => {
|
||||
const flat = isLegendFlat(ChartTypes.WAFFLE, undefined);
|
||||
expect(flat).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldShowLegend', () => {
|
||||
const visData = createMockVisData();
|
||||
|
||||
const runCommonShouldShowLegendScenario = (chartType: ChartTypes) => {
|
||||
it(`should hide legend if legendDisplay = hide for ${chartType}`, () => {
|
||||
const show = shouldShowLegend(chartType, LegendDisplay.HIDE);
|
||||
expect(show).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should show legend if legendDisplay = show for ${chartType}`, () => {
|
||||
const show = shouldShowLegend(chartType, LegendDisplay.SHOW);
|
||||
expect(show).toBeTruthy();
|
||||
});
|
||||
};
|
||||
|
||||
const runShouldShowLegendDefaultBucketsScenario = (chartType: ChartTypes) => {
|
||||
it(`should show legend if legendDisplay = default and multiple buckets for ${chartType}`, () => {
|
||||
const show = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [
|
||||
visData.columns[0],
|
||||
visData.columns[1],
|
||||
]);
|
||||
|
||||
expect(show).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should hide legend if legendDisplay = default and one bucket or less for ${chartType}`, () => {
|
||||
const show1 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [visData.columns[0]]);
|
||||
expect(show1).toBeFalsy();
|
||||
|
||||
const show2 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, []);
|
||||
expect(show2).toBeFalsy();
|
||||
|
||||
const show3 = shouldShowLegend(chartType, LegendDisplay.DEFAULT);
|
||||
expect(show3).toBeFalsy();
|
||||
});
|
||||
};
|
||||
|
||||
const runShouldShowLegendDefaultAlwaysFalsyScenario = (chartType: ChartTypes) => {
|
||||
it(`should hide legend if legendDisplay = default and multiple buckets for ${chartType}`, () => {
|
||||
const show = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [
|
||||
visData.columns[0],
|
||||
visData.columns[1],
|
||||
]);
|
||||
|
||||
expect(show).toBeFalsy();
|
||||
});
|
||||
|
||||
it(`should hide legend if legendDisplay = default and one bucket or less for ${chartType}`, () => {
|
||||
const show1 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [visData.columns[0]]);
|
||||
expect(show1).toBeFalsy();
|
||||
|
||||
const show2 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, []);
|
||||
expect(show2).toBeFalsy();
|
||||
|
||||
const show3 = shouldShowLegend(chartType, LegendDisplay.DEFAULT);
|
||||
expect(show3).toBeFalsy();
|
||||
});
|
||||
};
|
||||
|
||||
const runShouldShowLegendDefaultAlwaysTruthyScenario = (chartType: ChartTypes) => {
|
||||
it(`should show legend if legendDisplay = default and multiple buckets for ${chartType}`, () => {
|
||||
const show = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [
|
||||
visData.columns[0],
|
||||
visData.columns[1],
|
||||
]);
|
||||
|
||||
expect(show).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should show legend if legendDisplay = default and one bucket or less for ${chartType}`, () => {
|
||||
const show1 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [visData.columns[0]]);
|
||||
expect(show1).toBeTruthy();
|
||||
|
||||
const show2 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, []);
|
||||
expect(show2).toBeTruthy();
|
||||
|
||||
const show3 = shouldShowLegend(chartType, LegendDisplay.DEFAULT);
|
||||
expect(show3).toBeTruthy();
|
||||
});
|
||||
};
|
||||
|
||||
runCommonShouldShowLegendScenario(ChartTypes.PIE);
|
||||
runShouldShowLegendDefaultBucketsScenario(ChartTypes.PIE);
|
||||
|
||||
runCommonShouldShowLegendScenario(ChartTypes.DONUT);
|
||||
runShouldShowLegendDefaultBucketsScenario(ChartTypes.DONUT);
|
||||
|
||||
runCommonShouldShowLegendScenario(ChartTypes.TREEMAP);
|
||||
runShouldShowLegendDefaultAlwaysFalsyScenario(ChartTypes.TREEMAP);
|
||||
|
||||
runCommonShouldShowLegendScenario(ChartTypes.MOSAIC);
|
||||
runShouldShowLegendDefaultAlwaysFalsyScenario(ChartTypes.MOSAIC);
|
||||
|
||||
runCommonShouldShowLegendScenario(ChartTypes.WAFFLE);
|
||||
runShouldShowLegendDefaultAlwaysTruthyScenario(ChartTypes.WAFFLE);
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DatatableColumn } from '../../../../expressions';
|
||||
import { BucketColumns, ChartTypes, LegendDisplay } from '../../common/types';
|
||||
|
||||
type GetLegendIsFlatFn = (splitChartDimension: DatatableColumn | undefined) => boolean;
|
||||
|
||||
const isLegendFlatCommon: GetLegendIsFlatFn = (splitChartDimension) => Boolean(splitChartDimension);
|
||||
|
||||
export const isLegendFlat = (
|
||||
visType: ChartTypes,
|
||||
splitChartDimension: DatatableColumn | undefined
|
||||
) =>
|
||||
({
|
||||
[ChartTypes.PIE]: () => isLegendFlatCommon(splitChartDimension),
|
||||
[ChartTypes.DONUT]: () => isLegendFlatCommon(splitChartDimension),
|
||||
[ChartTypes.TREEMAP]: () => isLegendFlatCommon(splitChartDimension),
|
||||
[ChartTypes.MOSAIC]: () => isLegendFlatCommon(splitChartDimension),
|
||||
[ChartTypes.WAFFLE]: () => true,
|
||||
}[visType]());
|
||||
|
||||
const showIfBuckets = (bucketColumns: Array<Partial<BucketColumns>>) => bucketColumns.length > 1;
|
||||
|
||||
const showLegendDefault = (visType: ChartTypes, bucketColumns: Array<Partial<BucketColumns>>) =>
|
||||
({
|
||||
[ChartTypes.PIE]: () => showIfBuckets(bucketColumns),
|
||||
[ChartTypes.DONUT]: () => showIfBuckets(bucketColumns),
|
||||
[ChartTypes.TREEMAP]: () => false,
|
||||
[ChartTypes.MOSAIC]: () => false,
|
||||
[ChartTypes.WAFFLE]: () => true,
|
||||
}[visType]());
|
||||
|
||||
export const shouldShowLegend = (
|
||||
visType: ChartTypes,
|
||||
legendDisplay: LegendDisplay,
|
||||
bucketColumns: Array<Partial<BucketColumns>> = []
|
||||
) =>
|
||||
legendDisplay === LegendDisplay.SHOW ||
|
||||
(legendDisplay === LegendDisplay.DEFAULT && showLegendDefault(visType, bucketColumns));
|
|
@ -6,10 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExpressionPiePlugin } from './plugin';
|
||||
import { ExpressionPartitionVisPlugin } from './plugin';
|
||||
|
||||
export function plugin() {
|
||||
return new ExpressionPiePlugin();
|
||||
return new ExpressionPartitionVisPlugin();
|
||||
}
|
||||
|
||||
export type { ExpressionPiePluginSetup, ExpressionPiePluginStart } from './types';
|
||||
export type { ExpressionPartitionVisPluginSetup, ExpressionPartitionVisPluginStart } from './types';
|
43
src/plugins/chart_expressions/expression_partition_vis/server/plugin.ts
Executable file
43
src/plugins/chart_expressions/expression_partition_vis/server/plugin.ts
Executable file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { CoreSetup, CoreStart, Plugin } from '../../../../core/server';
|
||||
import {
|
||||
partitionLabelsFunction,
|
||||
pieVisFunction,
|
||||
treemapVisFunction,
|
||||
mosaicVisFunction,
|
||||
waffleVisFunction,
|
||||
} from '../common';
|
||||
import {
|
||||
ExpressionPartitionVisPluginSetup,
|
||||
ExpressionPartitionVisPluginStart,
|
||||
SetupDeps,
|
||||
StartDeps,
|
||||
} from './types';
|
||||
|
||||
export class ExpressionPartitionVisPlugin
|
||||
implements
|
||||
Plugin<
|
||||
ExpressionPartitionVisPluginSetup,
|
||||
ExpressionPartitionVisPluginStart,
|
||||
SetupDeps,
|
||||
StartDeps
|
||||
>
|
||||
{
|
||||
public setup(core: CoreSetup, { expressions }: SetupDeps) {
|
||||
expressions.registerFunction(partitionLabelsFunction);
|
||||
expressions.registerFunction(pieVisFunction);
|
||||
expressions.registerFunction(treemapVisFunction);
|
||||
expressions.registerFunction(mosaicVisFunction);
|
||||
expressions.registerFunction(waffleVisFunction);
|
||||
}
|
||||
|
||||
public start(core: CoreStart, deps: StartDeps) {}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -7,8 +7,8 @@
|
|||
*/
|
||||
import { ExpressionsServerStart, ExpressionsServerSetup } from '../../../expressions/server';
|
||||
|
||||
export type ExpressionPiePluginSetup = void;
|
||||
export type ExpressionPiePluginStart = void;
|
||||
export type ExpressionPartitionVisPluginSetup = void;
|
||||
export type ExpressionPartitionVisPluginStart = void;
|
||||
|
||||
export interface SetupDeps {
|
||||
expressions: ExpressionsServerSetup;
|
|
@ -1,9 +0,0 @@
|
|||
# expressionPie
|
||||
|
||||
Expression Pie plugin adds a `pie` renderer and function to the expression plugin. The renderer will display the `Pie` chart.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment.
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const PLUGIN_ID = 'expressionPie';
|
||||
export const PLUGIN_NAME = 'expressionPie';
|
||||
|
||||
export const PIE_VIS_EXPRESSION_NAME = 'pie_vis';
|
||||
export const PIE_LABELS_VALUE = 'pie_labels_value';
|
||||
export const PIE_LABELS_FUNCTION = 'pie_labels';
|
||||
|
||||
export const DEFAULT_PERCENT_DECIMALS = 2;
|
|
@ -1,98 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`interpreter/functions#pie logs correct datatable to inspector 1`] = `
|
||||
Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"meta": Object {
|
||||
"dimensionName": "Slice size",
|
||||
},
|
||||
"name": "Count",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`interpreter/functions#pie returns an object with the correct structure 1`] = `
|
||||
Object {
|
||||
"as": "pie_vis",
|
||||
"type": "render",
|
||||
"value": Object {
|
||||
"params": Object {
|
||||
"listenOnChange": true,
|
||||
},
|
||||
"syncColors": false,
|
||||
"visConfig": Object {
|
||||
"addLegend": true,
|
||||
"addTooltip": true,
|
||||
"buckets": undefined,
|
||||
"dimensions": Object {
|
||||
"buckets": undefined,
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
},
|
||||
"distinctColors": false,
|
||||
"emptySizeRatio": 0.3,
|
||||
"isDonut": true,
|
||||
"labels": Object {
|
||||
"last_level": false,
|
||||
"percentDecimals": 2,
|
||||
"position": "default",
|
||||
"show": false,
|
||||
"truncate": 100,
|
||||
"type": "pie_labels_value",
|
||||
"values": true,
|
||||
"valuesFormat": "percent",
|
||||
},
|
||||
"legendPosition": "right",
|
||||
"maxLegendLines": 2,
|
||||
"metric": Object {
|
||||
"accessor": 0,
|
||||
"format": Object {
|
||||
"id": "number",
|
||||
"params": Object {},
|
||||
},
|
||||
"type": "vis_dimension",
|
||||
},
|
||||
"nestedLegend": true,
|
||||
"palette": Object {
|
||||
"name": "kibana_palette",
|
||||
"type": "system_palette",
|
||||
},
|
||||
"splitColumn": undefined,
|
||||
"splitRow": undefined,
|
||||
"truncateLegend": true,
|
||||
},
|
||||
"visData": Object {
|
||||
"columns": Array [
|
||||
Object {
|
||||
"id": "col-0-1",
|
||||
"name": "Count",
|
||||
},
|
||||
],
|
||||
"rows": Array [
|
||||
Object {
|
||||
"col-0-1": 0,
|
||||
},
|
||||
],
|
||||
"type": "datatable",
|
||||
},
|
||||
"visType": "pie",
|
||||
},
|
||||
}
|
||||
`;
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ExpressionFunctionDefinition, Datatable } from '../../../../expressions/common';
|
||||
import { PIE_LABELS_FUNCTION, PIE_LABELS_VALUE } from '../constants';
|
||||
import { ExpressionValuePieLabels, PieLabelsArguments } from '../types/expression_functions';
|
||||
|
||||
export const pieLabelsFunction = (): ExpressionFunctionDefinition<
|
||||
typeof PIE_LABELS_FUNCTION,
|
||||
Datatable | null,
|
||||
PieLabelsArguments,
|
||||
ExpressionValuePieLabels
|
||||
> => ({
|
||||
name: PIE_LABELS_FUNCTION,
|
||||
help: i18n.translate('expressionPie.pieLabels.function.help', {
|
||||
defaultMessage: 'Generates the pie labels object',
|
||||
}),
|
||||
type: PIE_LABELS_VALUE,
|
||||
args: {
|
||||
show: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieLabels.function.args.show.help', {
|
||||
defaultMessage: 'Displays the pie labels',
|
||||
}),
|
||||
default: true,
|
||||
},
|
||||
position: {
|
||||
types: ['string'],
|
||||
default: 'default',
|
||||
help: i18n.translate('expressionPie.pieLabels.function.args.position.help', {
|
||||
defaultMessage: 'Defines the label position',
|
||||
}),
|
||||
},
|
||||
values: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieLabels.function.args.values.help', {
|
||||
defaultMessage: 'Displays the values inside the slices',
|
||||
}),
|
||||
default: true,
|
||||
},
|
||||
percentDecimals: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionPie.pieLabels.function.args.percentDecimals.help', {
|
||||
defaultMessage: 'Defines the number of decimals that will appear on the values as percent',
|
||||
}),
|
||||
default: 2,
|
||||
},
|
||||
lastLevel: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieLabels.function.args.lastLevel.help', {
|
||||
defaultMessage: 'Show top level labels only',
|
||||
}),
|
||||
default: true,
|
||||
},
|
||||
truncate: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionPie.pieLabels.function.args.truncate.help', {
|
||||
defaultMessage: 'Defines the number of characters that the slice value will display',
|
||||
}),
|
||||
default: null,
|
||||
},
|
||||
valuesFormat: {
|
||||
types: ['string'],
|
||||
default: 'percent',
|
||||
help: i18n.translate('expressionPie.pieLabels.function.args.valuesFormat.help', {
|
||||
defaultMessage: 'Defines the format of the values',
|
||||
}),
|
||||
},
|
||||
},
|
||||
fn: (context, args) => {
|
||||
return {
|
||||
type: PIE_LABELS_VALUE,
|
||||
show: args.show,
|
||||
position: args.position,
|
||||
percentDecimals: args.percentDecimals,
|
||||
values: args.values,
|
||||
truncate: args.truncate,
|
||||
valuesFormat: args.valuesFormat,
|
||||
last_level: args.lastLevel,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,183 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EmptySizeRatios, PieVisParams } from '../types/expression_renderers';
|
||||
import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table';
|
||||
import { PieVisExpressionFunctionDefinition } from '../types/expression_functions';
|
||||
import { PIE_LABELS_FUNCTION, PIE_LABELS_VALUE, PIE_VIS_EXPRESSION_NAME } from '../constants';
|
||||
|
||||
export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({
|
||||
name: PIE_VIS_EXPRESSION_NAME,
|
||||
type: 'render',
|
||||
inputTypes: ['datatable'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.help', {
|
||||
defaultMessage: 'Pie visualization',
|
||||
}),
|
||||
args: {
|
||||
metric: {
|
||||
types: ['vis_dimension'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.metricHelpText', {
|
||||
defaultMessage: 'Metric dimensions config',
|
||||
}),
|
||||
required: true,
|
||||
},
|
||||
buckets: {
|
||||
types: ['vis_dimension'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.bucketsHelpText', {
|
||||
defaultMessage: 'Buckets dimensions config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
splitColumn: {
|
||||
types: ['vis_dimension'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.splitColumnHelpText', {
|
||||
defaultMessage: 'Split by column dimension config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
splitRow: {
|
||||
types: ['vis_dimension'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.splitRowHelpText', {
|
||||
defaultMessage: 'Split by row dimension config',
|
||||
}),
|
||||
multi: true,
|
||||
},
|
||||
addTooltip: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.addTooltipHelpText', {
|
||||
defaultMessage: 'Show tooltip on slice hover',
|
||||
}),
|
||||
default: true,
|
||||
},
|
||||
addLegend: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.addLegendHelpText', {
|
||||
defaultMessage: 'Show legend chart legend',
|
||||
}),
|
||||
},
|
||||
legendPosition: {
|
||||
types: ['string'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.legendPositionHelpText', {
|
||||
defaultMessage: 'Position the legend on top, bottom, left, right of the chart',
|
||||
}),
|
||||
},
|
||||
nestedLegend: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.nestedLegendHelpText', {
|
||||
defaultMessage: 'Show a more detailed legend',
|
||||
}),
|
||||
default: false,
|
||||
},
|
||||
truncateLegend: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.truncateLegendHelpText', {
|
||||
defaultMessage: 'Defines if the legend items will be truncated or not',
|
||||
}),
|
||||
default: true,
|
||||
},
|
||||
maxLegendLines: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.maxLegendLinesHelpText', {
|
||||
defaultMessage: 'Defines the number of lines per legend item',
|
||||
}),
|
||||
},
|
||||
distinctColors: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.distinctColorsHelpText', {
|
||||
defaultMessage:
|
||||
'Maps different color per slice. Slices with the same value have the same color',
|
||||
}),
|
||||
default: false,
|
||||
},
|
||||
isDonut: {
|
||||
types: ['boolean'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.isDonutHelpText', {
|
||||
defaultMessage: 'Displays the pie chart as donut',
|
||||
}),
|
||||
default: false,
|
||||
},
|
||||
emptySizeRatio: {
|
||||
types: ['number'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.emptySizeRatioHelpText', {
|
||||
defaultMessage: 'Defines donut inner empty area size',
|
||||
}),
|
||||
default: EmptySizeRatios.SMALL,
|
||||
},
|
||||
palette: {
|
||||
types: ['palette', 'system_palette'],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.paletteHelpText', {
|
||||
defaultMessage: 'Defines the chart palette name',
|
||||
}),
|
||||
default: '{palette}',
|
||||
},
|
||||
labels: {
|
||||
types: [PIE_LABELS_VALUE],
|
||||
help: i18n.translate('expressionPie.pieVis.function.args.labelsHelpText', {
|
||||
defaultMessage: 'Pie labels config',
|
||||
}),
|
||||
default: `{${PIE_LABELS_FUNCTION}}`,
|
||||
},
|
||||
},
|
||||
fn(context, args, handlers) {
|
||||
const visConfig: PieVisParams = {
|
||||
...args,
|
||||
palette: args.palette,
|
||||
dimensions: {
|
||||
metric: args.metric,
|
||||
buckets: args.buckets,
|
||||
splitColumn: args.splitColumn,
|
||||
splitRow: args.splitRow,
|
||||
},
|
||||
};
|
||||
|
||||
if (handlers?.inspectorAdapters?.tables) {
|
||||
const logTable = prepareLogTable(context, [
|
||||
[
|
||||
[args.metric],
|
||||
i18n.translate('expressionPie.pieVis.function.dimension.metric', {
|
||||
defaultMessage: 'Slice size',
|
||||
}),
|
||||
],
|
||||
[
|
||||
args.buckets,
|
||||
i18n.translate('expressionPie.pieVis.function.dimension.buckets', {
|
||||
defaultMessage: 'Slice',
|
||||
}),
|
||||
],
|
||||
[
|
||||
args.splitColumn,
|
||||
i18n.translate('expressionPie.pieVis.function.dimension.splitcolumn', {
|
||||
defaultMessage: 'Column split',
|
||||
}),
|
||||
],
|
||||
[
|
||||
args.splitRow,
|
||||
i18n.translate('expressionPie.pieVis.function.dimension.splitrow', {
|
||||
defaultMessage: 'Row split',
|
||||
}),
|
||||
],
|
||||
]);
|
||||
handlers.inspectorAdapters.tables.logDatatable('default', logTable);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: PIE_VIS_EXPRESSION_NAME,
|
||||
value: {
|
||||
visData: context,
|
||||
visConfig,
|
||||
syncColors: handlers?.isSyncColorsEnabled?.() ?? false,
|
||||
visType: 'pie',
|
||||
params: {
|
||||
listenOnChange: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export {
|
||||
PLUGIN_ID,
|
||||
PLUGIN_NAME,
|
||||
PIE_VIS_EXPRESSION_NAME,
|
||||
PIE_LABELS_VALUE,
|
||||
PIE_LABELS_FUNCTION,
|
||||
} from './constants';
|
||||
|
||||
export { pieVisFunction, pieLabelsFunction } from './expression_functions';
|
||||
|
||||
export type {
|
||||
ExpressionValuePieLabels,
|
||||
PieVisExpressionFunctionDefinition,
|
||||
} from './types/expression_functions';
|
||||
|
||||
export type {
|
||||
PieVisParams,
|
||||
PieVisConfig,
|
||||
LabelsParams,
|
||||
Dimension,
|
||||
Dimensions,
|
||||
} from './types/expression_renderers';
|
||||
|
||||
export { ValueFormats, LabelPositions, EmptySizeRatios } from './types/expression_renderers';
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PIE_LABELS_VALUE, PIE_VIS_EXPRESSION_NAME } from '../constants';
|
||||
import {
|
||||
ExpressionFunctionDefinition,
|
||||
Datatable,
|
||||
ExpressionValueRender,
|
||||
ExpressionValueBoxed,
|
||||
} from '../../../../expressions/common';
|
||||
import { RenderValue, PieVisConfig, LabelPositions, ValueFormats } from './expression_renderers';
|
||||
|
||||
export interface PieLabelsArguments {
|
||||
show: boolean;
|
||||
position: LabelPositions;
|
||||
values: boolean;
|
||||
truncate: number | null;
|
||||
valuesFormat: ValueFormats;
|
||||
lastLevel: boolean;
|
||||
percentDecimals: number;
|
||||
}
|
||||
|
||||
export type ExpressionValuePieLabels = ExpressionValueBoxed<
|
||||
typeof PIE_LABELS_VALUE,
|
||||
{
|
||||
show: boolean;
|
||||
position: LabelPositions;
|
||||
values: boolean;
|
||||
truncate: number | null;
|
||||
valuesFormat: ValueFormats;
|
||||
last_level: boolean;
|
||||
percentDecimals: number;
|
||||
}
|
||||
>;
|
||||
|
||||
export type PieVisExpressionFunctionDefinition = ExpressionFunctionDefinition<
|
||||
typeof PIE_VIS_EXPRESSION_NAME,
|
||||
Datatable,
|
||||
PieVisConfig,
|
||||
ExpressionValueRender<RenderValue>
|
||||
>;
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { Datatable } from '../../../../expressions';
|
||||
import { Render } from '../../../../presentation_util/public/__stories__';
|
||||
import { getPieVisRenderer } from '../expression_renderers';
|
||||
import { LabelPositions, RenderValue, ValueFormats } from '../../common/types';
|
||||
import { palettes, theme, getStartDeps } from '../__mocks__';
|
||||
|
||||
const visData: Datatable = {
|
||||
type: 'datatable',
|
||||
columns: [
|
||||
{ id: 'cost', name: 'cost', meta: { type: 'number' } },
|
||||
{ id: 'age', name: 'age', meta: { type: 'number' } },
|
||||
{ id: 'price', name: 'price', meta: { type: 'number' } },
|
||||
{ id: 'project', name: 'project', meta: { type: 'string' } },
|
||||
{ id: '@timestamp', name: '@timestamp', meta: { type: 'date' } },
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
cost: 32.15,
|
||||
age: 63,
|
||||
price: 53,
|
||||
project: 'elasticsearch',
|
||||
'@timestamp': 1546334211208,
|
||||
},
|
||||
{
|
||||
cost: 20.52,
|
||||
age: 68,
|
||||
price: 33,
|
||||
project: 'beats',
|
||||
'@timestamp': 1546351551031,
|
||||
},
|
||||
{
|
||||
cost: 21.15,
|
||||
age: 57,
|
||||
price: 59,
|
||||
project: 'apm',
|
||||
'@timestamp': 1546352631083,
|
||||
},
|
||||
{
|
||||
cost: 35.64,
|
||||
age: 73,
|
||||
price: 71,
|
||||
project: 'machine-learning',
|
||||
'@timestamp': 1546402490956,
|
||||
},
|
||||
{
|
||||
cost: 27.19,
|
||||
age: 38,
|
||||
price: 36,
|
||||
project: 'kibana',
|
||||
'@timestamp': 1546467111351,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config: RenderValue = {
|
||||
visType: 'pie_vis',
|
||||
visData,
|
||||
visConfig: {
|
||||
dimensions: {
|
||||
metric: {
|
||||
type: 'vis_dimension',
|
||||
accessor: { id: 'cost', name: 'cost', meta: { type: 'number' } },
|
||||
format: { id: 'number', params: {} },
|
||||
},
|
||||
buckets: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: { id: 'age', name: 'age', meta: { type: 'number' } },
|
||||
format: { id: 'number', params: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
palette: { type: 'system_palette', name: 'default' },
|
||||
addTooltip: false,
|
||||
addLegend: false,
|
||||
legendPosition: 'right',
|
||||
nestedLegend: false,
|
||||
truncateLegend: false,
|
||||
distinctColors: false,
|
||||
isDonut: false,
|
||||
emptySizeRatio: 0.37,
|
||||
maxLegendLines: 1,
|
||||
labels: {
|
||||
show: false,
|
||||
last_level: false,
|
||||
position: LabelPositions.DEFAULT,
|
||||
values: false,
|
||||
truncate: null,
|
||||
valuesFormat: ValueFormats.VALUE,
|
||||
percentDecimals: 1,
|
||||
},
|
||||
},
|
||||
syncColors: false,
|
||||
};
|
||||
|
||||
const containerSize = {
|
||||
width: '700px',
|
||||
height: '700px',
|
||||
};
|
||||
|
||||
const pieRenderer = getPieVisRenderer({ palettes, theme, getStartDeps });
|
||||
|
||||
storiesOf('renderers/pieVis', module).add('Default', () => {
|
||||
return <Render renderer={() => pieRenderer} config={config} {...containerSize} />;
|
||||
});
|
|
@ -1,201 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Datum, PartitionLayer, ShapeTreeNode, ArrayEntry } from '@elastic/charts';
|
||||
import { isEqual } from 'lodash';
|
||||
import type { FieldFormatsStart } from 'src/plugins/field_formats/public';
|
||||
import { SeriesLayer, PaletteRegistry, lightenColor } from '../../../../charts/public';
|
||||
import type { DatatableRow } from '../../../../expressions/public';
|
||||
import type { BucketColumns, PieVisParams, SplitDimensionParams } from '../../common/types';
|
||||
import { getDistinctSeries } from './get_distinct_series';
|
||||
|
||||
const EMPTY_SLICE = Symbol('empty_slice');
|
||||
|
||||
export const computeColor = (
|
||||
d: ShapeTreeNode,
|
||||
isSplitChart: boolean,
|
||||
overwriteColors: { [key: string]: string } = {},
|
||||
columns: Array<Partial<BucketColumns>>,
|
||||
rows: DatatableRow[],
|
||||
visParams: PieVisParams,
|
||||
palettes: PaletteRegistry | null,
|
||||
syncColors: boolean,
|
||||
formatter: FieldFormatsStart,
|
||||
format?: BucketColumns['format']
|
||||
) => {
|
||||
const { parentSeries, allSeries } = getDistinctSeries(rows, columns);
|
||||
const dataName = d.dataName;
|
||||
|
||||
let formattedLabel = '';
|
||||
if (format) {
|
||||
formattedLabel = formatter.deserialize(format).convert(dataName) ?? '';
|
||||
}
|
||||
|
||||
if (visParams.distinctColors) {
|
||||
let overwriteColor;
|
||||
// this is for supporting old visualizations (created by vislib plugin)
|
||||
// it seems that there for some aggs, the uiState saved from vislib is
|
||||
// different than the es-charts handle it
|
||||
if (overwriteColors.hasOwnProperty(formattedLabel)) {
|
||||
overwriteColor = overwriteColors[formattedLabel];
|
||||
}
|
||||
|
||||
if (Object.keys(overwriteColors).includes(dataName.toString())) {
|
||||
overwriteColor = overwriteColors[dataName];
|
||||
}
|
||||
|
||||
if (overwriteColor) {
|
||||
return overwriteColor;
|
||||
}
|
||||
|
||||
const index = allSeries.findIndex((name) => isEqual(name, dataName));
|
||||
const isSplitParentLayer = isSplitChart && parentSeries.includes(dataName);
|
||||
return palettes?.get(visParams.palette.name).getCategoricalColor(
|
||||
[
|
||||
{
|
||||
name: dataName,
|
||||
rankAtDepth: isSplitParentLayer
|
||||
? parentSeries.findIndex((name) => name === dataName)
|
||||
: index > -1
|
||||
? index
|
||||
: 0,
|
||||
totalSeriesAtDepth: isSplitParentLayer ? parentSeries.length : allSeries.length || 1,
|
||||
},
|
||||
],
|
||||
{
|
||||
maxDepth: 1,
|
||||
totalSeries: allSeries.length || 1,
|
||||
behindText: visParams.labels.show,
|
||||
syncColors,
|
||||
},
|
||||
visParams.palette?.params ?? { colors: [] }
|
||||
);
|
||||
}
|
||||
const seriesLayers: SeriesLayer[] = [];
|
||||
let tempParent: typeof d | typeof d['parent'] = d;
|
||||
while (tempParent.parent && tempParent.depth > 0) {
|
||||
const seriesName = String(tempParent.parent.children[tempParent.sortIndex][0]);
|
||||
const isSplitParentLayer = isSplitChart && parentSeries.includes(seriesName);
|
||||
seriesLayers.unshift({
|
||||
name: seriesName,
|
||||
rankAtDepth: isSplitParentLayer
|
||||
? parentSeries.findIndex((name) => name === seriesName)
|
||||
: tempParent.sortIndex,
|
||||
totalSeriesAtDepth: isSplitParentLayer
|
||||
? parentSeries.length
|
||||
: tempParent.parent.children.length,
|
||||
});
|
||||
tempParent = tempParent.parent;
|
||||
}
|
||||
|
||||
let overwriteColor;
|
||||
// this is for supporting old visualizations (created by vislib plugin)
|
||||
// it seems that there for some aggs, the uiState saved from vislib is
|
||||
// different than the es-charts handle it
|
||||
if (overwriteColors.hasOwnProperty(formattedLabel)) {
|
||||
overwriteColor = overwriteColors[formattedLabel];
|
||||
}
|
||||
|
||||
seriesLayers.forEach((layer) => {
|
||||
if (Object.keys(overwriteColors).includes(layer.name)) {
|
||||
overwriteColor = overwriteColors[layer.name];
|
||||
}
|
||||
});
|
||||
|
||||
if (overwriteColor) {
|
||||
return lightenColor(overwriteColor, seriesLayers.length, columns.length);
|
||||
}
|
||||
return palettes?.get(visParams.palette.name).getCategoricalColor(
|
||||
seriesLayers,
|
||||
{
|
||||
behindText: visParams.labels.show,
|
||||
maxDepth: columns.length,
|
||||
totalSeries: rows.length,
|
||||
syncColors,
|
||||
},
|
||||
visParams.palette?.params ?? { colors: [] }
|
||||
);
|
||||
};
|
||||
|
||||
export const getLayers = (
|
||||
columns: Array<Partial<BucketColumns>>,
|
||||
visParams: PieVisParams,
|
||||
overwriteColors: { [key: string]: string } = {},
|
||||
rows: DatatableRow[],
|
||||
palettes: PaletteRegistry | null,
|
||||
formatter: FieldFormatsStart,
|
||||
syncColors: boolean
|
||||
): PartitionLayer[] => {
|
||||
const fillLabel: PartitionLayer['fillLabel'] = {
|
||||
valueFont: {
|
||||
fontWeight: 700,
|
||||
},
|
||||
};
|
||||
|
||||
if (!visParams.labels.values) {
|
||||
fillLabel.valueFormatter = () => '';
|
||||
}
|
||||
const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow);
|
||||
return columns.map((col) => {
|
||||
return {
|
||||
groupByRollup: (d: Datum) => {
|
||||
return col.id ? d[col.id] : col.name;
|
||||
},
|
||||
showAccessor: (d: Datum) => d !== EMPTY_SLICE,
|
||||
nodeLabel: (d: unknown) => {
|
||||
if (col.format) {
|
||||
return formatter.deserialize(col.format).convert(d) ?? '';
|
||||
}
|
||||
return String(d);
|
||||
},
|
||||
sortPredicate: ([name1, node1]: ArrayEntry, [name2, node2]: ArrayEntry) => {
|
||||
const params = col.meta?.sourceParams?.params as SplitDimensionParams | undefined;
|
||||
const sort: string | undefined = params?.orderBy;
|
||||
// unconditionally put "Other" to the end (as the "Other" slice may be larger than a regular slice, yet should be at the end)
|
||||
if (name1 === '__other__' && name2 !== '__other__') return 1;
|
||||
if (name2 === '__other__' && name1 !== '__other__') return -1;
|
||||
// metric sorting
|
||||
if (sort && sort !== '_key') {
|
||||
if (params?.order === 'desc') {
|
||||
return node2.value - node1.value;
|
||||
} else {
|
||||
return node1.value - node2.value;
|
||||
}
|
||||
// alphabetical sorting
|
||||
} else {
|
||||
if (name1 > name2) {
|
||||
return params?.order === 'desc' ? -1 : 1;
|
||||
}
|
||||
if (name2 > name1) {
|
||||
return params?.order === 'desc' ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
fillLabel,
|
||||
shape: {
|
||||
fillColor: (d) => {
|
||||
const outputColor = computeColor(
|
||||
d,
|
||||
isSplitChart,
|
||||
overwriteColors,
|
||||
columns,
|
||||
rows,
|
||||
visParams,
|
||||
palettes,
|
||||
syncColors,
|
||||
formatter,
|
||||
col.format
|
||||
);
|
||||
|
||||
return outputColor || 'rgba(0,0,0,0)';
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getPartitionTheme } from './get_partition_theme';
|
||||
import { createMockPieParams } from '../mocks';
|
||||
|
||||
const visParams = createMockPieParams();
|
||||
|
||||
describe('getConfig', () => {
|
||||
it('should cap the outerSizeRatio to 1', () => {
|
||||
expect(
|
||||
getPartitionTheme(visParams, {}, { width: 400, height: 400 }).partition?.outerSizeRatio
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it('should not have outerSizeRatio for split chart', () => {
|
||||
expect(
|
||||
getPartitionTheme(
|
||||
{
|
||||
...visParams,
|
||||
dimensions: {
|
||||
...visParams.dimensions,
|
||||
splitColumn: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ width: 400, height: 400 }
|
||||
).partition?.outerSizeRatio
|
||||
).toBeUndefined();
|
||||
|
||||
expect(
|
||||
getPartitionTheme(
|
||||
{
|
||||
...visParams,
|
||||
dimensions: {
|
||||
...visParams.dimensions,
|
||||
splitRow: [
|
||||
{
|
||||
type: 'vis_dimension',
|
||||
accessor: 1,
|
||||
format: {
|
||||
id: 'number',
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{},
|
||||
{ width: 400, height: 400 }
|
||||
).partition?.outerSizeRatio
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not set outerSizeRatio if dimensions are not defined', () => {
|
||||
expect(getPartitionTheme(visParams, {}).partition?.outerSizeRatio).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { PartialTheme } from '@elastic/charts';
|
||||
import { Required } from '@kbn/utility-types';
|
||||
import { LabelPositions, PieVisParams, PieContainerDimensions } from '../../common/types';
|
||||
|
||||
const MAX_SIZE = 1000;
|
||||
|
||||
export const getPartitionTheme = (
|
||||
visParams: PieVisParams,
|
||||
chartTheme: PartialTheme,
|
||||
dimensions?: PieContainerDimensions,
|
||||
rescaleFactor: number = 1
|
||||
): PartialTheme => {
|
||||
// On small multiples we want the labels to only appear inside
|
||||
const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow);
|
||||
const paddingProps: PartialTheme | null =
|
||||
dimensions && !isSplitChart
|
||||
? {
|
||||
chartPaddings: {
|
||||
// TODO: simplify ratio logic to be static px units
|
||||
top: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height,
|
||||
bottom: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height,
|
||||
left: ((1 - Math.min(1, MAX_SIZE / dimensions?.width)) / 2) * dimensions?.height,
|
||||
right: ((1 - Math.min(1, MAX_SIZE / dimensions?.width)) / 2) * dimensions?.height,
|
||||
},
|
||||
}
|
||||
: null;
|
||||
|
||||
const outerSizeRatio: PartialTheme['partition'] | null =
|
||||
dimensions && !isSplitChart
|
||||
? {
|
||||
outerSizeRatio:
|
||||
// Cap the ratio to 1 and then rescale
|
||||
rescaleFactor * Math.min(MAX_SIZE / Math.min(dimensions?.width, dimensions?.height), 1),
|
||||
}
|
||||
: null;
|
||||
const theme: Required<PartialTheme, 'partition'> = {
|
||||
chartMargins: { top: 0, bottom: 0, left: 0, right: 0 },
|
||||
...paddingProps,
|
||||
partition: {
|
||||
fontFamily: chartTheme.barSeriesStyle?.displayValue?.fontFamily,
|
||||
...outerSizeRatio,
|
||||
minFontSize: 10,
|
||||
maxFontSize: 16,
|
||||
linkLabel: {
|
||||
maxCount: 5,
|
||||
fontSize: 11,
|
||||
textColor: chartTheme.axes?.axisTitle?.fill,
|
||||
maxTextLength: visParams.labels.truncate ?? undefined,
|
||||
},
|
||||
sectorLineStroke: chartTheme.lineSeriesStyle?.point?.fill,
|
||||
sectorLineWidth: 1.5,
|
||||
circlePadding: 4,
|
||||
emptySizeRatio: visParams.isDonut ? visParams.emptySizeRatio : 0,
|
||||
},
|
||||
};
|
||||
if (!visParams.labels.show) {
|
||||
// Force all labels to be linked, then prevent links from showing
|
||||
theme.partition.linkLabel = { maxCount: 0, maximumSection: Number.POSITIVE_INFINITY };
|
||||
}
|
||||
|
||||
if (visParams.labels.last_level && visParams.labels.show) {
|
||||
theme.partition.linkLabel = {
|
||||
maxCount: Number.POSITIVE_INFINITY,
|
||||
maximumSection: Number.POSITIVE_INFINITY,
|
||||
maxTextLength: visParams.labels.truncate ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(visParams.labels.position === LabelPositions.INSIDE || isSplitChart) &&
|
||||
visParams.labels.show
|
||||
) {
|
||||
theme.partition.linkLabel = { maxCount: 0 };
|
||||
}
|
||||
|
||||
return theme;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue