Skip to content

Commit 5f01205

Browse files
authored
feat(vis): support multiple fields per axis in visualizations (#11726)
* feat(vis): support multiple fields per axis in visualizations Refactor visualization axis configuration to allow selecting multiple fields for Y-axis across bar, line, area, scatter, heatmap, histogram, state timeline, and gauge chart types. Add AxisSelector component with multi-select support, update expression builders and data aggregation to handle multiple series, and simplify visualization registry and builder utilities. Signed-off-by: Yulong Ruan <ruanyl@amazon.com> * cleanup unused code Signed-off-by: Yulong Ruan <ruanyl@amazon.com> --------- Signed-off-by: Yulong Ruan <ruanyl@amazon.com>
1 parent 274b2dc commit 5f01205

83 files changed

Lines changed: 2766 additions & 2359 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/explore/16/build_vis.spec.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ export const runBuildVisTests = () => {
2727

2828
const selectFieldFromComboBox = (labelText, index, fieldName) => {
2929
cy.get('.euiFormLabel').contains(labelText).should('be.visible');
30-
cy.get('#axesSelector').within(() => {
31-
cy.get('[data-test-subj="comboBoxInput"]').eq(index).click();
32-
});
33-
cy.get('div[role="listBox"]').contains(fieldName).click();
30+
cy.get('.euiFormLabel')
31+
.contains(labelText)
32+
.closest('.euiFormRow')
33+
.find('[data-test-subj="axisSelectorButton"]')
34+
.click();
35+
cy.get('li[role="option"]').contains(fieldName).trigger('click');
3436
cy.wait(500);
3537
};
3638

src/plugins/agent_traces/public/components/visualizations/visualization_container.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import React, { useCallback, useEffect } from 'react';
99
import moment from 'moment';
1010
import { useDispatch } from 'react-redux';
1111

12-
import { AxisColumnMappings } from '../../../../explore/public';
1312
import { useTabResults } from '../../application/utils/hooks/use_tab_results';
1413
import { useSearchContext } from '../query_panel/utils/use_search_context';
1514
import { getVisualizationBuilder } from './visualization_builder_singleton';
@@ -23,10 +22,6 @@ import {
2322
} from '../../application/utils/state_management/slices';
2423
import { executeQueries } from '../../application/utils/state_management/actions/query_actions';
2524

26-
export interface UpdateVisualizationProps {
27-
mappings: AxisColumnMappings;
28-
}
29-
3025
export const VisualizationContainer = React.memo(() => {
3126
const { services } = useOpenSearchDashboards<AgentTracesServices>();
3227
const { results } = useTabResults();
Lines changed: 95 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
# OpenSearch Dashboards Visualizations
22

3-
This directory contains the visualization components for the OpenSearch Dashboards Explore plugin. It provides a flexible, rule-based system for rendering different types of visualizations based on data structure.
3+
This directory contains the visualization components for the OpenSearch Dashboards Explore plugin. It provides a flexible, registry-based system for rendering different types of visualizations based on axis-role mappings and data column types.
44

55
## Structure
66

7-
| Component | Description |
8-
| ---------------------------- | ------------------------------------------------------------------ |
9-
| **Visualization Registry** | Manages the registration and retrieval of visualization rules |
10-
| **Rule Repository** | Contains predefined rules for matching data to visualization types |
11-
| **Visualization Container** | Renders the selected visualization with its own styling options |
12-
| **Type-specific Components** | Implementation for each supported chart type |
7+
| Component | Description |
8+
| -------------------------- | ---------------------------------------------------------------------------- |
9+
| **Visualization Registry** | Manages registration and retrieval of `VisualizationType` configurations |
10+
| **Visualization Builder** | Orchestrates chart selection, axis mapping, style management, and rendering |
11+
| **Type-specific Configs** | Each chart type defines its rules, axis mappings, defaults, and render logic |
12+
| **Style Panel** | Shared and chart-specific style controls (axes, legend, thresholds, etc.) |
13+
| **ECharts Render** | Common ECharts rendering component used by most chart types |
1314

1415
## Supported Visualization Types
1516

16-
The system currently supports the following visualization types:
17-
1817
<table>
1918
<tr>
2019
<td><strong>Line Charts</strong></td>
@@ -26,207 +25,134 @@ The system currently supports the following visualization types:
2625
<td><strong>Scatter Plots</strong></td>
2726
<td><strong>Metric Visualizations</strong></td>
2827
</tr>
28+
<tr>
29+
<td><strong>Area Charts</strong></td>
30+
<td><strong>Tables</strong></td>
31+
<td><strong>Gauges</strong></td>
32+
</tr>
33+
<tr>
34+
<td><strong>Bar Gauges</strong></td>
35+
<td><strong>Histograms</strong></td>
36+
<td><strong>State Timelines</strong></td>
37+
</tr>
2938
</table>
3039

31-
## Rule-Based Visualization Selection
32-
33-
### How Rules Work
40+
## Architecture
3441

35-
Each visualization rule defines:
42+
### VisualizationType
3643

37-
1. A unique identifier
38-
2. A matching function that determines if the rule applies to the given set of data
39-
3. A list of chart types with priorities
40-
4. A function to convert the data to a vega expression for rendering
44+
Each chart type is defined as a `VisualizationType<T>` object containing:
4145

42-
### Rule to Chart Type Mapping
46+
- `name` / `type` / `icon` — metadata
47+
- `getRules()` — returns an array of `VisRule<T>` objects
48+
- `ui.style.defaults` — default style options
49+
- `ui.style.render` — React component for the style panel
4350

44-
**Key Feature:** Each rule can map to multiple chart types with different priorities
51+
### VisRule and Axis Mappings
4552

46-
This allows for:
53+
Each `VisRule` defines:
4754

48-
- Providing alternative visualization options for the same data structure
49-
- Defining a default (highest priority) visualization while allowing users to switch to alternatives
55+
1. A **priority** (higher = preferred when multiple rules match)
56+
2. One or more **mappings** — each mapping is a `Record<AxisRole, { type: VisFieldType }>` that declares which axis roles map to which field types
57+
3. A **render** function that produces the chart's React output
5058

51-
For a complete reference of all currently defined rules, see the [Visualization Rules Reference](./RULES.md).
59+
```typescript
60+
interface VisRule<T extends ChartType> {
61+
priority: number;
62+
mappings: AxisTypeMapping[];
63+
render: (props: VisRenderProps<T>) => React.ReactNode;
64+
}
65+
```
5266

53-
## Usage and Extension
67+
Axis roles include: `x`, `y`, `color`, `facet`, `size`, `y2`, `value`, `time`.
5468

55-
### Basic Usage
69+
Field types include: `numerical`, `categorical`, `date`.
5670

57-
The visualization container automatically selects and renders the appropriate visualization based on the data:
71+
A single rule can have multiple mappings (e.g., allowing either X=Date/Y=Numerical or X=Numerical/Y=Date). The registry matches rules by comparing the required field type counts in each mapping against the available columns.
5872

59-
```tsx
60-
<VisualizationContainer />
61-
```
73+
### Rule Matching
6274

63-
### Accessing Available Chart Types
75+
The `VisualizationRegistry` provides two levels of matching:
6476

65-
```typescript
66-
const visualizationData = getVisualizationType(rows, fieldSchema);
67-
const availableChartTypes = visualizationData?.availableChartTypes;
77+
- **Exact match**: the mapping's required field counts equal the input column counts exactly
78+
- **Compatible match**: the mapping's required field counts are less than or equal to the input counts (superset)
6879

69-
// availableChartTypes contains all chart types that can be used with the current data
70-
// sorted by priority
71-
```
80+
The `findBestMatch` method returns the highest-priority rule with an exact column-count match, optionally scoped to a specific chart type.
7281

73-
### Registering New Visualization Rules
82+
For a complete reference of all currently defined rules, see the [Visualization Rules Reference](./RULES.md).
7483

75-
To add a new visualization rule:
84+
## Usage and Extension
7685

77-
1. Define a new rule object that implements the `VisualizationRule` interface:
86+
### Basic Usage
7887

79-
```typescript
80-
const myCustomRule: VisualizationRule = {
81-
id: 'my-custom-rule',
82-
name: 'My Custom Rule',
83-
description: 'Description of when this rule applies',
84-
85-
// Define when this rule should match
86-
matches: (numerical, categorical, date) => numerical.length === 2 && categorical.length === 1,
87-
88-
// Define chart types with priorities (higher number = higher priority)
89-
chartTypes: [
90-
{ type: 'scatter', priority: 100, name: 'Scatter Plot' },
91-
{ type: 'bar', priority: 80, name: 'Bar Chart' },
92-
],
88+
The `VisualizationBuilder` handles chart selection and rendering automatically:
9389

94-
// Define how to convert data to an expression
95-
toExpression: (
96-
transformedData,
97-
numericalColumns,
98-
categoricalColumns,
99-
dateColumns,
100-
styleOptions,
101-
chartType = 'scatter'
102-
) => {
103-
switch (chartType) {
104-
case 'scatter':
105-
return createCustomScatterChart(
106-
transformedData,
107-
numericalColumns,
108-
categoricalColumns,
109-
styleOptions
110-
);
111-
case 'bar':
112-
return createCustomBarChart(
113-
transformedData,
114-
numericalColumns,
115-
categoricalColumns,
116-
styleOptions
117-
);
118-
default:
119-
return createCustomScatterChart(
120-
transformedData,
121-
numericalColumns,
122-
categoricalColumns,
123-
styleOptions
124-
);
125-
}
126-
},
127-
};
90+
```tsx
91+
<VisualizationContainer />
12892
```
12993

130-
2. Register the rule with the visualization registry:
94+
### Accessing the Registry
13195

13296
```typescript
133-
// Register a single rule
134-
visualizationRegistry.registerRule(myCustomRule);
97+
// Via React hook (preferred)
98+
const registry = useVisualizationRegistry();
13599

136-
// Or register multiple rules
137-
visualizationRegistry.registerRules([myCustomRule, anotherRule]);
100+
// Via plugin services
101+
const registry = services.visualizationRegistry.getRegistry();
138102
```
139103

140-
### Adding a New Chart Type
141-
142-
To add a new chart type:
143-
144-
1. Create a new directory for your chart type:
104+
### Registering a New Visualization Type
145105

146-
```
147-
visualizations/
148-
└── my_chart_type/
149-
├── my_chart_vis_config.ts # Chart configuration
150-
├── my_chart_vis_options.tsx # UI options component
151-
├── to_expression.ts # Expression generation
152-
└── ... other files
153-
```
154-
155-
2. Define the chart configuration in `my_chart_vis_config.ts`:
106+
1. Define a config factory that returns a `VisualizationType`:
156107

157108
```typescript
158-
export interface MyChartStyleControls {
159-
// Define style options specific to your chart
160-
showLegend: boolean;
161-
colors: string[];
162-
// ... other options
163-
}
164-
165-
export const createMyChartConfig = () => {
166-
return {
167-
name: 'My Chart',
168-
type: 'my_chart_type',
169-
ui: {
170-
style: {
171-
defaults: {
172-
showLegend: true,
173-
colors: ['#1EA7FD', '#FF5733'],
174-
// ... default values for other options
109+
export const createMyChartConfig = (): VisualizationType<'my_chart'> => ({
110+
name: 'My Chart',
111+
type: 'my_chart',
112+
icon: 'visMyChart',
113+
getRules: () => [
114+
{
115+
priority: 100,
116+
mappings: [
117+
{
118+
[AxisRole.X]: { type: VisFieldType.Categorical },
119+
[AxisRole.Y]: { type: VisFieldType.Numerical },
175120
},
176-
render: (props: StyleControlsProps<MyChartStyleControls>) => (
177-
<MyChartVisOptions {...props} />
178-
),
121+
],
122+
render(props) {
123+
const spec = createMyChartSpec(
124+
props.transformedData,
125+
props.styleOptions,
126+
props.axisColumnMappings
127+
);
128+
return <EchartsRender spec={spec} />;
179129
},
180130
},
181-
};
182-
};
131+
],
132+
ui: {
133+
style: {
134+
defaults: defaultMyChartStyles,
135+
render: (props) => React.createElement(MyChartVisOptions, props),
136+
},
137+
},
138+
});
183139
```
184140

185-
3. Create the expression generator in `to_expression.ts`:
141+
2. Register it via the `VisualizationRegistryService` setup contract:
186142

187143
```typescript
188-
export const createMyChartExpression = (
189-
transformedData: Array<Record<string, any>>,
190-
numericalColumns: VisColumn[],
191-
categoricalColumns: VisColumn[],
192-
dateColumns: VisColumn[],
193-
styleOptions: MyChartStyleControls
194-
) => {
195-
// Generate the expression for your chart
196-
// ...
197-
198-
return {
199-
type: 'expression',
200-
chain: [
201-
// Your expression chain
202-
],
203-
};
204-
};
144+
visualizationRegistry.register(createMyChartConfig());
205145
```
206146

207-
4. Update the `ChartType` type and `ChartStyleControlMap` interface in `utils/use_visualization_types.ts`:
147+
3. Add the new chart type to the `ChartType` union and `ChartStylesMapping` interface in `utils/use_visualization_types.ts`.
208148

209-
```typescript
210-
export type ChartType = 'line' | 'pie' | /* ... */ | 'my_chart_type';
149+
### Adding a New Chart Type Directory
211150

212-
export interface ChartStyleControlMap {
213-
line: LineChartStyleControls;
214-
pie: PieChartStyleControls;
215-
// ... other chart types
216-
my_chart_type: MyChartStyleControls;
217-
}
218151
```
219-
220-
5. Update the `getVisualizationConfig` method in `visualization_registry.ts`:
221-
222-
```typescript
223-
private getVisualizationConfig(type: string) {
224-
switch (type) {
225-
// ... existing cases
226-
case 'my_chart_type':
227-
return createMyChartConfig();
228-
default:
229-
return;
230-
}
231-
}
152+
visualizations/
153+
└── my_chart/
154+
├── my_chart_vis_config.tsx # VisualizationType factory + style types + defaults
155+
├── my_chart_vis_options.tsx # Style panel component
156+
├── to_expression.ts # ECharts spec generation
157+
└── ... other files (utils, tests, exclusive options)
232158
```

0 commit comments

Comments
 (0)