diff --git a/web/src/components/ui/charts/BusterChartJS/ChartJSTheme.ts b/web/src/components/ui/charts/BusterChartJS/ChartJSTheme.ts index a267b6f07..d2aa0611a 100644 --- a/web/src/components/ui/charts/BusterChartJS/ChartJSTheme.ts +++ b/web/src/components/ui/charts/BusterChartJS/ChartJSTheme.ts @@ -20,8 +20,7 @@ import { BubbleController, PieController, ScatterController, - DoughnutController, - scales + DoughnutController } from 'chart.js'; import { ChartMountedPlugin } from './core/plugins'; import ChartDeferred from 'chartjs-plugin-deferred'; diff --git a/web/src/components/ui/charts/stories/BusterChart.LineChart.stories.tsx b/web/src/components/ui/charts/stories/BusterChart.LineChart.stories.tsx index e2e614174..ce25442af 100644 --- a/web/src/components/ui/charts/stories/BusterChart.LineChart.stories.tsx +++ b/web/src/components/ui/charts/stories/BusterChart.LineChart.stories.tsx @@ -26,7 +26,7 @@ export const Default: Story = { y: ['revenue', 'profit', 'customers'], category: [] }, - className: 'w-[800px] h-[400px]', + className: 'resize overflow-auto min-w-[250px] h-[400px]', columnLabelFormats: { date: { columnType: 'date', @@ -128,12 +128,12 @@ export const MultiYearDate: Story = { y: ['value'], category: [] }, - className: 'w-[800px] h-[400px]', + className: 'w-[800px] h-[400px] resize overflow-auto', columnLabelFormats: { date: { columnType: 'date', style: 'date', - dateFormat: 'YYYY' // Show only year for multi-year view + dateFormat: 'auto' // Show only year for multi-year view } satisfies IColumnLabelFormat, value: { columnType: 'number', @@ -177,7 +177,7 @@ export const MultipleYAxes: Story = { date: { columnType: 'date', style: 'date', - dateFormat: 'MMM YYYY' + dateFormat: 'auto' } satisfies IColumnLabelFormat, revenue: { columnType: 'number', @@ -203,30 +203,74 @@ export const UnevenlySpacedDates: Story = { args: { selectedChartType: ChartType.Line, data: [ - { date: new Date('2024-01-05'), value: 120 }, - { date: new Date('2024-01-28'), value: 145 }, - { date: new Date('2024-02-15'), value: 160 }, - { date: new Date('2024-03-02'), value: 155 }, - { date: new Date('2024-04-18'), value: 180 }, - { date: new Date('2024-05-30'), value: 210 }, - { date: new Date('2024-07-12'), value: 195 }, - { date: new Date('2024-08-03'), value: 225 }, - { date: new Date('2024-09-22'), value: 240 }, - { date: new Date('2024-11-15'), value: 260 }, - { date: new Date('2024-12-28'), value: 280 }, - { date: new Date('2025-04-08'), value: 310 } + { date: new Date('2024-01-05').toISOString(), value: 120 }, + { date: new Date('2024-01-28').toISOString(), value: 145 }, + { date: new Date('2024-02-15').toISOString(), value: 160 }, + { date: new Date('2024-03-02').toISOString(), value: 155 }, + { date: new Date('2024-04-18').toISOString(), value: 180 }, + { date: new Date('2024-05-30').toISOString(), value: 210 }, + { date: new Date('2024-07-12').toISOString(), value: 195 }, + { date: new Date('2024-08-03').toISOString(), value: 225 }, + { date: new Date('2024-09-22').toISOString(), value: 240 }, + { date: new Date('2024-11-15').toISOString(), value: 260 }, + { date: new Date('2024-12-28').toISOString(), value: 280 }, + { date: new Date('2025-04-08').toISOString(), value: 310 } ], barAndLineAxis: { x: ['date'], y: ['value'], category: [] }, - className: 'w-[800px] h-[400px]', + className: 'w-[800px] h-[400px]', + + columnSettings: { + value: { + lineSymbolSize: 5 + } + }, columnLabelFormats: { date: { columnType: 'date', style: 'date', - dateFormat: 'll' // Full date format to show uneven spacing clearly + dateFormat: 'auto' // Full date format to show uneven spacing clearly + } satisfies IColumnLabelFormat, + value: { + columnType: 'number', + style: 'number', + minimumFractionDigits: 0, + maximumFractionDigits: 0 + } satisfies IColumnLabelFormat + } + } +}; + +// Closely spaced dates +export const CloselySpacedDates: Story = { + args: { + selectedChartType: ChartType.Line, + data: [ + { date: new Date('2024-01-01').toISOString(), value: 120 }, + { date: new Date('2024-01-03').toISOString(), value: 145 }, + { date: new Date('2024-01-05').toISOString(), value: 160 }, + { date: new Date('2024-01-07').toISOString(), value: 155 }, + { date: new Date('2024-01-12').toISOString(), value: 180 } + ], + barAndLineAxis: { + x: ['date'], + y: ['value'], + category: [] + }, + className: 'w-[800px] h-[400px] resize overflow-auto', + columnSettings: { + value: { + lineSymbolSize: 5 + } + }, + columnLabelFormats: { + date: { + columnType: 'date', + style: 'date', + dateFormat: 'll' // Full date format to show spacing clearly } satisfies IColumnLabelFormat, value: { columnType: 'number', @@ -243,18 +287,18 @@ export const WithCategory: Story = { args: { selectedChartType: ChartType.Line, data: [ - { month: new Date('2024-01-01'), sales: 1200, region: 'North' }, - { month: new Date('2024-02-01'), sales: 1400, region: 'North' }, - { month: new Date('2024-03-01'), sales: 1600, region: 'North' }, - { month: new Date('2024-01-01'), sales: 800, region: 'South' }, - { month: new Date('2024-02-01'), sales: 900, region: 'South' }, - { month: new Date('2024-03-01'), sales: 1100, region: 'South' }, - { month: new Date('2024-01-01'), sales: 1500, region: 'East' }, - { month: new Date('2024-02-01'), sales: 1700, region: 'East' }, - { month: new Date('2024-03-01'), sales: 1900, region: 'East' }, - { month: new Date('2024-01-01'), sales: 1000, region: 'West' }, - { month: new Date('2024-02-01'), sales: 1300, region: 'West' }, - { month: new Date('2024-03-01'), sales: 1500, region: 'West' } + { month: new Date('2024-01-01').toISOString(), sales: 1200, region: 'North' }, + { month: new Date('2024-02-01').toISOString(), sales: 1400, region: 'North' }, + { month: new Date('2024-03-01').toISOString(), sales: 1600, region: 'North' }, + { month: new Date('2024-01-01').toISOString(), sales: 800, region: 'South' }, + { month: new Date('2024-02-01').toISOString(), sales: 900, region: 'South' }, + { month: new Date('2024-03-01').toISOString(), sales: 1100, region: 'South' }, + { month: new Date('2024-01-01').toISOString(), sales: 1500, region: 'East' }, + { month: new Date('2024-02-01').toISOString(), sales: 1700, region: 'East' }, + { month: new Date('2024-03-01').toISOString(), sales: 1900, region: 'East' }, + { month: new Date('2024-01-01').toISOString(), sales: 1000, region: 'West' }, + { month: new Date('2024-02-01').toISOString(), sales: 1300, region: 'West' }, + { month: new Date('2024-03-01').toISOString(), sales: 1500, region: 'West' } ], barAndLineAxis: { x: ['month'], @@ -266,7 +310,7 @@ export const WithCategory: Story = { month: { columnType: 'date', style: 'date', - dateFormat: 'MMM YYYY' + dateFormat: 'auto' } satisfies IColumnLabelFormat, sales: { columnType: 'number', @@ -286,15 +330,60 @@ export const MultipleYAxesWithCategory: Story = { args: { selectedChartType: ChartType.Line, data: [ - { date: new Date('2024-01-01'), revenue: 1200, satisfaction: 4.2, product: 'Hardware' }, - { date: new Date('2024-02-01'), revenue: 1400, satisfaction: 4.3, product: 'Hardware' }, - { date: new Date('2024-03-01'), revenue: 1600, satisfaction: 4.4, product: 'Hardware' }, - { date: new Date('2024-01-01'), revenue: 800, satisfaction: 4.7, product: 'Software' }, - { date: new Date('2024-02-01'), revenue: 1000, satisfaction: 4.8, product: 'Software' }, - { date: new Date('2024-03-01'), revenue: 1200, satisfaction: 4.9, product: 'Software' }, - { date: new Date('2024-01-01'), revenue: 2000, satisfaction: 4.0, product: 'Services' }, - { date: new Date('2024-02-01'), revenue: 2200, satisfaction: 4.1, product: 'Services' }, - { date: new Date('2024-03-01'), revenue: 2400, satisfaction: 4.2, product: 'Services' } + { + date: new Date('2024-01-01').toISOString(), + revenue: 1200, + satisfaction: 4.2, + product: 'Hardware' + }, + { + date: new Date('2024-02-01').toISOString(), + revenue: 1400, + satisfaction: 4.3, + product: 'Hardware' + }, + { + date: new Date('2024-03-01').toISOString(), + revenue: 1600, + satisfaction: 4.4, + product: 'Hardware' + }, + { + date: new Date('2024-01-01').toISOString(), + revenue: 800, + satisfaction: 4.7, + product: 'Software' + }, + { + date: new Date('2024-02-01').toISOString(), + revenue: 1000, + satisfaction: 4.8, + product: 'Software' + }, + { + date: new Date('2024-03-01').toISOString(), + revenue: 1200, + satisfaction: 4.9, + product: 'Software' + }, + { + date: new Date('2024-01-01').toISOString(), + revenue: 2000, + satisfaction: 4.0, + product: 'Services' + }, + { + date: new Date('2024-02-01').toISOString(), + revenue: 2200, + satisfaction: 4.1, + product: 'Services' + }, + { + date: new Date('2024-03-01').toISOString(), + revenue: 2400, + satisfaction: 4.2, + product: 'Services' + } ], barAndLineAxis: { x: ['date'], @@ -306,7 +395,7 @@ export const MultipleYAxesWithCategory: Story = { date: { columnType: 'date', style: 'date', - dateFormat: 'MMM YYYY' + dateFormat: 'auto' } satisfies IColumnLabelFormat, revenue: { columnType: 'number', @@ -356,7 +445,7 @@ export const NumericMonthX: Story = { month: { columnType: 'number', style: 'date', - dateFormat: 'MMM', + dateFormat: 'auto', minimumFractionDigits: 0, maximumFractionDigits: 0 } satisfies IColumnLabelFormat, @@ -389,7 +478,7 @@ export const PercentageStackedLineSingle: Story = { date: { columnType: 'number', style: 'date', - dateFormat: 'LLL', + dateFormat: 'auto', minimumFractionDigits: 0, maximumFractionDigits: 0 } satisfies IColumnLabelFormat, diff --git a/web/src/mocks/chart/chartMocks.ts b/web/src/mocks/chart/chartMocks.ts index c80d69735..bc41fc544 100644 --- a/web/src/mocks/chart/chartMocks.ts +++ b/web/src/mocks/chart/chartMocks.ts @@ -1,55 +1,69 @@ import { IDataResult } from '@/api/asset_interfaces/metric/interfaces'; -import { faker } from '@faker-js/faker'; +import dayjs from 'dayjs'; // Helper to generate dates for time series const generateDates = (count: number) => { const dates: Date[] = []; - const startDate = new Date(); - startDate.setDate(startDate.getDate() - count); + const startDate = dayjs('April 1, 2025').toDate(); for (let i = 0; i < count; i++) { - const date = new Date(startDate); - date.setDate(date.getDate() + i); + const date = dayjs(startDate).add(i, 'day').toDate(); dates.push(date); } return dates; }; -// Line chart mock data +// Helper to add controlled noise to values +const addNoise = (value: number, variabilityPercent: number = 10): number => { + const maxNoise = value * (variabilityPercent / 100); + const noise = Math.sin(value) * maxNoise; // Using sin for pseudo-random but predictable noise + return Math.round(value + noise); +}; + +// Line chart mock data with predictable growth patterns and controlled variability export const generateLineChartData = (pointCount = 10): IDataResult => { const dates = generateDates(pointCount); - return dates.map((date) => ({ - date: date.toISOString(), - revenue: faker.number.int({ min: 1000, max: 10000 }), - profit: faker.number.int({ min: 100, max: 5000 }), - customers: faker.number.int({ min: 50, max: 500 }) - })); + return dates.map((date, index) => { + const baseRevenue = 1000 + index * 500; + const baseProfit = 100 + index * 200; + const baseCustomers = 50 + index * 25; + + return { + date: date.toISOString(), + revenue: addNoise(baseRevenue, 15), // 15% variability + profit: addNoise(baseProfit, 20), // 20% variability + customers: addNoise(baseCustomers, 10) // 10% variability + }; + }); }; -// Bar chart mock data +// Bar chart mock data with consistent categories export const generateBarChartData = (categoryCount = 6): IDataResult => { - return Array.from({ length: categoryCount }, () => ({ - category: faker.commerce.department(), - sales: faker.number.int({ min: 1000, max: 10000 }), - units: faker.number.int({ min: 50, max: 500 }), - returns: faker.number.int({ min: 100, max: 500 }) + const categories = ['Electronics', 'Clothing', 'Food', 'Books', 'Sports', 'Home', 'Beauty']; + return Array.from({ length: categoryCount }, (_, index) => ({ + category: categories[index % categories.length], + sales: 1000 + index * 1000, // Increases by 1000 each category + units: 50 + index * 50, // Increases by 50 each category + returns: 100 + index * 25 // Increases by 25 each category })); }; -// Pie chart mock data +// Pie chart mock data with fixed segments export const generatePieChartData = (segmentCount = 5): IDataResult => { - return Array.from({ length: segmentCount }, () => ({ - segment: faker.commerce.product(), - value: faker.number.int({ min: 100, max: 1000 }) + const segments = ['Product A', 'Product B', 'Product C', 'Product D', 'Product E']; + return Array.from({ length: segmentCount }, (_, index) => ({ + segment: segments[index % segments.length], + value: 100 + index * 200 // Increases by 200 each segment })); }; -// Scatter chart mock data +// Scatter chart mock data with predictable patterns export const generateScatterChartData = (pointCount = 30): IDataResult => { - return Array.from({ length: pointCount }, () => ({ - x: faker.number.float({ min: 0, max: 100, fractionDigits: 1 }), - y: faker.number.float({ min: 0, max: 100, fractionDigits: 1 }), - size: faker.number.int({ min: 10, max: 50 }), - category: faker.helpers.arrayElement(['Electronics', 'Clothing', 'Home Goods']) + const categories = ['Electronics', 'Clothing', 'Home Goods']; + return Array.from({ length: pointCount }, (_, index) => ({ + x: (index % 10) * 10, // Values from 0-90 in steps of 10 + y: Math.floor(index / 10) * 10, // Creates a grid pattern + size: 10 + (index % 5) * 10, // Sizes cycle between 10-50 in steps of 10 + category: categories[index % categories.length] })); };