From ec75f526c00540fb77b859904cbbd02340b2e621 Mon Sep 17 00:00:00 2001 From: Mrunal Zambre <79525611+mrunal-z@users.noreply.github.com> Date: Thu, 9 Jun 2022 23:52:07 +0000 Subject: [PATCH] [Feature]: Treemap chart support in Event Analytics (#693) * Initial commit for treemap visualization Signed-off-by: Mrunal Zambre * changes to labelField, layout and config Signed-off-by: Mrunal Zambre * reverted changes of layoutConfig Signed-off-by: Mrunal Zambre * Cypress TestCase for TreeMap Signed-off-by: Nidhi Singhai * added new line Signed-off-by: Mrunal Zambre * updated test cases Signed-off-by: Mrunal Zambre * reverted snapshots Signed-off-by: Mrunal Zambre * implemented treemap config options Signed-off-by: Mrunal Zambre * added multicolored theme option Signed-off-by: Mrunal Zambre * updated snapshots Signed-off-by: Mrunal Zambre * Updated test scripts for multicolored section Signed-off-by: Pratibha Pandey * Fixed default selection for treemap Signed-off-by: Mrunal Zambre Co-authored-by: Nidhi Singhai Co-authored-by: Pratibha Pandey --- .../integration/1_event_analytics.spec.js | 157 +++++- .../.cypress/utils/event_constants.js | 25 +- .../common/constants/colors.ts | 168 ++++++ .../common/constants/shared.ts | 2 +- .../__snapshots__/config_panel.test.tsx.snap | 388 ++++++++++++++ .../config_panel/config_panel.tsx | 2 +- .../config_controls/config_chart_options.tsx | 52 +- .../config_color_palette_picker.tsx | 92 ++++ .../config_controls/config_panel_item.tsx | 5 +- .../config_controls/config_value_options.tsx | 4 +- .../config_panes/config_controls/index.ts | 1 + .../__snapshots__/treemap.test.tsx.snap | 504 ++++++++++++++++++ .../charts/__tests__/treemap.test.tsx | 30 ++ .../charts/maps/treemap_type.ts | 70 ++- .../visualizations/charts/maps/treemaps.tsx | 184 +++++-- 15 files changed, 1607 insertions(+), 77 deletions(-) create mode 100644 dashboards-observability/common/constants/colors.ts create mode 100644 dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_color_palette_picker.tsx create mode 100644 dashboards-observability/public/components/visualizations/charts/__tests__/__snapshots__/treemap.test.tsx.snap create mode 100644 dashboards-observability/public/components/visualizations/charts/__tests__/treemap.test.tsx diff --git a/dashboards-observability/.cypress/integration/1_event_analytics.spec.js b/dashboards-observability/.cypress/integration/1_event_analytics.spec.js index d8dbde877..4cfa67727 100644 --- a/dashboards-observability/.cypress/integration/1_event_analytics.spec.js +++ b/dashboards-observability/.cypress/integration/1_event_analytics.spec.js @@ -17,10 +17,34 @@ import { landOnEventHome, landOnEventExplorer, landOnEventVisualizations, - landOnPanels + landOnPanels, + renderTreeMapchart } from '../utils/event_constants'; import { supressResizeObserverIssue } from '../utils/constants'; +const vis_name_sub_string = Math.floor(Math.random() * 100); +const saveVisualizationAndVerify = () => { + cy.get('[data-test-subj="eventExplorer__saveManagementPopover"]').click(); + cy.get('[data-test-subj="eventExplorer__querySaveComboBox"]').click() + cy.get('.euiComboBoxOptionsList__rowWrap .euiFilterSelectItem').eq(0).click(); + cy.get('.euiPopover__panel .euiFormControlLayoutIcons [data-test-subj="comboBoxToggleListButton"]').eq(0).click(); + cy.get('.euiPopover__panel input').eq(1).type(`Test visualization` + vis_name_sub_string); + cy.get('[data-test-subj="eventExplorer__querySaveConfirm"]').click(); + cy.wait(delay); + cy.get('.euiHeaderBreadcrumbs a').eq(1).click(); + cy.get('.euiFlexGroup .euiFormControlLayout__childrenWrapper input').eq(0).type(`Test visualization` + vis_name_sub_string).type('{enter}'); + cy.get('.euiBasicTable .euiTableCellContent button').eq(0).click(); +} +const deleteVisualization = () => { + cy.get('a[href = "#/event_analytics"]').click(); + cy.get('.euiFlexGroup .euiFormControlLayout__childrenWrapper input').eq(0).type(`Test visualization`).type('{enter}'); + cy.get('input[data-test-subj = "checkboxSelectAll"]').click(); + cy.get('.euiButtonContent.euiButtonContent--iconRight.euiButton__content').click(); + cy.get('.euiContextMenuItem .euiContextMenuItem__text').eq(0).click(); + cy.get('input[placeholder = "delete"]').clear().type('delete'); + cy.get('button[data-test-subj = "popoverModal__deleteButton"]').click(); + cy.get('.euiToastHeader').should('exist'); +} describe('Adding sample data and visualization', () => { it('Adds sample flights data for event analytics', () => { cy.visit(`${Cypress.env('opensearchDashboards')}/app/home#/tutorial_directory/sampleData`); @@ -659,4 +683,133 @@ describe('Renders chart and verify Toast message if X-axis and Y-axis values are cy.get('[data-test-subj="eventExplorer__querySaveConfirm"]').click(); cy.get('[data-test-subj="euiToastHeader"]').contains('Invalid value options configuration selected.').should('exist'); }); -}); \ No newline at end of file +}); + +describe('Renders Tree Map', () => { + beforeEach(() => { + landOnEventVisualizations(); + }); + + it('Renders Tree Map', () => { + renderTreeMapchart(); + cy.get('.euiFlexItem.euiFlexItem--flexGrowZero .euiButton__text').eq(2).click(); + cy.get('path.surface').should('have.length', 176); + }); + + it('Renders Tree Map, add value parameters and verify Reset button click is working', () => { + renderTreeMapchart(); + cy.get('.euiFlexItem.euiFlexItem--flexGrowZero .euiButton__text').eq(2).click(); + cy.get('[data-test-subj="visualizeEditorResetButton"]').click(); + cy.get('#configPanel__panelOptions .euiFieldText').should('have.value', ''); + cy.get('.euiFlexItem .euiFormRow [placeholder="Description"]').should('have.value', ''); + cy.get('.euiComboBox__inputWrap.euiComboBox__inputWrap-isClearable').eq(1).should('have.value', ''); + cy.get('.euiComboBox__inputWrap.euiComboBox__inputWrap-isClearable').eq(2).should('have.value', ''); + cy.get('.euiComboBox__inputWrap.euiComboBox__inputWrap-isClearable').eq(3).should('have.value', ''); + }); + + it('Renders Tree Map, Save and Delete Visualization', () => { + renderTreeMapchart(); + cy.get('.euiFlexItem.euiFlexItem--flexGrowZero .euiButton__text').eq(2).click(); + saveVisualizationAndVerify(); + cy.wait(delay * 4); + deleteVisualization(); + }); + + it('Render Tree Map chart and verify color theme under Chart styles options', () =>{ + renderTreeMapchart(); + cy.get('.euiTitle.euiTitle--xxsmall').contains('Color Theme').should('exist'); + cy.get('.euiSuperSelectControl').contains('Default').click(); + cy.get('.euiContextMenuItem__text .euiColorPalettePicker__item').eq(1).contains('Single color').click(); + cy.get('.euiFieldText.euiColorPicker__input.euiFieldText--withIcon').click(); + cy.get('[aria-label="Select #D36086 as the color"]').click(); + cy.get('.euiButton__text').contains('Preview').should('exist').click(); + cy.get('path[style*="rgb(29, 30, 36)"]').eq(0).should('exist'); + cy.get('.euiSuperSelectControl').click(); + cy.get('.euiColorPalettePicker__itemTitle').eq(1).contains('Reds').click(); + cy.get('.euiButton__text').contains('Preview').should('exist').click(); + cy.get('path[style*="rgb(68, 68, 68)"]').eq(0).should('exist'); + }); + + it('Traverse between root and parent node in Tree Map chart', () => { + querySearch(TEST_QUERIES[5].query, TEST_QUERIES[5].dateRangeDOM); + cy.get('[data-test-subj="configPane__vizTypeSelector"] [data-test-subj="comboBoxInput"]').type('Tree Map').type('{enter}'); + cy.get('#configPanel__panelOptions .euiFieldText').click().type('Tree Map'); + cy.get('.euiFlexItem .euiFormRow [placeholder="Description"]').click().type('This is the description for Tree Map'); + cy.get('.euiComboBox__inputWrap.euiComboBox__inputWrap-isClearable').eq(0).click(); + cy.get('.euiFormControlLayoutIcons [data-test-subj ="comboBoxToggleListButton"]').eq(1).click(); + cy.get('.euiComboBoxOption__content').eq(2).click(); + cy.get('.euiFormControlLayoutIcons [data-test-subj ="comboBoxToggleListButton"]').eq(2).click(); + cy.get('.euiComboBoxOption__content').eq(1).click(); + cy.get('.euiFormControlLayoutIcons [data-test-subj ="comboBoxToggleListButton"]').eq(3).click(); + cy.get('.euiComboBoxOption__content').eq(0).click(); + cy.wait(delay); + cy.get('.euiSuperSelectControl').click(); + cy.get('.euiColorPalettePicker__itemTitle').eq(1).contains('Reds').click(); + cy.get('.euiButton__text').contains('Preview').should('exist').click(); + cy.get('.slicetext[data-unformatted="US"]').click({force:true}); + cy.wait(delay); + cy.get('.slicetext[data-unformatted*="Cleveland"]').click({force:true}); + cy.get('text.slicetext').contains('100% of entry').should('exist'); + cy.get('.pathbar.cursor-pointer .slicetext[data-unformatted="US"]').click({force:true}); + cy.wait(delay); + cy.get('.pathbar.cursor-pointer .slicetext[data-unformatted=" "]').click({force:true}); + }); + + it('"No results found" message when user fails to select proper fields', () =>{ + querySearch(TEST_QUERIES[5].query, TEST_QUERIES[5].dateRangeDOM); + cy.get('[data-test-subj="configPane__vizTypeSelector"] [data-test-subj="comboBoxInput"]').type('Tree Map').type('{enter}'); + cy.get('#configPanel__panelOptions .euiFieldText').click().type('Tree Map'); + cy.get('.euiFlexItem .euiFormRow [placeholder="Description"]').click().type('This is the description for Tree Map'); + cy.get('.euiComboBox__inputWrap.euiComboBox__inputWrap-isClearable').eq(0).click(); + cy.get('.euiFormControlLayoutIcons [data-test-subj ="comboBoxToggleListButton"]').eq(3).click(); + cy.get('.euiComboBoxOption__content').eq(1).click(); + cy.wait(delay); + cy.get('.euiSuperSelectControl').click(); + cy.get('.euiColorPalettePicker__itemTitle').eq(1).contains('Reds').click(); + cy.get('.euiButton__text').contains('Preview').should('exist').click(); + cy.get('.euiTextColor.euiTextColor--subdued').contains('No results found').should('exist'); + }); + + it('Verify multicolored option under color theme',() =>{ + renderTreeMapchart(); + cy.get('.euiTitle.euiTitle--xxsmall').contains('Color Theme').should('exist'); + cy.get('.euiSuperSelectControl').contains('Default').click(); + cy.get('.euiContextMenuItem__text .euiColorPalettePicker__item').eq(1).contains('Single color').click(); + cy.get('.euiFieldText.euiColorPicker__input.euiFieldText--withIcon').click(); + cy.get('[aria-label="Select #54B399 as the color"]').should('exist').click(); + cy.get('.euiButton__text').contains('Preview').click(); + cy.get('.euiSuperSelectControl').click(); + cy.get('.euiContextMenuItem__text .euiColorPalettePicker__item').eq(2).contains('Multicolored').click(); + cy.wait(delay); + cy.get('.euiFormHelpText.euiFormRow__text').eq(1).contains('Child field').should('exist'); + cy.get('.euiFieldText.euiColorPicker__input.euiFieldText--withIcon').eq(0).click(); + cy.get('[aria-label="Select #D36086 as the color"]').click(); + cy.get('.euiFormHelpText.euiFormRow__text').eq(2).contains('Parent field').should('exist'); + cy.get('.euiFieldText.euiColorPicker__input.euiFieldText--withIcon').eq(1).click(); + cy.get('[aria-label="Select #CA8EAE as the color"]').click(); + cy.get('.euiButton__text').contains('Preview').click(); + cy.get('.trace.treemap path[style*="rgb(202, 142, 174)"]').should('exist'); + }); + + it('Parent field not available under color theme', () => { + querySearch(TEST_QUERIES[5].query, TEST_QUERIES[5].dateRangeDOM); + cy.get('[data-test-subj="configPane__vizTypeSelector"] [data-test-subj="comboBoxInput"]').type('Tree Map').type('{enter}'); + cy.get('#configPanel__panelOptions .euiFieldText').click().type('Tree Map'); + cy.get('.euiFlexItem .euiFormRow [placeholder="Description"]').click().type('This is the description for Tree Map'); + cy.get('.euiTitle.euiTitle--xxsmall').contains('Color Theme').should('exist'); + cy.get('.euiSuperSelectControl').contains('Default').click(); + cy.get('.euiContextMenuItem__text .euiColorPalettePicker__item').eq(1).contains('Single color').click(); + cy.get('.euiFieldText.euiColorPicker__input.euiFieldText--withIcon').click(); + cy.get('[aria-label="Select #54B399 as the color"]').should('exist').click(); + cy.get('.euiButton__text').contains('Preview').click(); + cy.get('.euiSuperSelectControl').click(); + cy.get('.euiContextMenuItem__text .euiColorPalettePicker__item').eq(2).contains('Multicolored').click(); + cy.wait(delay); + cy.get('.euiFormHelpText.euiFormRow__text').eq(1).contains('Child field').should('exist'); + cy.get('.euiFieldText.euiColorPicker__input.euiFieldText--withIcon').eq(0).click(); + cy.get('[aria-label="Select #D36086 as the color"]').click(); + cy.get('.euiFormHelpText.euiFormRow__text').contains('Parent field').should('not.exist'); + cy.get('.euiButton__text').contains('Preview').click(); + cy.get('.trace.treemap path[style*="rgb(211, 96, 134)"]').should('exist'); + }); +}); diff --git a/dashboards-observability/.cypress/utils/event_constants.js b/dashboards-observability/.cypress/utils/event_constants.js index 130d32590..0155a5d19 100644 --- a/dashboards-observability/.cypress/utils/event_constants.js +++ b/dashboards-observability/.cypress/utils/event_constants.js @@ -21,13 +21,17 @@ export const TEST_QUERIES = [ query: 'source = opensearch_dashboards_sample_data_logs' }, { - query: 'source = opensearch_dashboards_sample_data_logs | stats count() by host', + query: 'source=opensearch_dashboards_sample_data_flights | stats max(AvgTicketPrice) by DestCountry, DestCityName, Carrier', dateRangeDOM: YEAR_TO_DATE_DOM_ID }, { query: 'source = opensearch_dashboards_sample_data_logs | stats count(), avg(bytes) by host, tags', dateRangeDOM: YEAR_TO_DATE_DOM_ID }, + { + query: 'source=opensearch_dashboards_sample_data_flights | stats avg(FlightDelayMin) by DestCountry, DestCityName', + dateRangeDOM: YEAR_TO_DATE_DOM_ID +}, ]; export const TESTING_PANEL = 'Mock Testing Panels'; @@ -70,4 +74,21 @@ export const landOnPanels = () => { `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/operational_panels` ); cy.wait(delay); -}; \ No newline at end of file +}; + +export const renderTreeMapchart = () => { + querySearch(TEST_QUERIES[5].query, TEST_QUERIES[5].dateRangeDOM); + cy.get('[data-test-subj="configPane__vizTypeSelector"] [data-test-subj="comboBoxInput"]').type('Tree Map').type('{enter}'); + cy.get('#configPanel__panelOptions .euiFieldText').click().type('Tree Map'); + cy.get('.euiFlexItem .euiFormRow [placeholder="Description"]').click().type('This is the description for Tree Map'); + cy.get('.euiFormControlLayoutIcons [data-test-subj ="comboBoxToggleListButton"]').eq(1).click(); + cy.get('.euiComboBoxOption__content').eq(2).click(); + cy.get('.euiFormControlLayoutIcons [data-test-subj ="comboBoxToggleListButton"]').eq(2).click(); + cy.get('.euiComboBoxOption__content').eq(1).click(); + cy.get('.euiFormControlLayoutIcons [data-test-subj ="comboBoxToggleListButton"]').eq(3).click(); + cy.get('.euiComboBoxOption__content').eq(0).click(); + cy.get('.euiIEFlexWrapFix').eq(2).contains('Treemap').should('exist'); + cy.get('#configPanel__treemap_options').contains('Tiling Algorithm').should('exist'); + cy.get('[data-test-subj = "comboBoxInput"]').eq(4).click(); + cy.get('button[name="Slice Dice"]').click(); +}; diff --git a/dashboards-observability/common/constants/colors.ts b/dashboards-observability/common/constants/colors.ts new file mode 100644 index 000000000..5f9304ff2 --- /dev/null +++ b/dashboards-observability/common/constants/colors.ts @@ -0,0 +1,168 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { colorPalette } from '@elastic/eui'; + +export const BLUES_PALETTE = { + name: 'Blues', + label: 'Blues', + colors: [ + 'rgb(5,10,172)', + 'rgb(40,60,190)', + 'rgb(70,100,245)', + 'rgb(90,120,245)', + 'rgb(106,137,247)', + 'rgb(220,220,220)', + ], +}; + +export const REDS_PALETTE = { + name: 'Reds', + label: 'Reds', + colors: ['rgb(220,220,220)', 'rgb(245,195,157)', 'rgb(245,160,105)', 'rgb(178,10,28)'], +}; + +export const GREENS_PALETTE = { + name: 'Greens', + label: 'Greens', + colors: [ + 'rgb(0,68,27)', + 'rgb(0,109,44)', + 'rgb(35,139,69)', + 'rgb(65,171,93)', + 'rgb(116,196,118)', + 'rgb(161,217,155)', + 'rgb(199,233,192)', + 'rgb(229,245,224)', + 'rgb(247,252,245)', + ], +}; + +export const GREYS_PALETTE = { + name: 'Greys', + label: 'Greys', + colors: ['rgb(0,0,0)', 'rgb(255,255,255)'], +}; + +export const BLUE_RED_PALETTE = { + name: 'Bluered', + label: 'Blue-Red', + colors: ['rgb(0,0,255)', 'rgb(255,0,0)'], +}; + +export const RED_BLUE_PALETTE = { + name: 'RdBu', + label: 'Red-Blue', + colors: [ + 'rgb(5,10,172)', + 'rgb(106,137,247)', + 'rgb(190,190,190)', + 'rgb(220,170,132)', + 'rgb(230,145,90)', + 'rgb(178,10,28)', + ], +}; + +export const YELLOW_ORANGE_RED_PALETTE = { + name: 'YlOrRd', + label: 'Yellow-Orange-Red', + colors: [ + 'rgb(128,0,38)', + 'rgb(189,0,38)', + 'rgb(227,26,28)', + 'rgb(252,78,42)', + 'rgb(253,141,60)', + 'rgb(254,178,76)', + 'rgb(254,217,118)', + 'rgb(255,237,160)', + 'rgb(255,255,204)', + ], +}; + +export const YELLOW_GREEN_BLUE_PALETTE = { + name: 'YlGnBu', + label: 'Yellow-Green-Blue', + colors: [ + 'rgb(8,29,88)', + 'rgb(37,52,148)', + 'rgb(34,94,168)', + 'rgb(29,145,192)', + 'rgb(65,182,196)', + 'rgb(127,205,187)', + 'rgb(199,233,180)', + 'rgb(237,248,217)', + 'rgb(255,255,217)', + ], +}; + +export const DEFAULT_PALETTE = 'default'; +export const SINGLE_COLOR_PALETTE = 'singleColor'; +export const MULTI_COLOR_PALETTE = 'multicolor'; + +export const COLOR_PALETTES = [ + { + value: DEFAULT_PALETTE, + title: 'Default', + type: 'text', + }, + { + value: SINGLE_COLOR_PALETTE, + title: 'Single color', + type: 'text', + }, + { + value: MULTI_COLOR_PALETTE, + title: 'Multicolored', + type: 'text', + }, + { + value: BLUES_PALETTE.name, + title: BLUES_PALETTE.label, + palette: colorPalette(BLUES_PALETTE.colors, 20), + type: 'gradient', + }, + { + value: REDS_PALETTE.name, + title: REDS_PALETTE.label, + palette: colorPalette(REDS_PALETTE.colors, 20), + type: 'gradient', + }, + { + value: GREENS_PALETTE.name, + title: GREENS_PALETTE.label, + palette: colorPalette(GREENS_PALETTE.colors, 20), + type: 'gradient', + }, + { + value: GREYS_PALETTE.name, + title: GREYS_PALETTE.label, + palette: colorPalette(GREYS_PALETTE.colors, 20), + type: 'gradient', + }, + { + value: BLUE_RED_PALETTE.name, + title: BLUE_RED_PALETTE.label, + palette: colorPalette(BLUE_RED_PALETTE.colors, 20), + type: 'gradient', + }, + { + value: RED_BLUE_PALETTE.name, + title: RED_BLUE_PALETTE.label, + palette: colorPalette(RED_BLUE_PALETTE.colors, 20, true), + type: 'gradient', + }, + { + value: YELLOW_ORANGE_RED_PALETTE.name, + title: YELLOW_ORANGE_RED_PALETTE.label, + palette: colorPalette(YELLOW_ORANGE_RED_PALETTE.colors, 20), + type: 'gradient', + }, + { + value: YELLOW_GREEN_BLUE_PALETTE.name, + title: YELLOW_GREEN_BLUE_PALETTE.label, + palette: colorPalette(YELLOW_GREEN_BLUE_PALETTE.colors, 20), + type: 'gradient', + }, +]; diff --git a/dashboards-observability/common/constants/shared.ts b/dashboards-observability/common/constants/shared.ts index c27d2bab1..a051d4424 100644 --- a/dashboards-observability/common/constants/shared.ts +++ b/dashboards-observability/common/constants/shared.ts @@ -97,7 +97,7 @@ export interface ValueOptionsAxes { export const NUMERICAL_FIELDS = ['short', 'integer', 'long', 'float', 'double']; -export const ENABLED_VIS_TYPES = [visChartTypes.Bar, visChartTypes.HorizontalBar, visChartTypes.Line, visChartTypes.Pie, visChartTypes.HeatMap, visChartTypes.Text]; +export const ENABLED_VIS_TYPES = [visChartTypes.Bar, visChartTypes.HorizontalBar, visChartTypes.Line, visChartTypes.Pie, visChartTypes.HeatMap, visChartTypes.Text, visChartTypes.TreeMap]; //Live tail constants export const LIVE_OPTIONS = [ diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap index babb55222..a6d447094 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap @@ -960,6 +960,394 @@ exports[`Config panel component Renders config panel with visualization data 1`] }, }, }, + Object { + "category": "Visualizations", + "categoryAxis": "xaxis", + "component": [Function], + "editorConfig": Object { + "panelTabs": Array [ + Object { + "editor": [Function], + "id": "data-panel", + "mapTo": "dataConfig", + "name": "Data", + "sections": Array [ + Object { + "editor": [Function], + "id": "value_options", + "mapTo": "valueOptions", + "name": "Value options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "childField", + "name": "Child Field", + }, + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "parentField", + "name": "Parent Field", + "props": Object { + "isInvalid": false, + }, + }, + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "valueField", + "name": "Value Field", + }, + ], + }, + Object { + "editor": [Function], + "id": "treemap_options", + "mapTo": "treemapOptions", + "name": "Treemap", + "schemas": Array [ + Object { + "component": null, + "defaultState": Array [ + Object { + "label": "Squarify", + "name": "Squarify", + "value": "squarify", + }, + ], + "isSingleSelection": true, + "mapTo": "tilingAlgorithm", + "name": "Tiling Algorithm", + "options": Array [ + Object { + "name": "Squarify", + "value": "squarify", + }, + Object { + "name": "Binary", + "value": "binary", + }, + Object { + "name": "Dice", + "value": "dice", + }, + Object { + "name": "Slice", + "value": "slice", + }, + Object { + "name": "Slice Dice", + "value": "slice-dice", + }, + Object { + "name": "Dice Slice", + "value": "dice-slice", + }, + ], + "props": Object { + "isClearable": false, + }, + }, + ], + }, + Object { + "editor": [Function], + "id": "chart_styles", + "mapTo": "chartStyles", + "name": "Chart Styles", + "schemas": Array [ + Object { + "component": [Function], + "defaultState": Object { + "name": "default", + }, + "eleType": "treemapColorPicker", + "isSingleSelection": true, + "mapTo": "colorTheme", + "name": "Color Theme", + "options": Array [ + Object { + "title": "Default", + "type": "text", + "value": "default", + }, + Object { + "title": "Single color", + "type": "text", + "value": "singleColor", + }, + Object { + "title": "Multicolored", + "type": "text", + "value": "multicolor", + }, + Object { + "palette": Array [ + "#050aac", + "#161cb2", + "#2029ba", + "#2835c2", + "#303fca", + "#384ad3", + "#4054db", + "#495ee2", + "#5368e9", + "#5f72ed", + "#6b7df0", + "#7887f0", + "#8692f0", + "#939cee", + "#a0a6ec", + "#adb1ea", + "#b9bce7", + "#c5c6e3", + "#d1d1e0", + "#dcdcdc", + ], + "title": "Blues", + "type": "gradient", + "value": "Blues", + }, + Object { + "palette": Array [ + "#dcdcdc", + "#e8d0c1", + "#edc6ad", + "#efbb9c", + "#efb18e", + "#eea782", + "#eb9e77", + "#e9946d", + "#e68b64", + "#e2815c", + "#de7854", + "#da6e4c", + "#d56545", + "#d15b3e", + "#cc5138", + "#c74632", + "#c23b2c", + "#bd2f26", + "#b72021", + "#b20a1c", + ], + "title": "Reds", + "type": "gradient", + "value": "Reds", + }, + Object { + "palette": Array [ + "#00441b", + "#014e1f", + "#035825", + "#09622a", + "#116d31", + "#1b7738", + "#25823f", + "#308c48", + "#3c9650", + "#49a15a", + "#57ab64", + "#66b56f", + "#76bf7b", + "#86c888", + "#98d296", + "#aadba6", + "#bde4b8", + "#d0ecca", + "#e3f4df", + "#f7fcf5", + ], + "title": "Greens", + "type": "gradient", + "value": "Greens", + }, + Object { + "palette": Array [ + "#000000", + "#121212", + "#1d1d1d", + "#272727", + "#333333", + "#3e3e3e", + "#4a4a4a", + "#575757", + "#636363", + "#707070", + "#7e7e7e", + "#8b8b8b", + "#999999", + "#a7a7a7", + "#b5b5b5", + "#c3c3c3", + "#d2d2d2", + "#e1e1e1", + "#f0f0f0", + "#ffffff", + ], + "title": "Greys", + "type": "gradient", + "value": "Greys", + }, + Object { + "palette": Array [ + "#0000ff", + "#3e00f7", + "#5800ee", + "#6c00e4", + "#7c00db", + "#8a00d1", + "#9600c6", + "#a200bb", + "#ac00b0", + "#b600a4", + "#bf0098", + "#c8008b", + "#d0007e", + "#d80071", + "#df0062", + "#e60054", + "#ed0045", + "#f30034", + "#f90021", + "#ff0000", + ], + "title": "Blue-Red", + "type": "gradient", + "value": "Bluered", + }, + Object { + "palette": Array [ + "#050aac", + "#2522b6", + "#3835c0", + "#4946c8", + "#5957cf", + "#6967d4", + "#7978d7", + "#8a89d7", + "#9a9bd4", + "#acaccd", + "#df9b70", + "#dd8e62", + "#da8056", + "#d6734c", + "#d16542", + "#cb583a", + "#c54931", + "#bf3a2a", + "#b92723", + "#b20a1c", + ], + "title": "Red-Blue", + "type": "gradient", + "value": "RdBu", + }, + Object { + "palette": Array [ + "#800026", + "#910126", + "#a10526", + "#b10e26", + "#c01b26", + "#cd2926", + "#d83728", + "#e2472a", + "#ea572e", + "#f16732", + "#f67738", + "#fa8740", + "#fd9849", + "#ffa753", + "#ffb760", + "#ffc76f", + "#ffd681", + "#ffe496", + "#fff2af", + "#ffffcc", + ], + "title": "Yellow-Orange-Red", + "type": "gradient", + "value": "YlOrRd", + }, + Object { + "palette": Array [ + "#081d58", + "#11256a", + "#182f79", + "#1e3b86", + "#234791", + "#28539a", + "#2e60a2", + "#346da9", + "#3b7aaf", + "#4488b4", + "#4f95b7", + "#5ba2ba", + "#6aafbc", + "#7bbcbe", + "#8dc8bf", + "#a2d4c1", + "#b7e0c4", + "#ceebc9", + "#e6f5d0", + "#ffffd9", + ], + "title": "Yellow-Green-Blue", + "type": "gradient", + "value": "YlGnBu", + }, + ], + }, + ], + }, + ], + }, + Object { + "content": Array [], + "editor": [Function], + "id": "style-panel", + "mapTo": "layoutConfig", + "name": "Layout", + }, + ], + }, + "fullLabel": "Tree Map", + "icon": [Function], + "iconType": "heatmap", + "id": "tree_map", + "label": "Tree Map", + "name": "tree_map", + "orientation": "v", + "selection": Object { + "dataLoss": "nothing", + }, + "seriesAxis": "yaxis", + "type": "tree_map", + "visConfig": Object { + "config": Object { + "displaylogo": false, + "responsive": true, + }, + "isUniColor": false, + "layout": Object { + "height": 500, + "legend": Object { + "orientation": "v", + "traceorder": "normal", + }, + "margin": Object { + "b": 30, + "l": 60, + "pad": 0, + "r": 30, + "t": 50, + }, + "showlegend": true, + }, + }, + }, ] } placeholder="Select a chart" diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx index d1e8d25a8..c0e9f83e1 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx @@ -92,7 +92,7 @@ export const ConfigPanel = ({ visualizations, setCurVisId, callback, changeIsVal useEffect(() => { setVizConfigs({ ...userConfigs, - dataConfig: { ...vizConfigs.dataConfig, ...(userConfigs?.dataConfig ? userConfigs.dataConfig : getDefaultAxisSelected()) }, + dataConfig: { ...(userConfigs?.dataConfig ? userConfigs.dataConfig : getDefaultAxisSelected()) }, layoutConfig: userConfigs?.layoutConfig ? hjson.stringify({ ...userConfigs.layoutConfig }, HJSON_STRINGIFY_OPTIONS) : getDefaultSpec(), diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_chart_options.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_chart_options.tsx index 2f946ccb2..668a0302c 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_chart_options.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_chart_options.tsx @@ -4,7 +4,7 @@ */ import React, { useMemo, useCallback } from 'react'; -import { EuiAccordion, EuiSpacer } from '@elastic/eui'; +import { EuiAccordion, EuiSpacer, EuiForm } from '@elastic/eui'; import { PanelItem } from './config_panel_item'; export const ConfigChartOptions = ({ @@ -15,6 +15,8 @@ export const ConfigChartOptions = ({ }: any) => { const { data } = visualizations; const { data: vizData = {}, metadata: { fields = [] } = {} } = data?.rawVizData; + const { dataConfig = {}, layoutConfig = {} } = visualizations?.data?.userConfigs; + const handleConfigurationChange = useCallback( (stateFiledName) => { return (changes) => { @@ -29,30 +31,50 @@ export const ConfigChartOptions = ({ const dimensions = useMemo(() => { return schemas.map((schema, index) => { + let params = {}; const DimensionComponent = schema.component || PanelItem; - const params = { - paddingTitle: schema.name, - advancedTitle: 'advancedTitle', - dropdownList: schema?.options?.map((option) => ({ name: option })) || fields, - onSelectChange: handleConfigurationChange(schema.mapTo), - isSingleSelection: schema.isSingleSelection, - selectedAxis: vizState[schema.mapTo], - ...schema.props, - }; + if (schema.eleType === 'treemapColorPicker') { + params = { + title: schema.name, + selectedColor: vizState[schema.mapTo] || schema?.defaultState, + colorPalettes: schema.options || [], + showParentColorPicker: + dataConfig?.valueOptions?.parentField !== undefined && + dataConfig?.valueOptions?.parentField.length > 0, + onSelectChange: handleConfigurationChange(schema.mapTo), + vizState, + ...schema.props, + }; + } else { + params = { + paddingTitle: schema.name, + advancedTitle: 'advancedTitle', + dropdownList: + schema?.options?.map((option) => ({ ...option })) || + fields.map((item) => ({ ...item })), + onSelectChange: handleConfigurationChange(schema.mapTo), + isSingleSelection: schema.isSingleSelection, + selectedAxis: vizState[schema.mapTo] || schema.defaultState, + vizState, + ...schema.props, + }; + } return ( <> - - + + + + ); }); - }, [schemas, fields, vizState, handleConfigurationChange]); + }, [vizState, handleConfigurationChange]); return ( {dimensions} diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_color_palette_picker.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_color_palette_picker.tsx new file mode 100644 index 000000000..2db11b3b3 --- /dev/null +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_color_palette_picker.tsx @@ -0,0 +1,92 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { + EuiTitle, + EuiSpacer, + EuiFlexItem, + EuiFlexGroup, + EuiColorPalettePicker, + EuiColorPicker, + EuiFormRow, +} from '@elastic/eui'; +import { + DEFAULT_PALETTE, + MULTI_COLOR_PALETTE, + SINGLE_COLOR_PALETTE, +} from '../../../../../../../../common/constants/colors'; + +export const ColorPalettePicker = ({ + title, + selectedColor, + showParentColorPicker, + colorPalettes, + onSelectChange, +}: any) => { + const getColorObject = ( + name: string = DEFAULT_PALETTE, + childColor?: string, + parentColor?: string + ) => ({ + name, + childColor: childColor ?? name, + parentColor: parentColor ?? name, + }); + + const [childColor, setChildColor] = useState('#000000'); + const [parentColor, setParentColor] = useState('#000000'); + + const onPaletteChange = (value: string) => { + if (value === SINGLE_COLOR_PALETTE) + onSelectChange(getColorObject(SINGLE_COLOR_PALETTE, childColor)); + else if (value === MULTI_COLOR_PALETTE) + onSelectChange(getColorObject(MULTI_COLOR_PALETTE, childColor, parentColor)); + else onSelectChange(getColorObject(value)); + }; + + const onChildColorChange = (value: string) => { + setChildColor(value); + onSelectChange(getColorObject(selectedColor.name, value, parentColor)); + }; + + const onParentColorChange = (value: string) => { + setParentColor(value); + onSelectChange(getColorObject(selectedColor.name, childColor, value)); + }; + + return ( + <> + +

{title}

+
+ + + {[SINGLE_COLOR_PALETTE, MULTI_COLOR_PALETTE].includes(selectedColor.name) && ( + + + + + + )} + {selectedColor.name === MULTI_COLOR_PALETTE && showParentColorPicker && ( + + + + + + )} + + + + + + ); +}; diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_panel_item.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_panel_item.tsx index 681096a63..0ab6fc7f2 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_panel_item.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_panel_item.tsx @@ -12,7 +12,9 @@ export const PanelItem = ({ selectedAxis, dropdownList, onSelectChange, + isInvalid, isSingleSelection = false, + isClearable = true, }: any) => { const options = dropdownList.map((item) => { return { @@ -32,7 +34,8 @@ export const PanelItem = ({ placeholder="Select a field" options={options} selectedOptions={selectedAxis} - isInvalid={isEmpty(selectedAxis)} + isInvalid={isInvalid ?? isEmpty(selectedAxis)} + isClearable={isClearable} singleSelection={isSingleSelection} onChange={onSelectChange} aria-label="Use aria labels when no actual label is in use" diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_value_options.tsx b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_value_options.tsx index 7f0c75de5..d86467f85 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_value_options.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_value_options.tsx @@ -36,11 +36,11 @@ export const ConfigValueOptions = ({ paddingTitle: schema.name, advancedTitle: 'advancedTitle', dropdownList: - schema?.options?.map((option) => ({ name: option })) || + schema?.options?.map((option) => ({ ...option })) || fields.map((item) => ({ ...item })), onSelectChange: handleConfigurationChange(schema.mapTo), isSingleSelection: schema.isSingleSelection, - selectedAxis: vizState[schema.mapTo], + selectedAxis: vizState[schema.mapTo] || schema?.defaultState, vizState, ...schema.props, }; diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/index.ts b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/index.ts index f75c9126b..ead2f05d5 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/index.ts +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/index.ts @@ -10,3 +10,4 @@ export { ConfigDataLinks } from './config_data_links'; export { ConfigThresholds } from './config_thresholds'; export { ConfigText } from './config_text'; export { ConfigGaugeValueOptions } from './config_gauge_options'; +export { ColorPalettePicker } from './config_color_palette_picker'; diff --git a/dashboards-observability/public/components/visualizations/charts/__tests__/__snapshots__/treemap.test.tsx.snap b/dashboards-observability/public/components/visualizations/charts/__tests__/__snapshots__/treemap.test.tsx.snap new file mode 100644 index 000000000..2f8495c0d --- /dev/null +++ b/dashboards-observability/public/components/visualizations/charts/__tests__/__snapshots__/treemap.test.tsx.snap @@ -0,0 +1,504 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Treemap component Renders treemap component 1`] = ` + + + +
+ + + +`; diff --git a/dashboards-observability/public/components/visualizations/charts/__tests__/treemap.test.tsx b/dashboards-observability/public/components/visualizations/charts/__tests__/treemap.test.tsx new file mode 100644 index 000000000..5210bd416 --- /dev/null +++ b/dashboards-observability/public/components/visualizations/charts/__tests__/treemap.test.tsx @@ -0,0 +1,30 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import React from 'react'; +import { waitFor } from '@testing-library/react'; +import { TreeMap } from '../maps/treemaps'; +import { + LAYOUT_CONFIG, + TEST_VISUALIZATIONS_DATA, +} from '../../../../../test/event_analytics_constants'; + +describe('Treemap component', () => { + configure({ adapter: new Adapter() }); + + it('Renders treemap component', async () => { + const wrapper = mount( + + ); + + wrapper.update(); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/dashboards-observability/public/components/visualizations/charts/maps/treemap_type.ts b/dashboards-observability/public/components/visualizations/charts/maps/treemap_type.ts index 74330f342..5eefda629 100644 --- a/dashboards-observability/public/components/visualizations/charts/maps/treemap_type.ts +++ b/dashboards-observability/public/components/visualizations/charts/maps/treemap_type.ts @@ -8,7 +8,12 @@ import { getPlotlySharedConfigs, getPlotlyCategory } from '../shared/shared_conf import { LensIconChartBar } from '../../assets/chart_bar'; import { VizDataPanel } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/default_vis_editor'; import { ConfigEditor } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/json_editor'; -import { ConfigValueOptions } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { + ConfigValueOptions, + ColorPalettePicker, + ConfigChartOptions, +} from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; +import { DEFAULT_PALETTE, COLOR_PALETTES } from '../../../../../common/constants/colors'; const sharedConfigs = getPlotlySharedConfigs(); const VIS_CATEGORY = getPlotlyCategory(); @@ -25,6 +30,7 @@ export const createTreeMapDefinition = (params: BarTypeParams = {}) => ({ dataLoss: 'nothing', }, category: VIS_CATEGORY.BASICS, + iconType: 'heatmap', icon: LensIconChartBar, categoryAxis: 'xaxis', seriesAxis: 'yaxis', @@ -45,16 +51,68 @@ export const createTreeMapDefinition = (params: BarTypeParams = {}) => ({ mapTo: 'valueOptions', schemas: [ { - name: 'X-axis', + name: 'Child Field', isSingleSelection: true, component: null, - mapTo: 'xaxis', + mapTo: 'childField', }, { - name: 'Y-axis', - isSingleSelection: false, + name: 'Parent Field', + isSingleSelection: true, + component: null, + mapTo: 'parentField', + props: { + isInvalid: false, + } + }, + { + name: 'Value Field', + isSingleSelection: true, component: null, - mapTo: 'yaxis', + mapTo: 'valueField', + }, + ], + }, + { + id: 'treemap_options', + name: 'Treemap', + editor: ConfigValueOptions, + mapTo: 'treemapOptions', + schemas: [ + { + name: 'Tiling Algorithm', + isSingleSelection: true, + component: null, + mapTo: 'tilingAlgorithm', + options: [ + { name: 'Squarify', value: 'squarify' }, + { name: 'Binary', value: 'binary' }, + { name: 'Dice', value: 'dice' }, + { name: 'Slice', value: 'slice' }, + { name: 'Slice Dice', value: 'slice-dice' }, + { name: 'Dice Slice', value: 'dice-slice' }, + ], + defaultState: [{ name: 'Squarify', label: 'Squarify', value: 'squarify' }], + props: { + isClearable: false, + } + }, + ], + }, + { + id: 'chart_styles', + name: 'Chart Styles', + editor: ConfigChartOptions, + mapTo: 'chartStyles', + schemas: [ + { + name: 'Color Theme', + isSingleSelection: true, + component: ColorPalettePicker, + mapTo: 'colorTheme', + eleType: 'treemapColorPicker', + options: COLOR_PALETTES, + defaultState: { name: DEFAULT_PALETTE }, }, ], }, diff --git a/dashboards-observability/public/components/visualizations/charts/maps/treemaps.tsx b/dashboards-observability/public/components/visualizations/charts/maps/treemaps.tsx index bdd014e64..3026851d2 100644 --- a/dashboards-observability/public/components/visualizations/charts/maps/treemaps.tsx +++ b/dashboards-observability/public/components/visualizations/charts/maps/treemaps.tsx @@ -3,57 +3,147 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; +import React, { useMemo } from 'react'; +import { indexOf, isEmpty, isEqual, isNull, uniq } from 'lodash'; import { Plt } from '../../plotly/plot'; +import { EmptyPlaceholder } from '../../../event_analytics/explorer/visualizations/shared_components/empty_placeholder'; +import { NUMERICAL_FIELDS } from '../../../../../common/constants/shared'; +import { + DEFAULT_PALETTE, + MULTI_COLOR_PALETTE, + SINGLE_COLOR_PALETTE, +} from '../../../../../common/constants/colors'; -export const TreeMap = ({ visualizations, layout, config }) => { - const labels = ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura']; - const parents = ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve']; - const treemapData = [ - { - type: 'treemap', - labels, - parents, - values: [10, 14, 12, 10, 2, 6, 6, 1, 4], - textinfo: 'label+value+percent parent+percent entry', - domain: { x: [0, 0.48] }, - outsidetextfont: { size: 20, color: '#377eb8' }, - marker: { line: { width: 2 } }, - pathbar: { visible: false }, - }, - { - type: 'treemap', - branchvalues: 'total', - labels, - parents, - domain: { x: [0.52, 1] }, - values: [65, 14, 12, 10, 2, 6, 6, 1, 4], - textinfo: 'label+value+percent parent+percent entry', - outsidetextfont: { size: 20, color: '#377eb8' }, - marker: { line: { width: 2 } }, - pathbar: { visible: false }, - }, - ]; - const finalLayout = { - annotations: [ - { - showarrow: false, - text: 'branchvalues: remainder', - x: 0.25, - xanchor: 'center', - y: 1.1, - yanchor: 'bottom', - }, +export const TreeMap = ({ visualizations, layout, config }: any) => { + const { + data, + metadata: { fields }, + } = visualizations.data.rawVizData; + const { dataConfig = {}, layoutConfig = {} } = visualizations?.data?.userConfigs; + + const childField = + dataConfig?.valueOptions && + dataConfig?.valueOptions.childField && + !isEmpty(dataConfig?.valueOptions.childField) + ? dataConfig?.valueOptions.childField[0] + : fields[fields.length - 1]; + + const parentField = + dataConfig?.valueOptions && + dataConfig?.valueOptions.parentField && + !isEmpty(dataConfig?.valueOptions.parentField) + ? dataConfig?.valueOptions.parentField[0] + : null; + + const valueField = + dataConfig?.valueOptions && + dataConfig?.valueOptions.valueField && + !isEmpty(dataConfig?.valueOptions.valueField) + ? dataConfig?.valueOptions.valueField[0] + : fields[0]; + + const colorField = + dataConfig?.chartStyles && dataConfig?.chartStyles.colorTheme + ? dataConfig?.chartStyles.colorTheme + : { name: DEFAULT_PALETTE }; + + const tilingAlgorithm = + dataConfig?.treemapOptions && + dataConfig?.treemapOptions.tilingAlgorithm && + !isEmpty(dataConfig?.treemapOptions.tilingAlgorithm) + ? dataConfig?.treemapOptions.tilingAlgorithm[0] + : 'squarify'; + + if ( + isEmpty(data[childField.name]) || + isEmpty(data[valueField.name]) || + (!isNull(parentField) && isEmpty(data[parentField.name])) || + isEqual(childField.name, parentField?.name) || + indexOf(NUMERICAL_FIELDS, valueField.type) < 0 + ) + return ; + + const [treemapData, mergedLayout] = useMemo(() => { + let labelsArray, parentsArray, valuesArray, colorsArray; + + if (parentField === null) { + labelsArray = [...data[childField.name]]; + parentsArray = [...Array(labelsArray.length).fill('')]; + valuesArray = [...data[valueField.name]]; + if (colorField.name === MULTI_COLOR_PALETTE) { + colorsArray = [...Array(data[childField.name].length).fill(colorField.childColor)]; + } + } else { + const uniqueParents = uniq(data[parentField.name]); + labelsArray = [...data[childField.name], ...uniqueParents]; + parentsArray = [...data[parentField.name], ...Array(uniqueParents.length).fill('')]; + valuesArray = [...data[valueField.name], ...Array(uniqueParents.length).fill(0)]; + if (colorField.name === MULTI_COLOR_PALETTE) { + colorsArray = [ + ...Array(data[childField.name].length).fill(colorField.childColor), + ...Array(uniqueParents.length).fill(colorField.parentColor), + ]; + } + } + + if (colorField.name === SINGLE_COLOR_PALETTE) { + colorsArray = [...Array(valuesArray.length).fill(colorField.childColor)]; + } + + const markerColors = + colorField.name === MULTI_COLOR_PALETTE + ? { + colors: colorsArray, + } + : ![DEFAULT_PALETTE, SINGLE_COLOR_PALETTE].includes(colorField.name) + ? { + colorscale: colorField.name, + colorbar: { + len: 1, + }, + } + : {}; + + const colorway = colorField.name === SINGLE_COLOR_PALETTE ? colorsArray : {}; + + const mapLayout = { + ...layout, + ...(layoutConfig.layout && layoutConfig.layout), + title: dataConfig?.panelOptions?.title || layoutConfig.layout?.title || '', + treemapcolorway: colorway, + }; + + const mapData = [ { - showarrow: false, - text: 'branchvalues: total', - x: 0.75, - xanchor: 'center', - y: 1.1, - yanchor: 'bottom', + type: 'treemap', + labels: labelsArray, + parents: parentsArray, + values: valuesArray, + textinfo: 'label+value+percent parent+percent entry', + tiling: { + packing: tilingAlgorithm.value, + }, + marker: markerColors, }, - ], + ]; + + return [mapData, mapLayout]; + }, [ + data, + childField, + valueField, + parentField, + colorField, + tilingAlgorithm, + dataConfig, + layoutConfig, + ]); + + const mergedConfigs = { + ...config, + ...(layoutConfig.config && layoutConfig.config), }; - return ; + + return ; };