@takaro/lib-components 0.3.3 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/package.json +1 -1
  2. package/src/components/actions/Dropdown/useDropdown.tsx +8 -1
  3. package/src/components/charts/echarts/EChartsArea.stories.tsx +139 -0
  4. package/src/components/charts/echarts/EChartsArea.tsx +139 -0
  5. package/src/components/charts/echarts/EChartsBar.stories.tsx +141 -0
  6. package/src/components/charts/echarts/EChartsBar.tsx +133 -0
  7. package/src/components/charts/echarts/EChartsBase.tsx +265 -0
  8. package/src/components/charts/echarts/EChartsFunnel.stories.tsx +164 -0
  9. package/src/components/charts/echarts/EChartsFunnel.tsx +114 -0
  10. package/src/components/charts/echarts/EChartsHeatmap.stories.tsx +168 -0
  11. package/src/components/charts/echarts/EChartsHeatmap.tsx +141 -0
  12. package/src/components/charts/echarts/EChartsLine.stories.tsx +132 -0
  13. package/src/components/charts/echarts/EChartsLine.tsx +111 -0
  14. package/src/components/charts/echarts/EChartsPie.stories.tsx +131 -0
  15. package/src/components/charts/echarts/EChartsPie.tsx +124 -0
  16. package/src/components/charts/echarts/EChartsRadialBar.stories.tsx +124 -0
  17. package/src/components/charts/echarts/EChartsRadialBar.tsx +118 -0
  18. package/src/components/charts/echarts/EChartsScatter.stories.tsx +166 -0
  19. package/src/components/charts/echarts/EChartsScatter.tsx +135 -0
  20. package/src/components/charts/echarts/index.ts +26 -0
  21. package/src/components/charts/index.tsx +26 -0
  22. package/src/components/data/Chip/__snapshots__/Chip.test.tsx.snap +4 -4
  23. package/src/components/feedback/Alert/__snapshots__/Alert.test.tsx.snap +1 -3
  24. package/src/components/feedback/Alert/index.tsx +1 -1
@@ -0,0 +1,265 @@
1
+ import { FC, useEffect, useRef, useMemo } from 'react';
2
+ import ReactEChartsCore from 'echarts-for-react/lib/core';
3
+ import * as echarts from 'echarts/core';
4
+ import { CanvasRenderer } from 'echarts/renderers';
5
+ import {
6
+ TitleComponent,
7
+ TooltipComponent,
8
+ GridComponent,
9
+ LegendComponent,
10
+ ToolboxComponent,
11
+ DataZoomComponent,
12
+ VisualMapComponent,
13
+ MarkLineComponent,
14
+ MarkPointComponent,
15
+ MarkAreaComponent,
16
+ PolarComponent,
17
+ } from 'echarts/components';
18
+ import {
19
+ LineChart,
20
+ BarChart,
21
+ PieChart,
22
+ ScatterChart,
23
+ RadarChart,
24
+ MapChart,
25
+ TreeChart,
26
+ TreemapChart,
27
+ GraphChart,
28
+ GaugeChart,
29
+ FunnelChart,
30
+ ParallelChart,
31
+ SankeyChart,
32
+ BoxplotChart,
33
+ CandlestickChart,
34
+ EffectScatterChart,
35
+ LinesChart,
36
+ HeatmapChart,
37
+ PictorialBarChart,
38
+ ThemeRiverChart,
39
+ SunburstChart,
40
+ CustomChart,
41
+ } from 'echarts/charts';
42
+ import { useTheme } from '../../../hooks';
43
+ import { ParentSize } from '@visx/responsive';
44
+ import { EChartsOption } from 'echarts';
45
+
46
+ // Register all components
47
+ echarts.use([
48
+ CanvasRenderer,
49
+ TitleComponent,
50
+ TooltipComponent,
51
+ GridComponent,
52
+ LegendComponent,
53
+ ToolboxComponent,
54
+ DataZoomComponent,
55
+ VisualMapComponent,
56
+ MarkLineComponent,
57
+ MarkPointComponent,
58
+ MarkAreaComponent,
59
+ PolarComponent,
60
+ LineChart,
61
+ BarChart,
62
+ PieChart,
63
+ ScatterChart,
64
+ RadarChart,
65
+ MapChart,
66
+ TreeChart,
67
+ TreemapChart,
68
+ GraphChart,
69
+ GaugeChart,
70
+ FunnelChart,
71
+ ParallelChart,
72
+ SankeyChart,
73
+ BoxplotChart,
74
+ CandlestickChart,
75
+ EffectScatterChart,
76
+ LinesChart,
77
+ HeatmapChart,
78
+ PictorialBarChart,
79
+ ThemeRiverChart,
80
+ SunburstChart,
81
+ CustomChart,
82
+ ]);
83
+
84
+ export interface EChartsBaseProps {
85
+ option: EChartsOption;
86
+ height?: string | number;
87
+ width?: string | number;
88
+ loading?: boolean;
89
+ onChartReady?: (instance: any) => void;
90
+ onEvents?: Record<string, (params: any) => void>;
91
+ style?: React.CSSProperties;
92
+ className?: string;
93
+ }
94
+
95
+ export const EChartsBase: FC<EChartsBaseProps> = ({
96
+ option,
97
+ height = '100%',
98
+ width = '100%',
99
+ loading = false,
100
+ onChartReady,
101
+ onEvents,
102
+ style,
103
+ className,
104
+ }) => {
105
+ const theme = useTheme();
106
+ const chartRef = useRef<ReactEChartsCore>(null);
107
+
108
+ // Create theme-aware default options
109
+ const themeOptions = useMemo(() => {
110
+ return {
111
+ backgroundColor: 'transparent',
112
+ textStyle: {
113
+ color: theme.colors.text,
114
+ fontFamily: 'inherit',
115
+ },
116
+ title: {
117
+ textStyle: {
118
+ color: theme.colors.text,
119
+ fontSize: 16,
120
+ fontWeight: 'bold',
121
+ },
122
+ subtextStyle: {
123
+ color: theme.colors.textAlt,
124
+ fontSize: 12,
125
+ },
126
+ },
127
+ legend: {
128
+ textStyle: {
129
+ color: theme.colors.text,
130
+ },
131
+ pageTextStyle: {
132
+ color: theme.colors.text,
133
+ },
134
+ },
135
+ tooltip: {
136
+ backgroundColor: theme.colors.backgroundAlt,
137
+ borderColor: theme.colors.backgroundAccent,
138
+ borderWidth: 1,
139
+ textStyle: {
140
+ color: theme.colors.text,
141
+ },
142
+ extraCssText: `box-shadow: ${theme.elevation[200]};`,
143
+ },
144
+ grid: {
145
+ left: '3%',
146
+ right: '4%',
147
+ bottom: '3%',
148
+ containLabel: true,
149
+ },
150
+ xAxis: {
151
+ axisLine: {
152
+ lineStyle: {
153
+ color: theme.colors.backgroundAccent,
154
+ },
155
+ },
156
+ axisTick: {
157
+ lineStyle: {
158
+ color: theme.colors.backgroundAccent,
159
+ },
160
+ },
161
+ axisLabel: {
162
+ color: theme.colors.textAlt,
163
+ },
164
+ splitLine: {
165
+ lineStyle: {
166
+ color: theme.colors.backgroundAccent,
167
+ type: 'dashed',
168
+ },
169
+ },
170
+ },
171
+ yAxis: {
172
+ axisLine: {
173
+ lineStyle: {
174
+ color: theme.colors.backgroundAccent,
175
+ },
176
+ },
177
+ axisTick: {
178
+ lineStyle: {
179
+ color: theme.colors.backgroundAccent,
180
+ },
181
+ },
182
+ axisLabel: {
183
+ color: theme.colors.textAlt,
184
+ },
185
+ splitLine: {
186
+ lineStyle: {
187
+ color: theme.colors.backgroundAccent,
188
+ type: 'dashed',
189
+ },
190
+ },
191
+ },
192
+ color: [
193
+ theme.colors.primary,
194
+ theme.colors.success,
195
+ theme.colors.warning,
196
+ theme.colors.error,
197
+ theme.colors.info,
198
+ '#8b5cf6',
199
+ '#06b6d4',
200
+ '#f59e0b',
201
+ '#ec4899',
202
+ '#10b981',
203
+ ],
204
+ };
205
+ }, [theme]);
206
+
207
+ // Merge theme options with provided options
208
+ const mergedOptions = useMemo(() => {
209
+ // Check if this is a chart type that doesn't use axes
210
+ const hasNonCartesianChart =
211
+ option.series &&
212
+ Array.isArray(option.series) &&
213
+ option.series.some((s: any) =>
214
+ ['pie', 'radar', 'gauge', 'funnel', 'sankey', 'graph', 'tree', 'treemap', 'sunburst'].includes(s.type),
215
+ );
216
+
217
+ // Create adjusted theme options based on chart type
218
+ const adjustedThemeOptions = hasNonCartesianChart
219
+ ? {
220
+ backgroundColor: themeOptions.backgroundColor,
221
+ textStyle: themeOptions.textStyle,
222
+ title: themeOptions.title,
223
+ legend: themeOptions.legend,
224
+ tooltip: themeOptions.tooltip,
225
+ color: themeOptions.color,
226
+ }
227
+ : themeOptions;
228
+
229
+ return echarts.util.merge(adjustedThemeOptions, option);
230
+ }, [themeOptions, option]);
231
+
232
+ // Handle resize
233
+ useEffect(() => {
234
+ const handleResize = () => {
235
+ if (chartRef.current) {
236
+ const instance = chartRef.current.getEchartsInstance();
237
+ instance.resize();
238
+ }
239
+ };
240
+
241
+ window.addEventListener('resize', handleResize);
242
+ return () => window.removeEventListener('resize', handleResize);
243
+ }, []);
244
+
245
+ return (
246
+ <ReactEChartsCore
247
+ ref={chartRef}
248
+ echarts={echarts}
249
+ option={mergedOptions}
250
+ style={{ height, width, ...style }}
251
+ className={className}
252
+ showLoading={loading}
253
+ onChartReady={onChartReady}
254
+ onEvents={onEvents}
255
+ opts={{ renderer: 'canvas' }}
256
+ notMerge={true}
257
+ lazyUpdate={true}
258
+ />
259
+ );
260
+ };
261
+
262
+ // Wrapper with ParentSize for responsive sizing
263
+ export const ResponsiveECharts: FC<Omit<EChartsBaseProps, 'width' | 'height'>> = (props) => {
264
+ return <ParentSize>{({ width, height }) => <EChartsBase {...props} width={width} height={height} />}</ParentSize>;
265
+ };
@@ -0,0 +1,164 @@
1
+ import React from 'react';
2
+ import { Meta, StoryFn } from '@storybook/react';
3
+ import { EChartsFunnel, EChartsFunnelProps } from './EChartsFunnel';
4
+ import { styled } from '../../../styled';
5
+ import { Card } from '../../../components';
6
+
7
+ export default {
8
+ title: 'Charts/ECharts/Funnel',
9
+ component: EChartsFunnel,
10
+ args: {
11
+ showLegend: true,
12
+ showLabel: true,
13
+ sort: 'descending',
14
+ title: 'Funnel Chart',
15
+ gap: 2,
16
+ },
17
+ } as Meta<EChartsFunnelProps>;
18
+
19
+ const Wrapper = styled.div`
20
+ height: 500px;
21
+ width: 100%;
22
+ `;
23
+
24
+ interface FunnelData {
25
+ stage: string;
26
+ count: number;
27
+ }
28
+
29
+ // Generate sample funnel data
30
+ function generateFunnelData(): FunnelData[] {
31
+ return [
32
+ { stage: 'Visits', count: 1000 },
33
+ { stage: 'Registrations', count: 650 },
34
+ { stage: 'First Purchase', count: 400 },
35
+ { stage: 'Repeat Purchase', count: 250 },
36
+ { stage: 'VIP Status', count: 100 },
37
+ ];
38
+ }
39
+
40
+ export const Default: StoryFn<EChartsFunnelProps<FunnelData>> = (args) => {
41
+ const data = generateFunnelData();
42
+
43
+ return (
44
+ <Wrapper>
45
+ <EChartsFunnel<FunnelData>
46
+ {...args}
47
+ data={data}
48
+ nameAccessor={(d) => d.stage}
49
+ valueAccessor={(d) => d.count}
50
+ seriesName="Conversion"
51
+ />
52
+ </Wrapper>
53
+ );
54
+ };
55
+
56
+ export const PlayerConversion: StoryFn = () => {
57
+ const conversionData = [
58
+ { stage: 'New Players', count: 5000 },
59
+ { stage: 'Tutorial Completed', count: 3500 },
60
+ { stage: 'First Mission', count: 2800 },
61
+ { stage: 'Level 10 Reached', count: 1800 },
62
+ { stage: 'First Purchase', count: 900 },
63
+ { stage: 'Regular Player', count: 450 },
64
+ ];
65
+
66
+ return (
67
+ <Wrapper>
68
+ <Card variant="outline">
69
+ <Card.Title label="Player Conversion Funnel" />
70
+ <Card.Body>
71
+ <div style={{ height: '400px' }}>
72
+ <EChartsFunnel
73
+ data={conversionData}
74
+ nameAccessor={(d) => d.stage}
75
+ valueAccessor={(d) => d.count}
76
+ seriesName="Players"
77
+ showLegend={false}
78
+ labelPosition="inside"
79
+ tooltipFormatter={(params: any) => {
80
+ return `${params.name}<br/>Players: ${params.value.toLocaleString()} (${params.percent}%)`;
81
+ }}
82
+ />
83
+ </div>
84
+ </Card.Body>
85
+ </Card>
86
+ </Wrapper>
87
+ );
88
+ };
89
+
90
+ export const SalesProcess: StoryFn<EChartsFunnelProps<FunnelData>> = (args) => {
91
+ const salesData = [
92
+ { stage: 'Leads', count: 100 },
93
+ { stage: 'Qualified', count: 80 },
94
+ { stage: 'Proposal', count: 60 },
95
+ { stage: 'Negotiation', count: 40 },
96
+ { stage: 'Closed', count: 30 },
97
+ ];
98
+
99
+ return (
100
+ <Wrapper>
101
+ <EChartsFunnel<FunnelData>
102
+ {...args}
103
+ data={salesData}
104
+ nameAccessor={(d) => d.stage}
105
+ valueAccessor={(d) => d.count}
106
+ seriesName="Sales"
107
+ title="Sales Pipeline"
108
+ subtitle="Q4 2024"
109
+ labelPosition="right"
110
+ />
111
+ </Wrapper>
112
+ );
113
+ };
114
+
115
+ export const AscendingFunnel: StoryFn<EChartsFunnelProps<FunnelData>> = (args) => {
116
+ const growthData = [
117
+ { stage: 'Bronze', count: 30 },
118
+ { stage: 'Silver', count: 50 },
119
+ { stage: 'Gold', count: 75 },
120
+ { stage: 'Platinum', count: 90 },
121
+ { stage: 'Diamond', count: 100 },
122
+ ];
123
+
124
+ return (
125
+ <Wrapper>
126
+ <EChartsFunnel<FunnelData>
127
+ {...args}
128
+ data={growthData}
129
+ nameAccessor={(d) => d.stage}
130
+ valueAccessor={(d) => d.count}
131
+ seriesName="Tier Distribution"
132
+ sort="ascending"
133
+ title="Player Tier Progression"
134
+ subtitle="Inverted funnel showing growth"
135
+ gap={5}
136
+ />
137
+ </Wrapper>
138
+ );
139
+ };
140
+
141
+ export const NoSort: StoryFn<EChartsFunnelProps<FunnelData>> = (args) => {
142
+ const customData = [
143
+ { stage: 'Start', count: 80 },
144
+ { stage: 'Middle', count: 100 },
145
+ { stage: 'Peak', count: 120 },
146
+ { stage: 'Decline', count: 60 },
147
+ { stage: 'End', count: 40 },
148
+ ];
149
+
150
+ return (
151
+ <Wrapper>
152
+ <EChartsFunnel<FunnelData>
153
+ {...args}
154
+ data={customData}
155
+ nameAccessor={(d) => d.stage}
156
+ valueAccessor={(d) => d.count}
157
+ seriesName="Custom Flow"
158
+ sort="none"
159
+ title="Custom Process Flow"
160
+ subtitle="Maintains data order"
161
+ />
162
+ </Wrapper>
163
+ );
164
+ };
@@ -0,0 +1,114 @@
1
+ import { FC, useMemo } from 'react';
2
+ import { EChartsOption } from 'echarts';
3
+ import { ResponsiveECharts, EChartsBaseProps } from './EChartsBase';
4
+
5
+ export interface EChartsFunnelProps<T = any> extends Omit<EChartsBaseProps, 'option'> {
6
+ data: T[];
7
+ nameAccessor: (d: T) => string;
8
+ valueAccessor: (d: T) => number;
9
+ seriesName?: string;
10
+ showLegend?: boolean;
11
+ showLabel?: boolean;
12
+ title?: string;
13
+ subtitle?: string;
14
+ sort?: 'ascending' | 'descending' | 'none';
15
+ gap?: number;
16
+ tooltipFormatter?: (params: any) => string;
17
+ labelPosition?: 'left' | 'right' | 'inside';
18
+ }
19
+
20
+ export const EChartsFunnel: FC<EChartsFunnelProps> = ({
21
+ data,
22
+ nameAccessor,
23
+ valueAccessor,
24
+ seriesName = 'Funnel',
25
+ showLegend = true,
26
+ showLabel = true,
27
+ title,
28
+ subtitle,
29
+ sort = 'descending',
30
+ gap = 2,
31
+ tooltipFormatter,
32
+ labelPosition = 'inside',
33
+ ...chartProps
34
+ }) => {
35
+ const option: EChartsOption = useMemo(() => {
36
+ const funnelData = data.map((d) => ({
37
+ name: nameAccessor(d),
38
+ value: valueAccessor(d),
39
+ }));
40
+
41
+ return {
42
+ title: title
43
+ ? {
44
+ text: title,
45
+ subtext: subtitle,
46
+ left: 'center',
47
+ }
48
+ : undefined,
49
+ legend: showLegend
50
+ ? {
51
+ orient: 'vertical',
52
+ left: 'left',
53
+ top: 'middle',
54
+ }
55
+ : undefined,
56
+ tooltip: {
57
+ trigger: 'item',
58
+ formatter: tooltipFormatter || '{a} <br/>{b}: {c} ({d}%)',
59
+ },
60
+ series: [
61
+ {
62
+ name: seriesName,
63
+ type: 'funnel',
64
+ left: '10%',
65
+ top: 60,
66
+ bottom: 60,
67
+ width: '80%',
68
+ min: 0,
69
+ max: 100,
70
+ minSize: '0%',
71
+ maxSize: '100%',
72
+ sort: sort,
73
+ gap: gap,
74
+ label: {
75
+ show: showLabel,
76
+ position: labelPosition,
77
+ formatter: '{b}: {c}',
78
+ },
79
+ labelLine: {
80
+ length: 10,
81
+ lineStyle: {
82
+ width: 1,
83
+ type: 'solid',
84
+ },
85
+ },
86
+ itemStyle: {
87
+ borderWidth: 0,
88
+ },
89
+ emphasis: {
90
+ label: {
91
+ fontSize: 20,
92
+ },
93
+ },
94
+ data: funnelData,
95
+ },
96
+ ],
97
+ };
98
+ }, [
99
+ data,
100
+ nameAccessor,
101
+ valueAccessor,
102
+ seriesName,
103
+ showLegend,
104
+ showLabel,
105
+ title,
106
+ subtitle,
107
+ sort,
108
+ gap,
109
+ tooltipFormatter,
110
+ labelPosition,
111
+ ]);
112
+
113
+ return <ResponsiveECharts option={option} {...chartProps} />;
114
+ };
@@ -0,0 +1,168 @@
1
+ import React from 'react';
2
+ import { Meta, StoryFn } from '@storybook/react';
3
+ import { EChartsHeatmap, EChartsHeatmapProps } from './EChartsHeatmap';
4
+ import { styled } from '../../../styled';
5
+ import { Card } from '../../../components';
6
+ import { faker } from '@faker-js/faker';
7
+
8
+ export default {
9
+ title: 'Charts/ECharts/Heatmap',
10
+ component: EChartsHeatmap,
11
+ args: {
12
+ title: 'Heatmap Example',
13
+ xAxisLabel: 'Hour',
14
+ yAxisLabel: 'Day',
15
+ showLabel: false,
16
+ },
17
+ } as Meta<EChartsHeatmapProps>;
18
+
19
+ const Wrapper = styled.div`
20
+ height: 500px;
21
+ width: 100%;
22
+ `;
23
+
24
+ interface HeatmapData {
25
+ hour: number;
26
+ day: number;
27
+ value: number;
28
+ }
29
+
30
+ // Generate sample heatmap data
31
+ function generateHeatmapData(): HeatmapData[] {
32
+ const data: HeatmapData[] = [];
33
+ const days = 7;
34
+ const hours = 24;
35
+
36
+ for (let day = 0; day < days; day++) {
37
+ for (let hour = 0; hour < hours; hour++) {
38
+ // Simulate peak hours (lunch and evening)
39
+ let baseValue = 10;
40
+ if (hour >= 11 && hour <= 13) baseValue = 40; // Lunch peak
41
+ if (hour >= 18 && hour <= 21) baseValue = 60; // Evening peak
42
+ if (hour >= 0 && hour <= 6) baseValue = 2; // Night low
43
+
44
+ // Weekend bonus
45
+ if (day === 5 || day === 6) baseValue *= 1.5;
46
+
47
+ data.push({
48
+ hour,
49
+ day,
50
+ value: baseValue + faker.number.int({ min: -5, max: 20 }),
51
+ });
52
+ }
53
+ }
54
+
55
+ return data;
56
+ }
57
+
58
+ const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
59
+ const hourLabels = Array.from({ length: 24 }, (_, i) =>
60
+ i === 0 ? '12am' : i < 12 ? `${i}am` : i === 12 ? '12pm' : `${i - 12}pm`,
61
+ );
62
+
63
+ export const Default: StoryFn<EChartsHeatmapProps<HeatmapData>> = (args) => {
64
+ const data = generateHeatmapData();
65
+
66
+ return (
67
+ <Wrapper>
68
+ <EChartsHeatmap<HeatmapData>
69
+ {...args}
70
+ data={data}
71
+ xAccessor={(d) => d.hour}
72
+ yAccessor={(d) => d.day}
73
+ valueAccessor={(d) => d.value}
74
+ xCategories={hourLabels}
75
+ yCategories={dayNames}
76
+ />
77
+ </Wrapper>
78
+ );
79
+ };
80
+
81
+ export const PeakSalesHeatmap: StoryFn = () => {
82
+ const data = generateHeatmapData();
83
+
84
+ return (
85
+ <Wrapper>
86
+ <Card variant="outline">
87
+ <Card.Title label="Peak Sales Heatmap" />
88
+ <Card.Body>
89
+ <div style={{ height: '400px' }}>
90
+ <EChartsHeatmap
91
+ data={data}
92
+ xAccessor={(d) => d.hour}
93
+ yAccessor={(d) => d.day}
94
+ valueAccessor={(d) => d.value}
95
+ xCategories={hourLabels}
96
+ yCategories={dayNames}
97
+ xAxisLabel="Hour of Day"
98
+ yAxisLabel="Day of Week"
99
+ tooltipFormatter={(params: any) => {
100
+ const value = params.value;
101
+ return `${dayNames[value[1]]}, ${hourLabels[value[0]]}<br/>Sales: ${value[2]}`;
102
+ }}
103
+ />
104
+ </div>
105
+ </Card.Body>
106
+ </Card>
107
+ </Wrapper>
108
+ );
109
+ };
110
+
111
+ export const PlayerActivityHeatmap: StoryFn<EChartsHeatmapProps<HeatmapData>> = (args) => {
112
+ const data = generateHeatmapData().map((d) => ({
113
+ ...d,
114
+ value: d.value * 2, // Higher values for player activity
115
+ }));
116
+
117
+ return (
118
+ <Wrapper>
119
+ <EChartsHeatmap<HeatmapData>
120
+ {...args}
121
+ data={data}
122
+ xAccessor={(d) => d.hour}
123
+ yAccessor={(d) => d.day}
124
+ valueAccessor={(d) => d.value}
125
+ xCategories={hourLabels}
126
+ yCategories={dayNames}
127
+ title="Player Activity Patterns"
128
+ subtitle="Average players online by hour and day"
129
+ showLabel={false}
130
+ minValue={0}
131
+ maxValue={150}
132
+ />
133
+ </Wrapper>
134
+ );
135
+ };
136
+
137
+ export const WithLabels: StoryFn<EChartsHeatmapProps<HeatmapData>> = (args) => {
138
+ // Smaller dataset for label visibility
139
+ const data: HeatmapData[] = [];
140
+ for (let day = 0; day < 5; day++) {
141
+ for (let hour = 0; hour < 12; hour++) {
142
+ data.push({
143
+ hour: hour * 2, // Every 2 hours
144
+ day,
145
+ value: faker.number.int({ min: 10, max: 100 }),
146
+ });
147
+ }
148
+ }
149
+
150
+ const sparseHours = hourLabels.filter((_, i) => i % 2 === 0);
151
+ const weekdays = dayNames.slice(0, 5);
152
+
153
+ return (
154
+ <Wrapper>
155
+ <EChartsHeatmap<HeatmapData>
156
+ {...args}
157
+ data={data}
158
+ xAccessor={(d) => d.hour / 2}
159
+ yAccessor={(d) => d.day}
160
+ valueAccessor={(d) => d.value}
161
+ xCategories={sparseHours}
162
+ yCategories={weekdays}
163
+ title="Resource Usage"
164
+ showLabel={true}
165
+ />
166
+ </Wrapper>
167
+ );
168
+ };