@takaro/lib-components 0.3.1 → 0.4.0

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.
@@ -0,0 +1,141 @@
1
+ import { FC, useMemo } from 'react';
2
+ import { EChartsOption } from 'echarts';
3
+ import { ResponsiveECharts, EChartsBaseProps } from './EChartsBase';
4
+ import { useTheme } from '../../../hooks';
5
+
6
+ export interface EChartsHeatmapProps<T = any> extends Omit<EChartsBaseProps, 'option'> {
7
+ data: T[];
8
+ xAccessor: (d: T) => number;
9
+ yAccessor: (d: T) => number;
10
+ valueAccessor: (d: T) => number;
11
+ xCategories?: string[];
12
+ yCategories?: string[];
13
+ title?: string;
14
+ subtitle?: string;
15
+ xAxisLabel?: string;
16
+ yAxisLabel?: string;
17
+ showLabel?: boolean;
18
+ tooltipFormatter?: (params: any) => string;
19
+ minValue?: number;
20
+ maxValue?: number;
21
+ }
22
+
23
+ export const EChartsHeatmap: FC<EChartsHeatmapProps> = ({
24
+ data,
25
+ xAccessor,
26
+ yAccessor,
27
+ valueAccessor,
28
+ xCategories,
29
+ yCategories,
30
+ title,
31
+ subtitle,
32
+ xAxisLabel,
33
+ yAxisLabel,
34
+ showLabel = false,
35
+ tooltipFormatter,
36
+ minValue,
37
+ maxValue,
38
+ ...chartProps
39
+ }) => {
40
+ const theme = useTheme();
41
+
42
+ const option: EChartsOption = useMemo(() => {
43
+ const heatmapData = data.map((d) => [xAccessor(d), yAccessor(d), valueAccessor(d)]);
44
+ const values = data.map(valueAccessor);
45
+ const min = minValue !== undefined ? minValue : Math.min(...values);
46
+ const max = maxValue !== undefined ? maxValue : Math.max(...values);
47
+
48
+ // Default categories if not provided
49
+ const defaultXCategories = Array.from(new Set(data.map(xAccessor)))
50
+ .sort((a, b) => a - b)
51
+ .map(String);
52
+ const defaultYCategories = Array.from(new Set(data.map(yAccessor)))
53
+ .sort((a, b) => a - b)
54
+ .map(String);
55
+
56
+ return {
57
+ title: title
58
+ ? {
59
+ text: title,
60
+ subtext: subtitle,
61
+ left: 'center',
62
+ }
63
+ : undefined,
64
+ tooltip: {
65
+ position: 'top',
66
+ formatter: tooltipFormatter,
67
+ },
68
+ grid: {
69
+ left: '3%',
70
+ right: '10%',
71
+ bottom: '3%',
72
+ containLabel: true,
73
+ },
74
+ xAxis: {
75
+ type: 'category',
76
+ data: xCategories || defaultXCategories,
77
+ name: xAxisLabel,
78
+ nameLocation: 'middle',
79
+ nameGap: 30,
80
+ splitArea: {
81
+ show: true,
82
+ },
83
+ },
84
+ yAxis: {
85
+ type: 'category',
86
+ data: yCategories || defaultYCategories,
87
+ name: yAxisLabel,
88
+ nameLocation: 'middle',
89
+ nameGap: 50,
90
+ splitArea: {
91
+ show: true,
92
+ },
93
+ },
94
+ visualMap: {
95
+ min: min,
96
+ max: max,
97
+ calculable: true,
98
+ orient: 'vertical',
99
+ right: '0%',
100
+ top: 'middle',
101
+ inRange: {
102
+ color: [theme.colors.background, theme.colors.primary + '40', theme.colors.primary],
103
+ },
104
+ },
105
+ series: [
106
+ {
107
+ name: 'Heatmap',
108
+ type: 'heatmap',
109
+ data: heatmapData,
110
+ label: {
111
+ show: showLabel,
112
+ },
113
+ emphasis: {
114
+ itemStyle: {
115
+ shadowBlur: 10,
116
+ shadowColor: 'rgba(0, 0, 0, 0.5)',
117
+ },
118
+ },
119
+ },
120
+ ],
121
+ };
122
+ }, [
123
+ data,
124
+ xAccessor,
125
+ yAccessor,
126
+ valueAccessor,
127
+ xCategories,
128
+ yCategories,
129
+ title,
130
+ subtitle,
131
+ xAxisLabel,
132
+ yAxisLabel,
133
+ showLabel,
134
+ tooltipFormatter,
135
+ minValue,
136
+ maxValue,
137
+ theme,
138
+ ]);
139
+
140
+ return <ResponsiveECharts option={option} {...chartProps} />;
141
+ };
@@ -0,0 +1,132 @@
1
+ import React from 'react';
2
+ import { Meta, StoryFn } from '@storybook/react';
3
+ import { EChartsLine, EChartsLineProps } from './EChartsLine';
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/Line',
10
+ component: EChartsLine,
11
+ args: {
12
+ smooth: true,
13
+ area: false,
14
+ showGrid: true,
15
+ showLegend: true,
16
+ xAxisLabel: 'Time',
17
+ yAxisLabel: 'Value',
18
+ title: 'Line Chart Example',
19
+ subtitle: 'Sample data visualization',
20
+ },
21
+ } as Meta<EChartsLineProps>;
22
+
23
+ const Wrapper = styled.div`
24
+ height: 500px;
25
+ width: 100%;
26
+ `;
27
+
28
+ interface TimeSeriesData {
29
+ date: string;
30
+ value: number;
31
+ }
32
+
33
+ // Generate sample time series data
34
+ function generateTimeSeriesData(days: number = 30): TimeSeriesData[] {
35
+ const data: TimeSeriesData[] = [];
36
+ const now = new Date();
37
+
38
+ for (let i = days - 1; i >= 0; i--) {
39
+ const date = new Date(now);
40
+ date.setDate(date.getDate() - i);
41
+
42
+ data.push({
43
+ date: date.toLocaleDateString(),
44
+ value: faker.number.int({ min: 100, max: 500 }) + Math.sin(i / 5) * 50,
45
+ });
46
+ }
47
+
48
+ return data;
49
+ }
50
+
51
+ export const Default: StoryFn<EChartsLineProps<TimeSeriesData>> = (args) => {
52
+ const data = generateTimeSeriesData();
53
+
54
+ return (
55
+ <Wrapper>
56
+ <EChartsLine<TimeSeriesData>
57
+ {...args}
58
+ data={data}
59
+ xAccessor={(d) => d.date}
60
+ yAccessor={(d) => d.value}
61
+ seriesName="Daily Value"
62
+ />
63
+ </Wrapper>
64
+ );
65
+ };
66
+
67
+ export const AreaChart: StoryFn<EChartsLineProps<TimeSeriesData>> = (args) => {
68
+ const data = generateTimeSeriesData();
69
+
70
+ return (
71
+ <Wrapper>
72
+ <EChartsLine<TimeSeriesData>
73
+ {...args}
74
+ data={data}
75
+ xAccessor={(d) => d.date}
76
+ yAccessor={(d) => d.value}
77
+ seriesName="Daily Revenue"
78
+ area={true}
79
+ title="Revenue Over Time"
80
+ subtitle="Last 30 days"
81
+ />
82
+ </Wrapper>
83
+ );
84
+ };
85
+
86
+ export const MultipleLines: StoryFn = () => {
87
+ const data = generateTimeSeriesData();
88
+
89
+ return (
90
+ <Wrapper>
91
+ <Card variant="outline">
92
+ <Card.Title label="Server Performance Metrics" />
93
+ <Card.Body>
94
+ <div style={{ height: '400px' }}>
95
+ <EChartsLine
96
+ data={data}
97
+ xAccessor={(d) => d.date}
98
+ yAccessor={(d) => d.value}
99
+ seriesName="CPU Usage"
100
+ smooth={true}
101
+ showGrid={true}
102
+ xAxisLabel="Date"
103
+ yAxisLabel="Usage (%)"
104
+ tooltipFormatter={(params: any) => {
105
+ return `${params[0].name}<br/>${params[0].seriesName}: ${params[0].value}%`;
106
+ }}
107
+ />
108
+ </div>
109
+ </Card.Body>
110
+ </Card>
111
+ </Wrapper>
112
+ );
113
+ };
114
+
115
+ export const SteppedLine: StoryFn<EChartsLineProps<TimeSeriesData>> = (args) => {
116
+ const data = generateTimeSeriesData(20);
117
+
118
+ return (
119
+ <Wrapper>
120
+ <EChartsLine<TimeSeriesData>
121
+ {...args}
122
+ data={data}
123
+ xAccessor={(d) => d.date}
124
+ yAccessor={(d) => d.value}
125
+ seriesName="Player Count"
126
+ smooth={false}
127
+ title="Players Online"
128
+ subtitle="Real-time monitoring"
129
+ />
130
+ </Wrapper>
131
+ );
132
+ };
@@ -0,0 +1,111 @@
1
+ import { FC, useMemo } from 'react';
2
+ import { EChartsOption } from 'echarts';
3
+ import { ResponsiveECharts, EChartsBaseProps } from './EChartsBase';
4
+
5
+ export interface EChartsLineProps<T = any> extends Omit<EChartsBaseProps, 'option'> {
6
+ data: T[];
7
+ xAccessor: (d: T) => string | number | Date;
8
+ yAccessor: (d: T) => number;
9
+ seriesName?: string;
10
+ smooth?: boolean;
11
+ area?: boolean;
12
+ showGrid?: boolean;
13
+ showLegend?: boolean;
14
+ xAxisLabel?: string;
15
+ yAxisLabel?: string;
16
+ title?: string;
17
+ subtitle?: string;
18
+ tooltipFormatter?: (params: any) => string;
19
+ }
20
+
21
+ export const EChartsLine: FC<EChartsLineProps> = ({
22
+ data,
23
+ xAccessor,
24
+ yAccessor,
25
+ seriesName = 'Value',
26
+ smooth = true,
27
+ area = false,
28
+ showGrid = true,
29
+ showLegend = false,
30
+ xAxisLabel,
31
+ yAxisLabel,
32
+ title,
33
+ subtitle,
34
+ tooltipFormatter,
35
+ ...chartProps
36
+ }) => {
37
+ const option: EChartsOption = useMemo(() => {
38
+ const xData = data.map(xAccessor);
39
+ const yData = data.map(yAccessor);
40
+
41
+ return {
42
+ title: title
43
+ ? {
44
+ text: title,
45
+ subtext: subtitle,
46
+ left: 'center',
47
+ }
48
+ : undefined,
49
+ legend: showLegend
50
+ ? {
51
+ data: [seriesName],
52
+ top: 30,
53
+ }
54
+ : undefined,
55
+ grid: showGrid
56
+ ? {
57
+ left: '3%',
58
+ right: '4%',
59
+ bottom: '3%',
60
+ containLabel: true,
61
+ }
62
+ : undefined,
63
+ tooltip: {
64
+ trigger: 'axis',
65
+ formatter: tooltipFormatter,
66
+ },
67
+ xAxis: {
68
+ type: 'category',
69
+ data: xData,
70
+ name: xAxisLabel,
71
+ nameLocation: 'middle',
72
+ nameGap: 30,
73
+ boundaryGap: false,
74
+ },
75
+ yAxis: {
76
+ type: 'value',
77
+ name: yAxisLabel,
78
+ nameLocation: 'middle',
79
+ nameGap: 50,
80
+ },
81
+ series: [
82
+ {
83
+ name: seriesName,
84
+ type: 'line',
85
+ data: yData,
86
+ smooth: smooth,
87
+ areaStyle: area ? {} : undefined,
88
+ emphasis: {
89
+ focus: 'series',
90
+ },
91
+ },
92
+ ],
93
+ };
94
+ }, [
95
+ data,
96
+ xAccessor,
97
+ yAccessor,
98
+ seriesName,
99
+ smooth,
100
+ area,
101
+ showGrid,
102
+ showLegend,
103
+ xAxisLabel,
104
+ yAxisLabel,
105
+ title,
106
+ subtitle,
107
+ tooltipFormatter,
108
+ ]);
109
+
110
+ return <ResponsiveECharts option={option} {...chartProps} />;
111
+ };
@@ -0,0 +1,131 @@
1
+ import React from 'react';
2
+ import { Meta, StoryFn } from '@storybook/react';
3
+ import { EChartsPie, EChartsPieProps } from './EChartsPie';
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/Pie',
10
+ component: EChartsPie,
11
+ args: {
12
+ donut: false,
13
+ showLegend: true,
14
+ showLabel: true,
15
+ title: 'Pie Chart Example',
16
+ },
17
+ } as Meta<EChartsPieProps>;
18
+
19
+ const Wrapper = styled.div`
20
+ height: 500px;
21
+ width: 100%;
22
+ `;
23
+
24
+ interface SegmentData {
25
+ category: string;
26
+ count: number;
27
+ }
28
+
29
+ // Generate sample segment data
30
+ function generateSegmentData(): SegmentData[] {
31
+ return [
32
+ { category: 'New Players', count: faker.number.int({ min: 100, max: 300 }) },
33
+ { category: 'Regular Players', count: faker.number.int({ min: 200, max: 500 }) },
34
+ { category: 'VIP Players', count: faker.number.int({ min: 50, max: 150 }) },
35
+ { category: 'Inactive', count: faker.number.int({ min: 30, max: 100 }) },
36
+ ];
37
+ }
38
+
39
+ export const Default: StoryFn<EChartsPieProps<SegmentData>> = (args) => {
40
+ const data = generateSegmentData();
41
+
42
+ return (
43
+ <Wrapper>
44
+ <EChartsPie<SegmentData>
45
+ {...args}
46
+ data={data}
47
+ nameAccessor={(d) => d.category}
48
+ valueAccessor={(d) => d.count}
49
+ seriesName="Player Segments"
50
+ />
51
+ </Wrapper>
52
+ );
53
+ };
54
+
55
+ export const DonutChart: StoryFn<EChartsPieProps<SegmentData>> = (args) => {
56
+ const data = generateSegmentData();
57
+
58
+ return (
59
+ <Wrapper>
60
+ <EChartsPie<SegmentData>
61
+ {...args}
62
+ data={data}
63
+ nameAccessor={(d) => d.category}
64
+ valueAccessor={(d) => d.count}
65
+ seriesName="Distribution"
66
+ donut={true}
67
+ title="Customer Segments"
68
+ subtitle="Based on purchase frequency"
69
+ />
70
+ </Wrapper>
71
+ );
72
+ };
73
+
74
+ export const RoseChart: StoryFn<EChartsPieProps<SegmentData>> = (args) => {
75
+ const categories = [
76
+ { category: 'Monday', count: 120 },
77
+ { category: 'Tuesday', count: 200 },
78
+ { category: 'Wednesday', count: 150 },
79
+ { category: 'Thursday', count: 180 },
80
+ { category: 'Friday', count: 290 },
81
+ { category: 'Saturday', count: 330 },
82
+ { category: 'Sunday', count: 310 },
83
+ ];
84
+
85
+ return (
86
+ <Wrapper>
87
+ <EChartsPie
88
+ {...args}
89
+ data={categories}
90
+ nameAccessor={(d) => d.category}
91
+ valueAccessor={(d) => d.count}
92
+ seriesName="Activity by Day"
93
+ roseType="radius"
94
+ title="Weekly Activity Distribution"
95
+ showLegend={false}
96
+ />
97
+ </Wrapper>
98
+ );
99
+ };
100
+
101
+ export const OrderStatusDistribution: StoryFn = () => {
102
+ const orderStatus = [
103
+ { category: 'Completed', count: 450 },
104
+ { category: 'Paid', count: 120 },
105
+ { category: 'Pending', count: 80 },
106
+ { category: 'Cancelled', count: 35 },
107
+ ];
108
+
109
+ return (
110
+ <Wrapper>
111
+ <Card variant="outline">
112
+ <Card.Title label="Order Status Distribution" />
113
+ <Card.Body>
114
+ <div style={{ height: '400px' }}>
115
+ <EChartsPie
116
+ data={orderStatus}
117
+ nameAccessor={(d) => d.category}
118
+ valueAccessor={(d) => d.count}
119
+ seriesName="Orders"
120
+ donut={true}
121
+ radius={['45%', '75%']}
122
+ tooltipFormatter={(params: any) => {
123
+ return `${params.seriesName}<br/>${params.name}: ${params.value} (${params.percent}%)`;
124
+ }}
125
+ />
126
+ </div>
127
+ </Card.Body>
128
+ </Card>
129
+ </Wrapper>
130
+ );
131
+ };
@@ -0,0 +1,124 @@
1
+ import { FC, useMemo } from 'react';
2
+ import { EChartsOption } from 'echarts';
3
+ import { ResponsiveECharts, EChartsBaseProps } from './EChartsBase';
4
+
5
+ export interface EChartsPieProps<T = any> extends Omit<EChartsBaseProps, 'option'> {
6
+ data: T[];
7
+ nameAccessor: (d: T) => string;
8
+ valueAccessor: (d: T) => number;
9
+ seriesName?: string;
10
+ donut?: boolean;
11
+ roseType?: boolean | 'radius' | 'area';
12
+ showLegend?: boolean;
13
+ showLabel?: boolean;
14
+ title?: string;
15
+ subtitle?: string;
16
+ radius?: string | [string, string];
17
+ center?: [string, string];
18
+ tooltipFormatter?: (params: any) => string;
19
+ legendOrient?: 'horizontal' | 'vertical';
20
+ legendLeft?: string | number;
21
+ legendTop?: string | number;
22
+ }
23
+
24
+ export const EChartsPie: FC<EChartsPieProps> = ({
25
+ data,
26
+ nameAccessor,
27
+ valueAccessor,
28
+ seriesName = 'Value',
29
+ donut = false,
30
+ roseType = false,
31
+ showLegend = true,
32
+ showLabel = false,
33
+ title,
34
+ subtitle,
35
+ radius,
36
+ center = ['50%', '50%'],
37
+ tooltipFormatter,
38
+ legendOrient = 'vertical',
39
+ legendLeft = 'left',
40
+ legendTop = 'middle',
41
+ ...chartProps
42
+ }) => {
43
+ const option: EChartsOption = useMemo(() => {
44
+ const pieData = data.map((d) => ({
45
+ name: nameAccessor(d),
46
+ value: valueAccessor(d),
47
+ }));
48
+
49
+ const defaultRadius = donut ? ['40%', '70%'] : '70%';
50
+ const actualRadius = radius || defaultRadius;
51
+
52
+ const series: any = {
53
+ name: seriesName,
54
+ type: 'pie',
55
+ radius: actualRadius,
56
+ center: center,
57
+ data: pieData,
58
+ label: showLabel
59
+ ? {
60
+ show: true,
61
+ formatter: '{b}: {d}%',
62
+ }
63
+ : {
64
+ show: false,
65
+ },
66
+ labelLine: {
67
+ show: showLabel,
68
+ },
69
+ emphasis: {
70
+ itemStyle: {
71
+ shadowBlur: 10,
72
+ shadowOffsetX: 0,
73
+ shadowColor: 'rgba(0, 0, 0, 0.5)',
74
+ },
75
+ },
76
+ };
77
+
78
+ // Only add roseType if it's not false
79
+ if (roseType !== false) {
80
+ series.roseType = roseType;
81
+ }
82
+
83
+ return {
84
+ title: title
85
+ ? {
86
+ text: title,
87
+ subtext: subtitle,
88
+ left: 'center',
89
+ }
90
+ : undefined,
91
+ legend: showLegend
92
+ ? {
93
+ orient: legendOrient,
94
+ left: legendLeft,
95
+ top: legendTop,
96
+ }
97
+ : undefined,
98
+ tooltip: {
99
+ trigger: 'item',
100
+ formatter: tooltipFormatter || '{a} <br/>{b}: {c} ({d}%)',
101
+ },
102
+ series: [series],
103
+ };
104
+ }, [
105
+ data,
106
+ nameAccessor,
107
+ valueAccessor,
108
+ seriesName,
109
+ donut,
110
+ roseType,
111
+ showLegend,
112
+ showLabel,
113
+ title,
114
+ subtitle,
115
+ radius,
116
+ center,
117
+ tooltipFormatter,
118
+ legendOrient,
119
+ legendLeft,
120
+ legendTop,
121
+ ]);
122
+
123
+ return <ResponsiveECharts option={option} {...chartProps} />;
124
+ };