diff --git a/src/plugins/vis_builder/public/visualizations/vega/components/axes.test.ts b/src/plugins/vis_builder/public/visualizations/vega/components/axes.test.ts new file mode 100644 index 000000000000..25d8d2e37cce --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/components/axes.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { buildAxes } from './axes'; + +describe('axes.ts', () => { + describe('buildAxes', () => { + it('should return correct axis configurations for date x-axis', () => { + const dimensions = { + x: { format: { id: 'date' } }, + y: [{ label: 'Y Axis' }], + }; + const formats = { + xAxisLabel: 'X Axis', + yAxisLabel: 'Custom Y Axis', + }; + + const result = buildAxes(dimensions, formats); + + expect(result).toHaveLength(2); + expect(result[0]).toEqual({ + orient: 'bottom', + scale: 'x', + labelAngle: -90, + labelAlign: 'right', + labelBaseline: 'middle', + title: 'X Axis', + format: '%Y-%m-%d %H:%M', + }); + expect(result[1]).toEqual({ + orient: 'left', + scale: 'y', + title: 'Custom Y Axis', + }); + }); + + it('should not add format when x is not date', () => { + const dimensions = { + x: { format: { id: 'number' } }, + y: [{ label: 'Y Axis' }], + }; + const result = buildAxes(dimensions, 'X', 'Y'); + + expect(result[0]).not.toHaveProperty('format'); + }); + + it('should use default labels when not provided', () => { + const dimensions = { + x: {}, + y: [{ label: 'Default Y' }], + }; + const result = buildAxes(dimensions, '', ''); + + expect(result[0].title).toBe('_all'); + expect(result[1].title).toBe('Default Y'); + }); + }); +}); diff --git a/src/plugins/vis_builder/public/visualizations/vega/components/axes.ts b/src/plugins/vis_builder/public/visualizations/vega/components/axes.ts new file mode 100644 index 000000000000..033ce020a8d9 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/vega/components/axes.ts @@ -0,0 +1,51 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AxisFormats } from '../utils/types'; + +export interface AxisConfig { + orient?: string; + scale?: string; + labelAngle?: number; + labelAlign?: string; + labelBaseline?: string; + title: any; + format?: string; // property for date format +} + +/** + * Builds the axes configuration for a chart. + * + * Note: This axis configuration is currently tailored for specific use cases. + * In the future, we plan to expand and generalize this function to accommodate + * a wider range of chart types and axis configurations. + * @param {any} dimensions - The dimensions of the data. + * @param {AxisFormats} formats - The formatting information for axes. + */ + +export const buildAxes = (dimensions: any, formats: AxisFormats): AxisConfig[] => { + const { xAxisLabel, yAxisLabel } = formats; + const xAxis: AxisConfig = { + orient: 'bottom', + scale: 'x', + labelAngle: -90, + labelAlign: 'right', + labelBaseline: 'middle', + title: xAxisLabel || '_all', + }; + + // Add date format if x dimension is a date type + if (dimensions.x && dimensions.x.format && dimensions.x.format.id === 'date') { + xAxis.format = '%Y-%m-%d %H:%M'; + } + + const yAxis: AxisConfig = { + orient: 'left', + scale: 'y', + title: yAxisLabel ? yAxisLabel : dimensions.y && dimensions.y[0] ? dimensions.y[0].label : '', + }; + + return [xAxis, yAxis]; +}; diff --git a/src/plugins/vis_builder/public/visualizations/vega/components/mark.ts b/src/plugins/vis_builder/public/visualizations/vega/components/mark.ts index 9a58ec425390..7e6e5410d5f6 100644 --- a/src/plugins/vis_builder/public/visualizations/vega/components/mark.ts +++ b/src/plugins/vis_builder/public/visualizations/vega/components/mark.ts @@ -4,6 +4,7 @@ */ import { AxisFormats } from '../utils/types'; +import { buildAxes } from './axes'; export type VegaMarkType = | 'line' @@ -98,7 +99,6 @@ export const buildMarkForVega = ( dimensions: any, formats: AxisFormats ): VegaMark => { - const { xAxisLabel, yAxisLabel } = formats; const baseMark: VegaMark = { type: 'group', from: { @@ -116,7 +116,7 @@ export const buildMarkForVega = ( }, signals: [{ name: 'width', update: 'chartWidth' }], scales: [ - buildXScale(chartType), + buildXScale(chartType, dimensions), buildYScale(chartType), { name: 'color', @@ -125,21 +125,7 @@ export const buildMarkForVega = ( range: 'category', }, ], - axes: [ - { - orient: 'bottom', - scale: 'x', - labelAngle: -90, - labelAlign: 'right', - labelBaseline: 'middle', - title: xAxisLabel || '_all', - }, - { - orient: 'left', - scale: 'y', - title: yAxisLabel ? yAxisLabel : dimensions.y[0].label, - }, - ], + axes: buildAxes(dimensions, formats), title: { text: { signal: 'parent.split' }, }, @@ -161,7 +147,17 @@ export const buildMarkForVega = ( return baseMark; }; -const buildXScale = (chartType: VegaMarkType) => { +const buildXScale = (chartType: VegaMarkType, dimensions) => { + // For date-based data, use a time scale regardless of the chart type. + if (dimensions.x && dimensions.x.format.id === 'date') { + return { + name: 'x', + type: 'time', + domain: { data: 'split_data', field: 'x' }, + range: 'width', + }; + } + switch (chartType) { case 'bar': return {