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 19, 2024
1 parent 7aef6e3 commit b5bc887
Show file tree
Hide file tree
Showing 19 changed files with 559 additions and 36 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
83 changes: 73 additions & 10 deletions src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,30 +79,93 @@ const flattenSeries = (
});
};

interface FlattenedSliceItem {
[key: string]: any;
value: number;
split?: string;
}

interface FlattenHierarchyResult {
flattenedData: FlattenedSliceItem[];
levels: string[];
}

/**
* Flattens hierarchical slice data into a single array of data points
* @param {any} data - The hierarchical data to flatten
* @param {any[]} group - The group data (rows or columns) if split dimensions exist
* @returns {FlattenedSliceItem[]} Flattened array of data points
*/
const flattenHierarchy = (data, group): FlattenHierarchyResult => {
const flattenedData: FlattenedSliceItem[] = [];
const levelSet = new Set<string>();

Check warning on line 101 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L100-L101

Added lines #L100 - L101 were not covered by tests

const flattenSlices = (

Check warning on line 103 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L103

Added line #L103 was not covered by tests
slices: any,
split?: string,
level = 1,
parentLabels: { [key: string]: string } = {}
) => {
slices.children.forEach((child: any) => {
const currentLabels = { ...parentLabels, [`level${level}`]: child.name };
levelSet.add(`level${level}`);

Check warning on line 111 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L109-L111

Added lines #L109 - L111 were not covered by tests

if (child.children && child.children.length > 0) {
flattenSlices(child, split, level + 1, currentLabels);

Check warning on line 114 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L114

Added line #L114 was not covered by tests
} else {
const dataPoint: FlattenedSliceItem = {

Check warning on line 116 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L116

Added line #L116 was not covered by tests
...currentLabels,
value: child.size,
};
if (split !== undefined) {
dataPoint.split = split;

Check warning on line 121 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L121

Added line #L121 was not covered by tests
}
flattenedData.push(dataPoint);

Check warning on line 123 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L123

Added line #L123 was not covered by tests
}
});
};

if (group && group.length !== 0) {
group.forEach((splitData) => {
flattenSlices(splitData.slices, splitData.label);

Check warning on line 130 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L129-L130

Added lines #L129 - L130 were not covered by tests
});
} else {
flattenSlices(data.slices, undefined);

Check warning on line 133 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L133

Added line #L133 was not covered by tests
}

return { flattenedData, levels: Array.from(levelSet) };

Check warning on line 136 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L136

Added line #L136 was not covered by tests
};

/**
* Handles the flattening of data for different chart types
* @param {any} context - The context object containing the data
* @param {any} dimensions - The dimensions object defining the chart structure
* @param {'series' | 'slices'} handlerType - The type of chart data to handle
* @returns {any} Converted and flattened data suitable for visualization
*/
export const flattenDataHandler = (context, dimensions, handlerType = 'series') => {
// Currently, our vislib only supports 'series' or 'slices' response types.
// This will need to be updated if more types are added in the future.
// TODO: Update this func if more types are added in the future.
const handler =
handlerType === 'series' ? vislibSeriesResponseHandler : vislibSlicesResponseHandler;
const converted = handler(context, dimensions);
const group = dimensions.splitRow
? converted.rows
: dimensions.splitColumn
? converted.columns
: [];

if (handlerType === 'series') {
// Determine the group based on split dimensions
const group = dimensions.splitRow
? converted.rows
: dimensions.splitColumn
? converted.columns
: [];

if (group && group.length !== 0) {
converted.series = group.flatMap((split) => flattenSeries(split.series, split.label));
setAxisProperties(converted, group);
} else {
converted.series = flattenSeries(converted.series);
}
} else if (handlerType === 'slices') {
// TODO: Handle slices data, such as pie charts
// This section should be implemented when support for slice-based charts is added
const { flattenedData, levels } = flattenHierarchy(converted, group);
converted.slices = flattenedData;
converted.levels = levels;

Check warning on line 168 in src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/utils/helpers.ts#L166-L168

Added lines #L166 - L168 were not covered by tests
}

return converted;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { generateVegaLiteSpec } from './vega_lite_spec_builder';
import { generateVegaLiteSpecForSeries } from './vega_lite_spec_series_builder';

describe('generateVegaLiteSpec', () => {
describe('generateVegaLiteSpecForSeries', () => {
it('should generate a basic Vega-Lite specification', () => {
const data = {
xAxisFormat: { id: 'date' },
Expand All @@ -22,7 +22,7 @@ describe('generateVegaLiteSpec', () => {
};
const style = { type: 'line' };

const result = generateVegaLiteSpec(data, visConfig, style);
const result = generateVegaLiteSpecForSeries(data, visConfig, style);

expect(result.$schema).toBe('https://vega.github.io/schema/vega-lite/v5.json');
expect(result.data).toBeDefined();
Expand All @@ -45,10 +45,10 @@ describe('generateVegaLiteSpec', () => {
addTooltip: true,
};

const lineResult = generateVegaLiteSpec(data, visConfig, { type: 'line' });
const lineResult = generateVegaLiteSpecForSeries(data, visConfig, { type: 'line' });
expect(lineResult.mark).toEqual({ type: 'line', point: true, tooltip: true });

const areaResult = generateVegaLiteSpec(data, visConfig, { type: 'area' });
const areaResult = generateVegaLiteSpecForSeries(data, visConfig, { type: 'area' });
expect(areaResult.mark).toEqual({
type: 'area',
line: true,
Expand All @@ -58,7 +58,7 @@ describe('generateVegaLiteSpec', () => {
baseline: 0,
});

const barResult = generateVegaLiteSpec(data, visConfig, { type: 'bar' });
const barResult = generateVegaLiteSpecForSeries(data, visConfig, { type: 'bar' });
expect(barResult.mark).toEqual({ type: 'bar', tooltip: true });
});

Expand All @@ -78,7 +78,7 @@ describe('generateVegaLiteSpec', () => {
};
const style = { type: 'line' };

const result = generateVegaLiteSpec(data, visConfig, style);
const result = generateVegaLiteSpecForSeries(data, visConfig, style);

expect(result.config).toBeDefined();
expect(result.config!.legend).toBeDefined();
Expand All @@ -101,7 +101,7 @@ describe('generateVegaLiteSpec', () => {
};
const style = { type: 'line' };

const result = generateVegaLiteSpec(data, visConfig, style);
const result = generateVegaLiteSpecForSeries(data, visConfig, style);

expect(result.encoding!.tooltip).toBeDefined();
expect(result.mark).toHaveProperty('tooltip', true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { mapChartTypeToVegaType } from './utils/helpers';
* @param {StyleState} style - The StyleState defined in style slice.
* @returns {VegaLiteSpec} The complete Vega-Lite specification.
*/
export const generateVegaLiteSpec = (
export const generateVegaLiteSpecForSeries = (
data: any,
visConfig: any,
style: StyleState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import { StyleState } from '../../application/utils/state_management';
import { flattenDataHandler } from './utils/helpers';
import { generateVegaLiteSpec } from './vega_lite_spec_builder';
import { generateVegaSpec } from './vega_spec_builder';
import { generateVegaLiteSpecForSeries } from './vega_lite_spec_series_builder';
import { generateVegaSpecForSeries } from './vega_spec_series_builder';
import { generateVegaSpecForSlices } from './vega_spec_slices_builder';
import { VegaLiteSpec, VegaSpec } from './utils/types';

/**
Expand All @@ -25,15 +26,26 @@ export const createVegaSpec = (
const { dimensions } = visConfig;

// Transform the data using the flattenDataHandler
const transformedData = flattenDataHandler(context, dimensions, 'series');
const handler = style.type !== 'pie' ? 'series' : 'slices';
const transformedData = flattenDataHandler(context, dimensions, handler);

Check warning on line 30 in src/plugins/vis_builder/public/visualizations/vega/vega_spec_factory.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/vega_spec_factory.ts#L30

Added line #L30 was not covered by tests

return handler === 'series'
? createVegaSpecForSeriesData(dimensions, transformedData, visConfig, style)
: createVegaSpecForSlicesData(dimensions, transformedData, visConfig, style);
};

const createVegaSpecForSeriesData = (dimensions, transformedData, visConfig, style) => {
// Determine whether to use Vega or Vega-Lite based on the presence of split dimensions
// TODO: Summarize the cases to use Vega. Change this to a better determine function.
if (dimensions.splitRow || dimensions.splitColumn) {
// Use Vega for more complex, split visualizations
return generateVegaSpec(transformedData, visConfig, style);
return generateVegaSpecForSeries(transformedData, visConfig, style);

Check warning on line 42 in src/plugins/vis_builder/public/visualizations/vega/vega_spec_factory.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/vega_spec_factory.ts#L42

Added line #L42 was not covered by tests
} else {
// Use Vega-Lite for simpler visualizations
return generateVegaLiteSpec(transformedData, visConfig, style);
return generateVegaLiteSpecForSeries(transformedData, visConfig, style);

Check warning on line 45 in src/plugins/vis_builder/public/visualizations/vega/vega_spec_factory.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/vega_spec_factory.ts#L45

Added line #L45 was not covered by tests
}
};

const createVegaSpecForSlicesData = (dimensions, transformedData, visConfig, style) => {
return generateVegaSpecForSlices(transformedData, visConfig, style);

Check warning on line 50 in src/plugins/vis_builder/public/visualizations/vega/vega_spec_factory.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/vis_builder/public/visualizations/vega/vega_spec_factory.ts#L50

Added line #L50 was not covered by tests
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { generateVegaSpec } from './vega_spec_builder';
import { generateVegaSpecForSeries } from './vega_spec_series_builder';

describe('generateVegaSpec', () => {
describe('generateVegaSpecForSeries', () => {
const baseData = {
xAxisFormat: { id: 'date' },
xAxisLabel: 'Date',
Expand All @@ -26,7 +26,7 @@ describe('generateVegaSpec', () => {
it('should generate a basic Vega specification', () => {
const style = { type: 'line' };

const result = generateVegaSpec(baseData, baseVisConfig, style);
const result = generateVegaSpecForSeries(baseData, baseVisConfig, style);

expect(result.$schema).toBe('https://vega.github.io/schema/vega/v5.json');
expect(result.data).toBeDefined();
Expand All @@ -38,7 +38,7 @@ describe('generateVegaSpec', () => {
it('should handle area charts', () => {
const style = { type: 'area' };

const result = generateVegaSpec(baseData, baseVisConfig, style);
const result = generateVegaSpecForSeries(baseData, baseVisConfig, style);

expect(result.data).toBeDefined();
expect(result.data?.some((d) => d.name === 'stacked')).toBe(true);
Expand All @@ -48,7 +48,7 @@ describe('generateVegaSpec', () => {
it('should add legend when specified', () => {
const style = { type: 'line' };

const result = generateVegaSpec(baseData, baseVisConfig, style);
const result = generateVegaSpecForSeries(baseData, baseVisConfig, style);

expect(result.legends).toBeDefined();
expect(result.legends?.[0]?.orient).toBe('right');
Expand All @@ -61,7 +61,7 @@ describe('generateVegaSpec', () => {
};
const style = { type: 'line' };

const result = generateVegaSpec(baseData, visConfigNoLegend, style);
const result = generateVegaSpecForSeries(baseData, visConfigNoLegend, style);

expect(result.legends).toBeUndefined();
});
Expand Down
Loading

0 comments on commit b5bc887

Please sign in to comment.