Skip to content

Commit

Permalink
add pie to visbuilder
Browse files Browse the repository at this point in the history
Signed-off-by: Anan Zhuang <[email protected]>
  • Loading branch information
ananzh committed Aug 20, 2024
1 parent 7aef6e3 commit c15a84d
Show file tree
Hide file tree
Showing 24 changed files with 737 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { addFieldToConfiguration } from './drag_drop/add_field_to_configuration'
import { replaceFieldInConfiguration } from './drag_drop/replace_field_in_configuration';
import { reorderFieldsWithinSchema } from './drag_drop/reorder_fields_within_schema';
import { moveFieldBetweenSchemas } from './drag_drop/move_field_between_schemas';
import { IAggConfig } from '../../../../../data/common';

export const DATA_TAB_ID = 'data_tab';

Expand All @@ -33,12 +34,14 @@ export const DataTab = () => {
const editingState = useTypedSelector(
(state) => state.visualization.activeVisualization?.draftAgg
);
const configs = useTypedSelector((state) => state.visualization);

Check warning on line 37 in src/plugins/vis_builder/public/application/components/data_tab/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/index.tsx#L37

Added line #L37 was not covered by tests
const schemas = vizType.ui.containerConfig.data.schemas;
const {
services: {
data: {
search: { aggs: aggService },
},
notifications: { toasts },
},
} = useOpenSearchDashboards<VisBuilderServices>();

Expand Down Expand Up @@ -76,6 +79,18 @@ export const DataTab = () => {

const panelGroups = Array.from(schemas.all.map((schema) => schema.name));

// Check schema order
if (destinationSchemaName === 'split') {
if (isAggTooLow(aggProps.aggs, schemas.all)) {
// Prevent the move and show an error message
toasts.addWarning({

Check warning on line 86 in src/plugins/vis_builder/public/application/components/data_tab/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/index.tsx#L86

Added line #L86 was not covered by tests
title: 'vb_invalid_schema',
text: 'Split chart must be first in the configuration.',
});
return;

Check warning on line 90 in src/plugins/vis_builder/public/application/components/data_tab/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/index.tsx#L90

Added line #L90 was not covered by tests
}
}

if (Object.values(FIELD_SELECTOR_ID).includes(sourceSchemaName as FIELD_SELECTOR_ID)) {
if (panelGroups.includes(destinationSchemaName) && !combine) {
addFieldToConfiguration({
Expand Down Expand Up @@ -148,3 +163,17 @@ export const DataTab = () => {
</EuiDragDropContext>
);
};

const isAggTooLow = (allAggs: IAggConfig[], schemas: any[]) => {
const schema = schemas.find((s) => s.name === 'split');

Check warning on line 168 in src/plugins/vis_builder/public/application/components/data_tab/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/index.tsx#L168

Added line #L168 was not covered by tests
if (!schema || !schema.mustBeFirst) {
return false;

Check warning on line 170 in src/plugins/vis_builder/public/application/components/data_tab/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/index.tsx#L170

Added line #L170 was not covered by tests
}

const firstGroupSchemaIndex = allAggs.findIndex((item) => item.schema === 'group');

Check warning on line 173 in src/plugins/vis_builder/public/application/components/data_tab/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/index.tsx#L173

Added line #L173 was not covered by tests
if (firstGroupSchemaIndex !== -1) {
return true;

Check warning on line 175 in src/plugins/vis_builder/public/application/components/data_tab/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/index.tsx#L175

Added line #L175 was not covered by tests
}

return false;

Check warning on line 178 in src/plugins/vis_builder/public/application/components/data_tab/index.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/application/components/data_tab/index.tsx#L178

Added line #L178 was not covered by tests
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useDebounce } from 'react-use';
import { i18n } from '@osd/i18n';
import { EuiCallOut } from '@elastic/eui';
import { useTypedDispatch, useTypedSelector } from '../../utils/state_management';
import { DefaultEditorAggParams } from '../../../../../vis_default_editor/public';
import { DefaultEditorAggParams, calcAggIsTooLow } from '../../../../../vis_default_editor/public';
import { Title } from './title';
import { useIndexPatterns, useVisualizationType } from '../../utils/use';
import {
Expand Down Expand Up @@ -38,6 +38,7 @@ export function SecondaryPanel() {
data: {
search: { aggs: aggService },
},
notifications: { toasts },
} = services;
const schemas = vizType.ui.containerConfig.data.schemas.all;

Expand Down
3 changes: 2 additions & 1 deletion src/plugins/vis_builder/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
} from '../../opensearch_dashboards_utils/public';
import { opensearchFilters } from '../../data/public';
import { createRawDataVisFn } from './visualizations/vega/utils/expression_helper';
import { VISBUILDER_ENABLE_VEGA_SETTING } from '../common/constants';

export class VisBuilderPlugin
implements
Expand Down Expand Up @@ -107,7 +108,7 @@ export class VisBuilderPlugin

// Register Default Visualizations
const typeService = this.typeService;
registerDefaultTypes(typeService.setup());
registerDefaultTypes(typeService.setup(), core.uiSettings.get(VISBUILDER_ENABLE_VEGA_SETTING));
exp.registerFunction(createRawDataVisFn());

// Register the plugin to core
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ export interface VisualizationTypeOptions<T = any> {
searchContext: IExpressionLoaderParams['searchContext'],
useVega: boolean
) => Promise<string | undefined>;
readonly hierarchicalData?: boolean | ((vis: { params: T }) => boolean);
}
15 changes: 12 additions & 3 deletions src/plugins/vis_builder/public/visualizations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,26 @@
import type { TypeServiceSetup } from '../services/type_service';
import { createMetricConfig } from './metric';
import { createTableConfig } from './table';
import { createHistogramConfig, createLineConfig, createAreaConfig } from './vislib';
import {
createHistogramConfig,
createLineConfig,
createAreaConfig,
createPieConfig,
} from './vislib';

export function registerDefaultTypes(typeServiceSetup: TypeServiceSetup) {
const visualizationTypes = [
export function registerDefaultTypes(typeServiceSetup: TypeServiceSetup, useVega: boolean) {
const defaultVisualizationTypes = [
createHistogramConfig,
createLineConfig,
createAreaConfig,
createMetricConfig,
createTableConfig,
];

const visualizationTypes = useVega
? [...defaultVisualizationTypes, createPieConfig]
: defaultVisualizationTypes;

visualizationTypes.forEach((createTypeConfig) => {
typeServiceSetup.createVisualizationType(createTypeConfig());
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { AxisFormats } from '../utils/types';
import { buildAxes } from './axes';
import { buildAxes } from '../axes';

export type VegaMarkType =
| 'line'
Expand Down Expand Up @@ -87,7 +87,7 @@ export const buildMarkForVegaLite = (vegaType: string): VegaLiteMark => {
};

/**
* Builds a mark configuration for Vega based on the chart type.
* Builds a mark configuration for Vega useing series data based on the chart type.
*
* @param {VegaMarkType} chartType - The type of chart to build the mark for.
* @param {any} dimensions - The dimensions of the data.
Expand All @@ -97,7 +97,7 @@ export const buildMarkForVegaLite = (vegaType: string): VegaLiteMark => {
export const buildMarkForVega = (
chartType: VegaMarkType,
dimensions: any,
formats: AxisFormats
formats: AxisFormats,

Check failure on line 100 in src/plugins/vis_builder/public/visualizations/vega/components/mark/mark.ts

View workflow job for this annotation

GitHub Actions / Build and Verify on Linux (ciGroup1)

Delete `,`
): VegaMark => {
const baseMark: VegaMark = {
type: 'group',
Expand All @@ -108,13 +108,13 @@ export const buildMarkForVega = (
groupby: 'split',
},
},
signals: [{ name: 'width', update: 'chartWidth' }],
encode: {
enter: {
width: { signal: 'chartWidth' },
height: { signal: 'height' },
},
width: { signal: "facetWidth" },

Check failure on line 114 in src/plugins/vis_builder/public/visualizations/vega/components/mark/mark.ts

View workflow job for this annotation

GitHub Actions / Build and Verify on Linux (ciGroup1)

Replace `"facetWidth"` with `'facetWidth'`
height: { signal: "facetHeight" }

Check failure on line 115 in src/plugins/vis_builder/public/visualizations/vega/components/mark/mark.ts

View workflow job for this annotation

GitHub Actions / Build and Verify on Linux (ciGroup1)

Replace `"facetHeight"·}` with `'facetHeight'·},`
}

Check failure on line 116 in src/plugins/vis_builder/public/visualizations/vega/components/mark/mark.ts

View workflow job for this annotation

GitHub Actions / Build and Verify on Linux (ciGroup1)

Insert `,`
},
signals: [{ name: 'width', update: 'chartWidth' }],
scales: [
buildXScale(chartType, dimensions),
buildYScale(chartType),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { buildSlicesMarkForVega, buildPieMarks, buildArcMarks } from './mark_slices';

describe('buildSlicesMarkForVega', () => {
it('should return a group mark with correct properties', () => {
const result = buildSlicesMarkForVega(['level1', 'level2'], true, true);
expect(result.type).toBe('group');
expect(result.from).toEqual({ data: 'splits' });
expect(result.encode.enter.width).toEqual({ signal: 'chartWidth' });
expect(result.title).toBeDefined();
expect(result.data).toBeDefined();
expect(result.marks).toBeDefined();
});

it('should handle non-split case correctly', () => {
const result = buildSlicesMarkForVega(['level1'], false, true);
expect(result.from).toBeNull();
expect(result.encode.enter.width).toEqual({ signal: 'width' });
expect(result.title).toBeNull();
});
});

describe('buildPieMarks', () => {
it('should create correct number of marks', () => {
const result = buildPieMarks(['level1', 'level2'], true);
expect(result).toHaveLength(2);
});

it('should create correct transformations', () => {
const result = buildPieMarks(['level1'], true);
expect(result[0].transform).toHaveLength(3);
expect(result[0].transform[0].type).toBe('filter');
expect(result[0].transform[1].type).toBe('aggregate');
expect(result[0].transform[2].type).toBe('pie');
});
});

describe('buildArcMarks', () => {
it('should create correct number of arc marks', () => {
const result = buildArcMarks(['level1', 'level2']);
expect(result).toHaveLength(2);
});

it('should create arc marks with correct properties', () => {
const result = buildArcMarks(['level1']);
expect(result[0].type).toBe('arc');
expect(result[0].encode.enter.fill).toBeDefined();
expect(result[0].encode.update.startAngle).toBeDefined();
expect(result[0].encode.update.tooltip).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Builds a mark configuration for Vega using slices data.
*
* @param {string[]} levels - The array of hierarchy levels.
* @param {boolean} hasSplit - Indicates whether we have split data.
* @returns {Object} An object containing a single group mark configuration.
*/
export const buildSlicesMarkForVega = (levels: string[], hasSplit: boolean, isDonut: boolean) => {
return {
type: 'group',
// If we have splits, use the 'splits' data, otherwise no specific data source
from: hasSplit ? { data: 'splits' } : null,
encode: {
enter: {
// Set width based on whether we have splits or not
width: { signal: hasSplit ? 'chartWidth' : 'width' },
height: { signal: 'height' },
},
},
// Define signals for facet dimensions
signals: [
{ name: 'facetWidth', update: hasSplit ? 'chartWidth' : 'width' },
{ name: 'facetHeight', update: 'height' },
],
// Add a title if we have splits
title: hasSplit
? {
text: { signal: 'parent.split' },
frame: 'group',
}
: null,
// Build the data for each level of the pie
data: buildPieMarks(levels, hasSplit),
// Build the arc marks for each level of the pie
marks: buildArcMarks(levels),
};
};

/**
* Builds the data transformations for each level of the pie chart.
*
* @param {string[]} levels - The array of hierarchy levels.
* @param {boolean} hasSplit - Indicates whether we have split data.
* @returns {Object[]} An array of data transformation configurations for each level.
*/
export const buildPieMarks = (levels: string[], hasSplit: boolean) => {
return levels.map((level, index) => ({
name: `facet_${level}`,
source: 'table',
transform: [
// Filter data if we have splits
{
type: 'filter',
expr: hasSplit ? `datum.split === parent.split` : 'true',
},
// Aggregate data for this level
{
type: 'aggregate',
groupby: levels.slice(0, index + 1),
fields: ['value'],
ops: ['sum'],
as: ['sum_value'],
},
// Create pie layout
{ type: 'pie', field: 'sum_value' },
],
}));
};

/**
* Builds the arc marks for each level of the pie chart.
*
* @param {string[]} levels - The array of hierarchy levels.
* @returns {Object[]} An array of arc mark configurations for each level.
*/
export const buildArcMarks = (levels: string[], isDonut: boolean) => {
return levels.map((level, index) => ({
type: 'arc',
from: { data: `facet_${level}` },
encode: {
enter: {
// Set fill color based on the current level
fill: { scale: 'color', field: level },
// Center the arc
x: { signal: 'facetWidth / 2' },
y: { signal: 'facetHeight / 2' },
},
update: {
// Set arc angles and dimensions
startAngle: { field: 'startAngle' },
endAngle: { field: 'endAngle' },
padAngle: { value: 0.01 },
innerRadius: { signal: `innerRadius + thickness * ${index}` },
outerRadius: { signal: `innerRadius + thickness * (${index} + 1)` },
stroke: { value: 'white' },
strokeWidth: { value: 2 },
// Create tooltip with all relevant level data
tooltip: {
signal: `{${levels
.slice(0, index + 1)
.map((l) => `'${l}': datum.${l}`)
.join(', ')}, 'Value': datum.sum_value}`,
},
},
},
}));
};
Loading

0 comments on commit c15a84d

Please sign in to comment.