diff --git a/common/constants/explorer.ts b/common/constants/explorer.ts index 4d07b7033..96908eab0 100644 --- a/common/constants/explorer.ts +++ b/common/constants/explorer.ts @@ -82,6 +82,7 @@ export const PLOTLY_GAUGE_COLUMN_NUMBER = 4; export const APP_ANALYTICS_TAB_ID_REGEX = /application-analytics-tab.+/; export const DEFAULT_AVAILABILITY_QUERY = 'stats count() by span( timestamp, 1h )'; export const ADD_BUTTON_TEXT = '+ Add color theme'; +export const ADD_SERIES_POSITION_TEXT = '+ Add label position'; export const NUMBER_INPUT_MIN_LIMIT = 1; export const VIZ_CONTAIN_XY_AXIS = [ @@ -280,3 +281,7 @@ export const DATA_CONFIG_HINTS_INFO = { [BREAKDOWNS]: "Defines how each series is broken down. Breakdowns are 'by' clauses that subdivide the existing series.", }; +export const SERIES_POSITION_OPTIONS = [ + { id: htmlIdGenerator('ct')(), label: 'Left', side: 'left' }, + { id: htmlIdGenerator('ct')(), label: 'Right', side: 'right' }, +]; diff --git a/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap b/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap index d8a2d7f31..a61ee008d 100644 --- a/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap +++ b/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap @@ -1885,6 +1885,13 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` }, ], }, + Object { + "editor": [Function], + "id": "yaxis-side", + "mapTo": "seriesPosition", + "name": "Series label position", + "schemas": Array [], + }, Object { "editor": [Function], "id": "color-theme", @@ -2386,6 +2393,13 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` }, ], }, + Object { + "editor": [Function], + "id": "yaxis-side", + "mapTo": "seriesPosition", + "name": "Series label position", + "schemas": Array [], + }, Object { "editor": [Function], "id": "color-theme", @@ -2901,6 +2915,13 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` }, ], }, + Object { + "editor": [Function], + "id": "yaxis-side", + "mapTo": "seriesPosition", + "name": "Series label position", + "schemas": Array [], + }, Object { "editor": [Function], "id": "color-theme", diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap b/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap index 7283927bb..3efa4edcc 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap +++ b/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap @@ -1410,6 +1410,13 @@ exports[`Config panel component Renders config panel with visualization data 1`] }, ], }, + Object { + "editor": [Function], + "id": "yaxis-side", + "mapTo": "seriesPosition", + "name": "Series label position", + "schemas": Array [], + }, Object { "editor": [Function], "id": "color-theme", diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_yaxis_side.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_yaxis_side.tsx new file mode 100644 index 000000000..492c4d2e3 --- /dev/null +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_yaxis_side.tsx @@ -0,0 +1,143 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { Fragment, useCallback } from 'react'; +import { + EuiButton, + EuiAccordion, + EuiFormRow, + EuiSpacer, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + htmlIdGenerator, + EuiComboBox, +} from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import { + ADD_SERIES_POSITION_TEXT, + AGGREGATIONS, + SERIES_POSITION_OPTIONS, +} from '../../../../../../../../common/constants/explorer'; +import { getPropName } from '../../../../../utils/utils'; + +export const ConfigYAxisSide = ({ + visualizations, + schemas, + vizState = [], + handleConfigChange, + sectionName = 'Series position', +}: any) => { + const { data } = visualizations; + const { metadata: { fields = [] } = {} } = data?.rawVizData; + const { dataConfig = {} } = data?.userConfigs; + + const options = (dataConfig[AGGREGATIONS] && dataConfig[AGGREGATIONS].length !== 0 + ? dataConfig[AGGREGATIONS] + : fields + ).map((optionItem) => ({ + ...optionItem, + label: getPropName(optionItem), + side: 'left', + className: 'color-theme-combo-box-option', + ctid: htmlIdGenerator('ct')(), + })); + + const getUpdatedOptions = () => + options.filter((option) => !vizState.some((vizOpt) => option.label === vizOpt?.label)); + + const handleColorThemeChange = useCallback( + (ctId, ctName) => (event) => { + const value = event.length ? event[0][ctName] : ''; + handleConfigChange([ + ...vizState.map((ct) => { + if (ctId !== ct.ctid) return ct; + return { + ...ct, + [ctName]: value, + }; + }), + ]); + }, + [vizState, handleConfigChange] + ); + + const handleDeletePosition = useCallback( + (ctId) => (event) => handleConfigChange([...vizState.filter((ct) => ct.ctid !== ctId)]), + [vizState, handleConfigChange] + ); + + const handleAddPosition = useCallback(() => { + const res = isEmpty(vizState) + ? [options.length ? options[0] : { id: htmlIdGenerator('ct')(), label: '', side: 'left' }] + : [ + ...vizState, + ...options.filter((option) => !vizState.some((vizOpt) => option.label === vizOpt?.label)), + ]; + handleConfigChange(res); + }, [vizState, handleConfigChange]); + + return ( + + + {ADD_SERIES_POSITION_TEXT} + + + {!isEmpty(vizState) && + vizState.map((ct) => { + return ( + + + + + + + + + + + positionItem.side === ct.side + ) + : [] + } + onChange={handleColorThemeChange(ct.ctid, 'side')} + aria-label="series-position-dropdown" + /> + + + + + + + + + + + ); + })} + + ); +}; diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx index 998cf3c71..09a3b59b2 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/data_configurations_panel.tsx @@ -306,11 +306,6 @@ export const DataConfigPanelItem = ({ fillVisDataInStore({ visData: visData, query, visConfMetadata, visMeta }); }; - const isPositionButtonVisible = (sectionName: string) => - sectionName === AGGREGATIONS && - (visualizations.vis.name === VIS_CHART_TYPES.Line || - visualizations.vis.name === VIS_CHART_TYPES.Scatter); - const getTimeStampFilteredFields = (options: IField[]) => filter(options, (i: IField) => i.type !== TIMESTAMP); @@ -381,19 +376,6 @@ export const DataConfigPanelItem = ({ )} {/* Show input fields for dimensions */} {!isAggregations && getCommonDimensionsField(selectedObj, name)} - {isPositionButtonVisible(name) && ( - - updateList(id, 'side')} - /> - - )} diff --git a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/index.ts b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/index.ts index 10c56703e..c026190a6 100644 --- a/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/index.ts +++ b/public/components/event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/index.ts @@ -26,3 +26,4 @@ export { ButtonGroupItem } from './config_button_group'; export { TextInputFieldItem } from './config_text_input'; export { ConfigBarChartStyles } from './config_bar_chart_styles'; export { ConfigAvailability } from './config_availability'; +export { ConfigYAxisSide } from './config_yaxis_side'; diff --git a/public/components/visualizations/charts/lines/line.tsx b/public/components/visualizations/charts/lines/line.tsx index d669c60ed..88de3ae06 100644 --- a/public/components/visualizations/charts/lines/line.tsx +++ b/public/components/visualizations/charts/lines/line.tsx @@ -44,6 +44,7 @@ export const Line = ({ visualizations, layout, config }: any) => { userConfigs: { dataConfig: { chartStyles = {}, + seriesPosition = [], legend = {}, span = {}, colorTheme = [], @@ -85,7 +86,8 @@ export const Line = ({ visualizations, layout, config }: any) => { colorTheme.find((colorSelected) => colorSelected.name.name === field)?.color) || PLOTLY_COLOR[index % PLOTLY_COLOR.length]; const timestampField = find(fields, (field) => field.type === 'timestamp'); - let xaxis = [timestampField]; + const xaxis = [timestampField]; + let multiYAxisLayout = {}; if (!timestampField || isEmpty(series)) return ; @@ -94,16 +96,34 @@ export const Line = ({ visualizations, layout, config }: any) => { return traces.map((trace, idx: number) => { const selectedColor = getSelectedColorTheme(trace.aggName, idx); const fillColor = hexToRgb(selectedColor, fillOpacity); + const side = seriesPosition.find((seriesItem) => seriesItem.label === trace.name); + const multiYaxis = { yaxis: `y${idx + 1}` }; + + multiYAxisLayout = { + ...multiYAxisLayout, + [`yaxis${idx > 0 ? idx + 1 : ''}`]: { + titlefont: { + color: selectedColor, + }, + automargin: true, + tickfont: { + color: selectedColor, + ...(labelSize && { + size: labelSize, + }), + }, + ...(idx > 0 && { overlaying: 'y' }), + side: side ? side.side : 'left', + }, + }; return { ...trace, hoverinfo: tooltipMode === 'hidden' ? 'none' : tooltipText, type: 'line', mode, - ...{ - fill: 'tozeroy', - fillcolor: fillColor, - }, + fill: 'tozeroy', + fillcolor: fillColor, line: { shape: lineShape, width: lineWidth, @@ -111,14 +131,13 @@ export const Line = ({ visualizations, layout, config }: any) => { }, marker: { size: markerSize, - ...{ - color: fillColor, - line: { - color: selectedColor, - width: lineWidth, - }, + color: fillColor, + line: { + color: selectedColor, + width: lineWidth, }, }, + ...(idx >= 1 && multiYaxis), }; }); }; @@ -143,16 +162,7 @@ export const Line = ({ visualizations, layout, config }: any) => { transformPreprocessedDataToTraces(preprocessJsonData(jsonData, visConfig), visConfig), traceStyles ); - }, [ - chartStyles, - jsonData, - dimensions, - series, - span, - breakdowns, - panelOptions, - tooltipOptions, - ]); + }, [chartStyles, jsonData, dimensions, series, span, breakdowns, panelOptions, tooltipOptions]); const mergedLayout = useMemo(() => { const axisLabelsStyle = { @@ -181,9 +191,7 @@ export const Line = ({ visualizations, layout, config }: any) => { tickangle: tickAngle, ...axisLabelsStyle, }, - yaxis: { - ...axisLabelsStyle, - }, + ...multiYAxisLayout, showlegend: showLegend, margin: PLOT_MARGIN, }; diff --git a/public/components/visualizations/charts/lines/line_type.ts b/public/components/visualizations/charts/lines/line_type.ts index d02558b66..ccbde39ab 100644 --- a/public/components/visualizations/charts/lines/line_type.ts +++ b/public/components/visualizations/charts/lines/line_type.ts @@ -14,6 +14,7 @@ import { ConfigLegend, InputFieldItem, ConfigColorTheme, + ConfigYAxisSide, } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls'; import { ConfigAvailability } from '../../../event_analytics/explorer/visualizations/config_panel/config_panes/config_controls/config_availability'; import { @@ -204,6 +205,13 @@ export const createLineTypeDefinition = (params: any = {}) => ({ }, ], }, + { + id: 'yaxis-side', + name: 'Series label position', + editor: ConfigYAxisSide, + mapTo: 'seriesPosition', + schemas: [], + }, { id: 'color-theme', name: 'Color theme',