mirror of https://github.com/buster-so/buster.git
added different colorBy config
This commit is contained in:
parent
5dc479882c
commit
519f48929f
|
@ -2029,4 +2029,195 @@ describe('aggregateAndCreateDatasets', () => {
|
|||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
describe('createDatasetsByColorThenAggregate', () => {
|
||||
it('should create separate datasets for each color value', () => {
|
||||
const testData = [
|
||||
{ month: 'Jan', sales: 100, productType: 'Electronics' },
|
||||
{ month: 'Jan', sales: 80, productType: 'Clothing' },
|
||||
{ month: 'Feb', sales: 120, productType: 'Electronics' },
|
||||
{ month: 'Feb', sales: 90, productType: 'Clothing' },
|
||||
];
|
||||
|
||||
const colorConfig = {
|
||||
field: 'productType',
|
||||
mapping: new Map([
|
||||
['Electronics', '#ff0000'],
|
||||
['Clothing', '#00ff00'],
|
||||
]),
|
||||
};
|
||||
|
||||
const result = aggregateAndCreateDatasets(
|
||||
testData,
|
||||
{
|
||||
x: ['month'],
|
||||
y: ['sales'],
|
||||
},
|
||||
{},
|
||||
false,
|
||||
colorConfig
|
||||
);
|
||||
|
||||
// Should have 2 datasets (one for each color/productType)
|
||||
expect(result.datasets).toHaveLength(2);
|
||||
|
||||
// Check first dataset (Electronics)
|
||||
expect(result.datasets[0].colors).toBe('#ff0000');
|
||||
expect(result.datasets[0].data).toEqual([100, 120]);
|
||||
expect(result.datasets[0].label).toEqual([{ key: 'productType', value: 'Electronics' }]);
|
||||
|
||||
// Check second dataset (Clothing)
|
||||
expect(result.datasets[1].colors).toBe('#00ff00');
|
||||
expect(result.datasets[1].data).toEqual([80, 90]);
|
||||
expect(result.datasets[1].label).toEqual([{ key: 'productType', value: 'Clothing' }]);
|
||||
|
||||
// Check ticks are consistent across all datasets
|
||||
expect(result.ticks).toEqual([['Jan'], ['Feb']]);
|
||||
expect(result.ticksKey).toEqual([{ key: 'month', value: '' }]);
|
||||
});
|
||||
|
||||
it('should handle multiple y-axes with color configuration', () => {
|
||||
const testData = [
|
||||
{ month: 'Jan', sales: 100, profit: 20, region: 'North' },
|
||||
{ month: 'Jan', sales: 80, profit: 15, region: 'South' },
|
||||
{ month: 'Feb', sales: 120, profit: 25, region: 'North' },
|
||||
{ month: 'Feb', sales: 90, profit: 18, region: 'South' },
|
||||
];
|
||||
|
||||
const colorConfig = {
|
||||
field: 'region',
|
||||
mapping: new Map([
|
||||
['North', '#0000ff'],
|
||||
['South', '#ff00ff'],
|
||||
]),
|
||||
};
|
||||
|
||||
const result = aggregateAndCreateDatasets(
|
||||
testData,
|
||||
{
|
||||
x: ['month'],
|
||||
y: ['sales', 'profit'],
|
||||
},
|
||||
{},
|
||||
false,
|
||||
colorConfig
|
||||
);
|
||||
|
||||
// Should have 4 datasets (2 metrics × 2 regions)
|
||||
expect(result.datasets).toHaveLength(4);
|
||||
|
||||
// Find sales datasets
|
||||
const salesDatasets = result.datasets.filter(d => d.dataKey === 'sales');
|
||||
const profitDatasets = result.datasets.filter(d => d.dataKey === 'profit');
|
||||
|
||||
expect(salesDatasets).toHaveLength(2);
|
||||
expect(profitDatasets).toHaveLength(2);
|
||||
|
||||
// Check labels include both metric and color field
|
||||
const northSalesDataset = salesDatasets.find(d => d.colors === '#0000ff');
|
||||
expect(northSalesDataset?.label).toEqual([
|
||||
{ key: 'sales', value: '' },
|
||||
{ key: 'region', value: 'North' },
|
||||
]);
|
||||
|
||||
const southProfitDataset = profitDatasets.find(d => d.colors === '#ff00ff');
|
||||
expect(southProfitDataset?.label).toEqual([
|
||||
{ key: 'profit', value: '' },
|
||||
{ key: 'region', value: 'South' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle categories combined with color configuration', () => {
|
||||
const testData = [
|
||||
{ quarter: 'Q1', sales: 100, product: 'A', region: 'North' },
|
||||
{ quarter: 'Q1', sales: 80, product: 'B', region: 'North' },
|
||||
{ quarter: 'Q2', sales: 120, product: 'A', region: 'North' },
|
||||
{ quarter: 'Q1', sales: 90, product: 'A', region: 'South' },
|
||||
{ quarter: 'Q2', sales: 95, product: 'A', region: 'South' },
|
||||
];
|
||||
|
||||
const colorConfig = {
|
||||
field: 'region',
|
||||
mapping: new Map([
|
||||
['North', '#aabbcc'],
|
||||
['South', '#ddeeff'],
|
||||
]),
|
||||
};
|
||||
|
||||
const result = aggregateAndCreateDatasets(
|
||||
testData,
|
||||
{
|
||||
x: ['quarter'],
|
||||
y: ['sales'],
|
||||
category: ['product'],
|
||||
},
|
||||
{},
|
||||
false,
|
||||
colorConfig
|
||||
);
|
||||
|
||||
// Should have 3 datasets (North: A, B; South: A)
|
||||
expect(result.datasets).toHaveLength(3);
|
||||
|
||||
// Check that labels include product category AND color field
|
||||
const northProductADataset = result.datasets.find(
|
||||
d => d.colors === '#aabbcc' && d.label.some(l => l.key === 'product' && l.value === 'A')
|
||||
);
|
||||
|
||||
expect(northProductADataset).toBeDefined();
|
||||
expect(northProductADataset?.label).toEqual([
|
||||
{ key: 'region', value: 'North' },
|
||||
{ key: 'product', value: 'A' },
|
||||
]);
|
||||
expect(northProductADataset?.data).toEqual([100, 120]);
|
||||
|
||||
const southProductADataset = result.datasets.find(
|
||||
d => d.colors === '#ddeeff' && d.label.some(l => l.key === 'product' && l.value === 'A')
|
||||
);
|
||||
|
||||
expect(southProductADataset).toBeDefined();
|
||||
expect(southProductADataset?.data).toEqual([90, 95]);
|
||||
});
|
||||
|
||||
it('should handle missing color values by filtering unmapped data', () => {
|
||||
const testData = [
|
||||
{ month: 'Jan', sales: 100, category: 'A' },
|
||||
{ month: 'Jan', sales: 80, category: 'C' }, // C is not mapped to a color
|
||||
{ month: 'Feb', sales: 120, category: 'A' },
|
||||
{ month: 'Feb', sales: 90, category: 'C' }, // C is not mapped to a color
|
||||
];
|
||||
|
||||
const colorConfig = {
|
||||
field: 'category',
|
||||
mapping: new Map([
|
||||
['A', '#ff0000'],
|
||||
['B', '#00ff00'], // B doesn't exist in data but is mapped
|
||||
]),
|
||||
};
|
||||
|
||||
const result = aggregateAndCreateDatasets(
|
||||
testData,
|
||||
{
|
||||
x: ['month'],
|
||||
y: ['sales'],
|
||||
},
|
||||
{},
|
||||
false,
|
||||
colorConfig
|
||||
);
|
||||
|
||||
// Should have 1 dataset (only for A which has data and color mapping)
|
||||
expect(result.datasets).toHaveLength(1);
|
||||
|
||||
// Dataset A should have real data
|
||||
const datasetA = result.datasets.find(d => d.colors === '#ff0000');
|
||||
expect(datasetA?.data).toEqual([100, 120]);
|
||||
expect(datasetA?.label).toEqual([{ key: 'category', value: 'A' }]);
|
||||
|
||||
// Category C data should be filtered out since it's not in the color mapping
|
||||
// Check ticks are still correct for all x-axis values from original data
|
||||
expect(result.ticks).toEqual([['Jan'], ['Feb']]);
|
||||
expect(result.ticksKey).toEqual([{ key: 'month', value: '' }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -423,7 +423,7 @@ export function aggregateAndCreateDatasets<
|
|||
* Creates datasets by splitting data by color values first, then aggregating each subset
|
||||
* This preserves the granular color information needed for proper aggregation
|
||||
*/
|
||||
function createDatasetsByColorThenAggregate<
|
||||
export function createDatasetsByColorThenAggregate<
|
||||
T extends Record<string, string | number | null | Date | undefined>,
|
||||
>(
|
||||
data: T[],
|
||||
|
@ -724,102 +724,6 @@ function createDatasetsByColorThenAggregate<
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates separate datasets for each unique color value when colorBy is specified
|
||||
* Optimized to minimize redundant operations and loops
|
||||
*/
|
||||
function createSeparateDatasetsByColor<
|
||||
T extends Record<string, string | number | null | Date | undefined>,
|
||||
>(
|
||||
datasets: DatasetOption[],
|
||||
originalData: T[],
|
||||
colorConfig: { field: string; mapping: Map<string, string> },
|
||||
xGroups: Array<{ id: string; rec: Record<string, string>; rows: T[] }>
|
||||
): DatasetOption[] {
|
||||
const { field: colorByField, mapping: colorMapping } = colorConfig;
|
||||
|
||||
// Get unique color values that have valid colors mapped
|
||||
const uniqueColorValues = Array.from(
|
||||
new Set(
|
||||
originalData
|
||||
.map((row) => row[colorByField])
|
||||
.filter((val) => val !== null && val !== undefined)
|
||||
.map(String)
|
||||
)
|
||||
).filter((colorValue) => colorMapping.has(colorValue)); // Only include values with mapped colors
|
||||
|
||||
if (uniqueColorValues.length === 0) {
|
||||
return datasets; // No valid color values, return original datasets
|
||||
}
|
||||
|
||||
// Pre-compute which x-groups contain each color value to avoid repeated calculations
|
||||
const colorValuesByXGroup: Map<number, Set<string>> = new Map();
|
||||
|
||||
for (let dataIndex = 0; dataIndex < xGroups.length; dataIndex++) {
|
||||
const xGroup = xGroups[dataIndex];
|
||||
const colorValuesInGroup = new Set<string>();
|
||||
|
||||
for (const row of xGroup.rows) {
|
||||
const colorValue = row[colorByField];
|
||||
if (colorValue !== null && colorValue !== undefined) {
|
||||
colorValuesInGroup.add(String(colorValue));
|
||||
}
|
||||
}
|
||||
|
||||
colorValuesByXGroup.set(dataIndex, colorValuesInGroup);
|
||||
}
|
||||
|
||||
const newDatasets: DatasetOption[] = [];
|
||||
|
||||
// For each original dataset, create separate datasets for each color value
|
||||
for (const originalDataset of datasets) {
|
||||
for (let i = 0; i < uniqueColorValues.length; i++) {
|
||||
const colorValue = uniqueColorValues[i];
|
||||
const color = colorMapping.get(colorValue); // Safe to use ! since we filtered above
|
||||
|
||||
// Create new data array with nulls where the color value doesn't match
|
||||
const newData: (number | null)[] = [];
|
||||
const newTooltipData: (typeof originalDataset.tooltipData)[0][] = [];
|
||||
|
||||
// Use pre-computed lookup for efficiency
|
||||
for (let dataIndex = 0; dataIndex < originalDataset.data.length; dataIndex++) {
|
||||
const colorValuesInGroup = colorValuesByXGroup.get(dataIndex);
|
||||
const hasColorValue = colorValuesInGroup?.has(colorValue) ?? false;
|
||||
|
||||
if (hasColorValue) {
|
||||
// Keep the original data point
|
||||
newData.push(originalDataset.data[dataIndex]);
|
||||
newTooltipData.push(originalDataset.tooltipData[dataIndex]);
|
||||
} else {
|
||||
// Set to null where color value doesn't exist
|
||||
newData.push(null);
|
||||
newTooltipData.push([]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new dataset ID and label
|
||||
const newId = `${originalDataset.id}${i + 1}`;
|
||||
const newLabel = [
|
||||
{
|
||||
key: colorByField,
|
||||
value: colorValue,
|
||||
},
|
||||
];
|
||||
|
||||
newDatasets.push({
|
||||
...originalDataset,
|
||||
id: newId,
|
||||
label: newLabel,
|
||||
data: newData,
|
||||
tooltipData: newTooltipData,
|
||||
colors: color, // Single color string instead of array
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return newDatasets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a consistent dataset ID by combining the measure field with category or x-axis information
|
||||
*/
|
||||
|
|
|
@ -11,7 +11,7 @@ describe('useColorMapping', () => {
|
|||
|
||||
it('should create color mapping when colorBy is present', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useColorMapping(mockData, { columnId: 'level' }, ['#FF0000', '#00FF00', '#0000FF'])
|
||||
useColorMapping(mockData, ['level'], ['#FF0000', '#00FF00', '#0000FF'])
|
||||
);
|
||||
|
||||
expect(result.current.hasColorMapping).toBe(true);
|
||||
|
@ -36,16 +36,14 @@ describe('useColorMapping', () => {
|
|||
});
|
||||
|
||||
it('should not create color mapping when colors array is empty', () => {
|
||||
const { result } = renderHook(() => useColorMapping(mockData, { columnId: 'level' }, []));
|
||||
const { result } = renderHook(() => useColorMapping(mockData, ['level'], []));
|
||||
|
||||
expect(result.current.hasColorMapping).toBe(false);
|
||||
expect(result.current.colorMapping.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle undefined colors gracefully', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useColorMapping(mockData, { columnId: 'level' }, undefined as any)
|
||||
);
|
||||
const { result } = renderHook(() => useColorMapping(mockData, ['level'], undefined as any));
|
||||
|
||||
expect(result.current.hasColorMapping).toBe(false);
|
||||
expect(result.current.colorMapping.size).toBe(0);
|
||||
|
@ -53,7 +51,7 @@ describe('useColorMapping', () => {
|
|||
|
||||
it('should return undefined for values not found', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useColorMapping(mockData, { columnId: 'level' }, ['#FF0000', '#00FF00'])
|
||||
useColorMapping(mockData, ['level'], ['#FF0000', '#00FF00'])
|
||||
);
|
||||
|
||||
expect(result.current.getColorForValue('NonExistent')).toBeUndefined();
|
||||
|
@ -71,7 +69,7 @@ describe('useColorMapping', () => {
|
|||
const { result } = renderHook(() =>
|
||||
useColorMapping(
|
||||
dataWithManyLevels,
|
||||
{ columnId: 'level' },
|
||||
['level'],
|
||||
['#FF0000', '#00FF00'] // Only 2 colors for 4 levels
|
||||
)
|
||||
);
|
||||
|
@ -82,4 +80,37 @@ describe('useColorMapping', () => {
|
|||
expect(result.current.getColorForValue('Level 3')).toBe('#FF0000'); // Cycles back
|
||||
expect(result.current.getColorForValue('Level 4')).toBe('#00FF00'); // Cycles back
|
||||
});
|
||||
|
||||
it('should handle empty data array gracefully', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useColorMapping([], ['level'], ['#FF0000', '#00FF00', '#0000FF'])
|
||||
);
|
||||
|
||||
expect(result.current.hasColorMapping).toBe(false);
|
||||
expect(result.current.colorMapping.size).toBe(0);
|
||||
expect(result.current.getColorForValue('Level 1')).toBeUndefined();
|
||||
expect(result.current.colorConfig).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle data with null and undefined values in colorBy field', () => {
|
||||
const dataWithNullValues = [
|
||||
{ month: 'Jan', sales: 100, level: 'Level 1' },
|
||||
{ month: 'Feb', sales: 200, level: null },
|
||||
{ month: 'Mar', sales: 300, level: null },
|
||||
{ month: 'Apr', sales: 400, level: 'Level 2' },
|
||||
{ month: 'May', sales: 500, level: null }, // missing level field (set as null)
|
||||
];
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useColorMapping(dataWithNullValues, ['level'], ['#FF0000', '#00FF00', '#0000FF'])
|
||||
);
|
||||
|
||||
// Should only create mappings for non-null/undefined values
|
||||
expect(result.current.hasColorMapping).toBe(true);
|
||||
expect(result.current.colorMapping.size).toBe(2); // Only 'Level 1' and 'Level 2'
|
||||
expect(result.current.getColorForValue('Level 1')).toBe('#FF0000');
|
||||
expect(result.current.getColorForValue('Level 2')).toBe('#00FF00');
|
||||
expect(result.current.getColorForValue(null)).toBeUndefined();
|
||||
expect(result.current.getColorForValue(undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,19 +11,24 @@ import type { BusterChartProps } from '../../BusterChart.types';
|
|||
* @param colors - Array of available colors
|
||||
* @returns Object with color mapping and a function to get color for a value
|
||||
*/
|
||||
export function useColorMapping(
|
||||
export const useColorMapping = (
|
||||
data: NonNullable<BusterChartProps['data']>,
|
||||
colorBy: ColorBy | null,
|
||||
colors: string[]
|
||||
) {
|
||||
): {
|
||||
hasColorMapping: boolean;
|
||||
colorMapping: Map<string, string>;
|
||||
colorConfig: { field: string; mapping: Map<string, string> } | undefined;
|
||||
getColorForValue: (value: string | number | null | undefined) => string | undefined;
|
||||
} => {
|
||||
// Memoize the unique values extraction
|
||||
const uniqueColorValues = useMemo(() => {
|
||||
if (!colorBy?.columnId || !colors || colors.length === 0) {
|
||||
if (!colorBy || !colors || colors.length === 0 || colorBy.length === 0) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
const values = new Set<string>();
|
||||
const columnId = colorBy.columnId;
|
||||
const columnId = colorBy[0];
|
||||
|
||||
for (const row of data) {
|
||||
const value = row[columnId];
|
||||
|
@ -33,11 +38,11 @@ export function useColorMapping(
|
|||
}
|
||||
|
||||
return values;
|
||||
}, [data, colorBy?.columnId, colors?.length]);
|
||||
}, [data, colorBy, colors?.length]);
|
||||
|
||||
// Memoize the color mapping creation
|
||||
const colorMapping = useMemo(() => {
|
||||
if (!colorBy?.columnId || !colors || colors.length === 0 || uniqueColorValues.size === 0) {
|
||||
if (!colorBy || !colors || colors.length === 0 || uniqueColorValues.size === 0) {
|
||||
return new Map<string, string>();
|
||||
}
|
||||
|
||||
|
@ -49,18 +54,18 @@ export function useColorMapping(
|
|||
});
|
||||
|
||||
return mapping;
|
||||
}, [uniqueColorValues, colors, colorBy?.columnId]);
|
||||
}, [uniqueColorValues, colors, colorBy]);
|
||||
|
||||
// Create colorConfig for dataset aggregation
|
||||
const colorConfig = useMemo(() => {
|
||||
if (!colorBy?.columnId || !colors || colors.length === 0 || colorMapping.size === 0) {
|
||||
if (!colorBy || !colors || colors.length === 0 || colorMapping.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
field: colorBy.columnId,
|
||||
field: colorBy[0],
|
||||
mapping: colorMapping,
|
||||
};
|
||||
}, [colorBy?.columnId, colors?.length, colorMapping]);
|
||||
}, [colorBy, colors?.length, colorMapping]);
|
||||
|
||||
// Return the mapping and helper functions
|
||||
return useMemo(
|
||||
|
@ -77,4 +82,4 @@ export function useColorMapping(
|
|||
}),
|
||||
[colorMapping, colors, colorConfig]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -50,6 +50,7 @@ export enum SelectAxisContainerId {
|
|||
Tooltip = 'tooltip',
|
||||
Y2Axis = 'y2Axis',
|
||||
Metric = 'metric',
|
||||
ColorBy = 'colorBy',
|
||||
}
|
||||
|
||||
export const zoneIdToAxis: Record<SelectAxisContainerId, string> = {
|
||||
|
@ -61,6 +62,7 @@ export const zoneIdToAxis: Record<SelectAxisContainerId, string> = {
|
|||
[SelectAxisContainerId.Tooltip]: 'tooltip',
|
||||
[SelectAxisContainerId.Y2Axis]: 'y2',
|
||||
[SelectAxisContainerId.Metric]: 'metric',
|
||||
[SelectAxisContainerId.ColorBy]: 'colorBy',
|
||||
};
|
||||
|
||||
export const chartTypeToAxis: Record<
|
||||
|
|
|
@ -15,6 +15,7 @@ export const ZoneIdToTitle: Record<SelectAxisContainerId, string> = {
|
|||
[SelectAxisContainerId.Tooltip]: 'Tooltip',
|
||||
[SelectAxisContainerId.Available]: 'Available',
|
||||
[SelectAxisContainerId.Metric]: 'Metric',
|
||||
[SelectAxisContainerId.ColorBy]: 'Color By',
|
||||
};
|
||||
|
||||
const makeDropZone = (
|
||||
|
@ -62,6 +63,9 @@ const makeTooltipDropZone = (tooltipItems: string[] | null | undefined): DropZon
|
|||
const makeY2AxisDropZone = (y2Items: string[] | null | undefined): DropZone =>
|
||||
makeDropZone(SelectAxisContainerId.Y2Axis, y2Items ?? EMPTY_ARRAY);
|
||||
|
||||
const makeColorByDropZone = (colorByItems: string[] | null | undefined): DropZone =>
|
||||
makeDropZone(SelectAxisContainerId.ColorBy, colorByItems ?? EMPTY_ARRAY);
|
||||
|
||||
export const chartTypeToDropZones: Record<
|
||||
ChartConfigProps['selectedChartType'],
|
||||
(
|
||||
|
@ -80,6 +84,7 @@ export const chartTypeToDropZones: Record<
|
|||
isHorizontalBar
|
||||
? makeReverseXAxisDropZone(_selectedAxis.x)
|
||||
: makeYAxisDropZone(_selectedAxis.y),
|
||||
makeColorByDropZone(_selectedAxis.colorBy?.columnId),
|
||||
makeCategoryAxisDropZone(_selectedAxis.category),
|
||||
makeTooltipDropZone(_selectedAxis.tooltip),
|
||||
];
|
||||
|
|
|
@ -1,33 +1,6 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const ColorBySchema = z
|
||||
.object({
|
||||
/**
|
||||
* Column ID whose values will determine colors for chart elements.
|
||||
* Use this when you want to apply colors based on a column's values
|
||||
* without creating separate series (unlike category which creates multiple series).
|
||||
*
|
||||
* Example use cases:
|
||||
* - Color bars by status (red for "failed", green for "success")
|
||||
* - Color points by priority level
|
||||
* - Apply conditional formatting based on categorical values
|
||||
*/
|
||||
columnId: z.string().nullable().optional(),
|
||||
/**
|
||||
* Optional override for specific value-to-color mappings.
|
||||
* This allows custom color assignment for specific values.
|
||||
*/
|
||||
overrideColorByValue: z
|
||||
.object({
|
||||
// The specific value to override color for
|
||||
value: z.string(),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
})
|
||||
.nullable()
|
||||
.optional()
|
||||
.default(null);
|
||||
export const ColorBySchema = z.tuple([z.string()]).or(z.array(z.string()).length(0)).default([]);
|
||||
|
||||
export const BarAndLineAxisSchema = z
|
||||
.object({
|
||||
|
@ -63,7 +36,7 @@ export const BarAndLineAxisSchema = z
|
|||
y: [],
|
||||
category: [],
|
||||
tooltip: null,
|
||||
colorBy: null,
|
||||
colorBy: [],
|
||||
});
|
||||
|
||||
export const ScatterAxisSchema = z
|
||||
|
@ -143,7 +116,7 @@ export const ComboChartAxisSchema = z
|
|||
y2: [],
|
||||
category: [],
|
||||
tooltip: null,
|
||||
colorBy: null,
|
||||
colorBy: [],
|
||||
});
|
||||
|
||||
export const PieChartAxisSchema = z
|
||||
|
|
Loading…
Reference in New Issue