@takaro/lib-components 0.4.8 → 0.4.10
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.
- package/package.json +1 -1
- package/src/components/actions/Button/__snapshots__/Button.test.tsx.snap +1 -1
- package/src/components/actions/IconButton/__snapshots__/IconButton.test.tsx.snap +1 -1
- package/src/components/charts/AreaChart/AreaChart.stories.tsx +11 -7
- package/src/components/charts/AreaChart/index.tsx +114 -63
- package/src/components/charts/BarChart/BarChart.stories.tsx +33 -10
- package/src/components/charts/BarChart/index.tsx +280 -147
- package/src/components/charts/EmptyChart.tsx +45 -0
- package/src/components/charts/GeoMercator/GeoMercator.stories.tsx +15 -9
- package/src/components/charts/GeoMercator/index.tsx +15 -172
- package/src/components/charts/Heatmap/Heatmap.stories.tsx +167 -33
- package/src/components/charts/Heatmap/index.tsx +427 -193
- package/src/components/charts/LineChart/LineChart.stories.tsx +77 -3
- package/src/components/charts/LineChart/index.tsx +200 -79
- package/src/components/charts/PieChart/PieChart.stories.tsx +128 -20
- package/src/components/charts/PieChart/index.tsx +353 -59
- package/src/components/charts/PointHighlight.tsx +2 -2
- package/src/components/charts/RadarChart/RadarChart.stories.tsx +14 -5
- package/src/components/charts/RadarChart/index.tsx +94 -45
- package/src/components/charts/RadialBarChart/RadialBarChart.stories.tsx +26 -1
- package/src/components/charts/RadialBarChart/index.tsx +100 -34
- package/src/components/charts/RadialLineChart/RadialLineChart.stories.tsx +19 -2
- package/src/components/charts/RadialLineChart/index.tsx +116 -26
- package/src/components/charts/index.tsx +0 -26
- package/src/components/charts/util.ts +50 -12
- package/src/components/data/CountryList/index.tsx +146 -0
- package/src/components/data/Stats/Sparkline.tsx +48 -0
- package/src/components/data/Stats/Stat.tsx +15 -4
- package/src/components/data/Stats/context.tsx +1 -1
- package/src/components/data/Stats/index.tsx +8 -3
- package/src/components/data/index.ts +3 -0
- package/src/components/feedback/IconTooltip/index.tsx +9 -6
- package/src/components/feedback/ProgressBar/ProgressBar.stories.tsx +13 -14
- package/src/components/feedback/ProgressBar/index.tsx +1 -1
- package/src/components/inputs/DurationField/__tests__/Generic.test.tsx +12 -0
- package/src/components/visual/Card/CardTitle.tsx +7 -1
- package/src/components/visual/Card/index.tsx +0 -4
- package/src/helpers/formatNumber.ts +6 -0
- package/src/helpers/index.ts +1 -0
- package/src/components/charts/echarts/EChartsArea.stories.tsx +0 -139
- package/src/components/charts/echarts/EChartsArea.tsx +0 -139
- package/src/components/charts/echarts/EChartsBar.stories.tsx +0 -141
- package/src/components/charts/echarts/EChartsBar.tsx +0 -133
- package/src/components/charts/echarts/EChartsBase.tsx +0 -264
- package/src/components/charts/echarts/EChartsFunnel.stories.tsx +0 -164
- package/src/components/charts/echarts/EChartsFunnel.tsx +0 -114
- package/src/components/charts/echarts/EChartsHeatmap.stories.tsx +0 -168
- package/src/components/charts/echarts/EChartsHeatmap.tsx +0 -141
- package/src/components/charts/echarts/EChartsLine.stories.tsx +0 -132
- package/src/components/charts/echarts/EChartsLine.tsx +0 -111
- package/src/components/charts/echarts/EChartsPie.stories.tsx +0 -131
- package/src/components/charts/echarts/EChartsPie.tsx +0 -124
- package/src/components/charts/echarts/EChartsRadialBar.stories.tsx +0 -124
- package/src/components/charts/echarts/EChartsRadialBar.tsx +0 -118
- package/src/components/charts/echarts/EChartsScatter.stories.tsx +0 -166
- package/src/components/charts/echarts/EChartsScatter.tsx +0 -135
- package/src/components/charts/echarts/index.ts +0 -26
|
@@ -13,156 +13,12 @@ import { useCallback } from 'react';
|
|
|
13
13
|
import { localPoint } from '@visx/event';
|
|
14
14
|
import { ZoomControls } from '../ZoomControls';
|
|
15
15
|
import { alpha2ToAlpha3 } from './iso3166-alpha2-to-alpha3';
|
|
16
|
-
import { Flag } from '../../visual/Flag';
|
|
17
|
-
|
|
18
|
-
const alpha3ToAlpha2: Record<string, string> = Object.entries(alpha2ToAlpha3).reduce(
|
|
19
|
-
(acc, [alpha2, alpha3]) => {
|
|
20
|
-
acc[alpha3] = alpha2;
|
|
21
|
-
return acc;
|
|
22
|
-
},
|
|
23
|
-
{} as Record<string, string>,
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
const SidebarContainer = styled.div`
|
|
27
|
-
width: 320px;
|
|
28
|
-
min-width: 320px;
|
|
29
|
-
flex-shrink: 0;
|
|
30
|
-
height: 100%;
|
|
31
|
-
overflow: auto;
|
|
32
|
-
background-color: ${({ theme }) => theme.colors.backgroundAlt};
|
|
33
|
-
border: 1px solid ${({ theme }) => theme.colors.backgroundAccent};
|
|
34
|
-
border-radius: ${({ theme }) => theme.borderRadius.medium};
|
|
35
|
-
padding: ${({ theme }) => theme.spacing['3']};
|
|
36
|
-
margin-left: ${({ theme }) => theme.spacing['4']};
|
|
37
|
-
`;
|
|
38
|
-
|
|
39
|
-
const SidebarTitle = styled.h3`
|
|
40
|
-
margin: 0 0 ${({ theme }) => theme.spacing['2']} 0;
|
|
41
|
-
font-size: ${({ theme }) => theme.fontSize.tiny};
|
|
42
|
-
font-weight: 500;
|
|
43
|
-
color: ${({ theme }) => theme.colors.textAlt};
|
|
44
|
-
text-transform: uppercase;
|
|
45
|
-
letter-spacing: 0.5px;
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
const CountryGrid = styled.div`
|
|
49
|
-
display: grid;
|
|
50
|
-
grid-template-columns: 1fr 1fr;
|
|
51
|
-
gap: ${({ theme }) => theme.spacing['2']};
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
const CountryTable = styled.div`
|
|
55
|
-
display: table;
|
|
56
|
-
width: 100%;
|
|
57
|
-
font-size: ${({ theme }) => theme.fontSize.small};
|
|
58
|
-
border-collapse: collapse;
|
|
59
|
-
`;
|
|
60
|
-
|
|
61
|
-
const CountryRow = styled.div`
|
|
62
|
-
display: table-row;
|
|
63
|
-
|
|
64
|
-
&:hover {
|
|
65
|
-
background-color: ${({ theme }) => theme.colors.backgroundAccent};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
&:last-child > div {
|
|
69
|
-
border-bottom: none;
|
|
70
|
-
}
|
|
71
|
-
`;
|
|
72
|
-
|
|
73
|
-
const CountryCell = styled.div`
|
|
74
|
-
display: table-cell;
|
|
75
|
-
padding: 2px 0;
|
|
76
|
-
vertical-align: middle;
|
|
77
|
-
border-bottom: 1px solid ${({ theme }) => theme.colors.backgroundAccent};
|
|
78
|
-
color: ${({ theme }) => theme.colors.text};
|
|
79
|
-
line-height: 1.3;
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
const FlagCell = styled(CountryCell)`
|
|
83
|
-
width: 20px;
|
|
84
|
-
text-align: center;
|
|
85
|
-
padding-right: 4px;
|
|
86
|
-
`;
|
|
87
|
-
|
|
88
|
-
const CountryCell2 = styled(CountryCell)`
|
|
89
|
-
padding-right: 4px;
|
|
90
|
-
font-weight: 500;
|
|
91
|
-
font-size: ${({ theme }) => theme.fontSize.tiny};
|
|
92
|
-
`;
|
|
93
|
-
|
|
94
|
-
const CountCell = styled(CountryCell)`
|
|
95
|
-
text-align: right;
|
|
96
|
-
font-weight: 600;
|
|
97
|
-
color: ${({ theme }) => theme.colors.primary};
|
|
98
|
-
width: 20px;
|
|
99
|
-
font-size: ${({ theme }) => theme.fontSize.tiny};
|
|
100
|
-
`;
|
|
101
|
-
|
|
102
|
-
const FlexContainer = styled.div`
|
|
103
|
-
display: flex;
|
|
104
|
-
flex-direction: row;
|
|
105
|
-
align-items: stretch;
|
|
106
|
-
position: relative;
|
|
107
|
-
height: 100%;
|
|
108
|
-
width: 100%;
|
|
109
|
-
`;
|
|
110
|
-
|
|
111
|
-
const MapContainer = styled.div`
|
|
112
|
-
flex: 1;
|
|
113
|
-
position: relative;
|
|
114
|
-
`;
|
|
115
16
|
|
|
116
17
|
const StyledTooltip = styled(Tooltip)`
|
|
117
18
|
text-align: center;
|
|
118
19
|
transform: translate(-50%);
|
|
119
20
|
`;
|
|
120
21
|
|
|
121
|
-
interface CountrySidebarProps<T> {
|
|
122
|
-
data: T[];
|
|
123
|
-
xAccessor: (d: T) => string;
|
|
124
|
-
yAccessor: (d: T) => number;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const CountrySidebar = <T,>({ data, xAccessor, yAccessor }: CountrySidebarProps<T>) => {
|
|
128
|
-
const sortedData = [...data].sort((a, b) => yAccessor(b) - yAccessor(a));
|
|
129
|
-
|
|
130
|
-
// Split data into two columns
|
|
131
|
-
const midPoint = Math.ceil(sortedData.length / 2);
|
|
132
|
-
const leftColumnData = sortedData.slice(0, midPoint);
|
|
133
|
-
const rightColumnData = sortedData.slice(midPoint);
|
|
134
|
-
|
|
135
|
-
const renderCountryTable = (columnData: T[]) => (
|
|
136
|
-
<CountryTable>
|
|
137
|
-
{columnData.map((item, index) => {
|
|
138
|
-
const countryCode = xAccessor(item);
|
|
139
|
-
const playerCount = yAccessor(item);
|
|
140
|
-
const alpha2Code = countryCode.length === 3 ? alpha3ToAlpha2[countryCode] : countryCode;
|
|
141
|
-
// Prefer 2-letter country codes for display if available
|
|
142
|
-
const displayCode = alpha2Code || countryCode;
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<CountryRow key={`${countryCode}-${index}`}>
|
|
146
|
-
<FlagCell>{alpha2Code && <Flag countryCode={alpha2Code} size={1} />}</FlagCell>
|
|
147
|
-
<CountryCell2>{displayCode}</CountryCell2>
|
|
148
|
-
<CountCell>{playerCount}</CountCell>
|
|
149
|
-
</CountryRow>
|
|
150
|
-
);
|
|
151
|
-
})}
|
|
152
|
-
</CountryTable>
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
<SidebarContainer>
|
|
157
|
-
<SidebarTitle>Countries ({sortedData.length})</SidebarTitle>
|
|
158
|
-
<CountryGrid>
|
|
159
|
-
{renderCountryTable(leftColumnData)}
|
|
160
|
-
{renderCountryTable(rightColumnData)}
|
|
161
|
-
</CountryGrid>
|
|
162
|
-
</SidebarContainer>
|
|
163
|
-
);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
22
|
interface FeatureShape {
|
|
167
23
|
type: 'Feature';
|
|
168
24
|
id: string;
|
|
@@ -186,7 +42,6 @@ export interface GeoMercatorProps<T> {
|
|
|
186
42
|
tooltipAccessor?: (d: T) => string;
|
|
187
43
|
showZoomControls?: boolean;
|
|
188
44
|
allowZoomAndDrag?: boolean;
|
|
189
|
-
showCountrySidebar?: boolean;
|
|
190
45
|
}
|
|
191
46
|
|
|
192
47
|
type InnerGeoMercator<T> = InnerChartProps & GeoMercatorProps<T>;
|
|
@@ -200,7 +55,6 @@ export const GeoMercator = <T,>({
|
|
|
200
55
|
tooltipAccessor,
|
|
201
56
|
showZoomControls = false,
|
|
202
57
|
allowZoomAndDrag = false,
|
|
203
|
-
showCountrySidebar = false,
|
|
204
58
|
}: GeoMercatorProps<T>) => {
|
|
205
59
|
return (
|
|
206
60
|
<ParentSize>
|
|
@@ -216,7 +70,6 @@ export const GeoMercator = <T,>({
|
|
|
216
70
|
allowZoomAndDrag={allowZoomAndDrag}
|
|
217
71
|
showZoomControls={showZoomControls}
|
|
218
72
|
tooltipAccessor={tooltipAccessor}
|
|
219
|
-
showCountrySidebar={showCountrySidebar}
|
|
220
73
|
/>
|
|
221
74
|
)}
|
|
222
75
|
</ParentSize>
|
|
@@ -233,20 +86,13 @@ const Chart = <T,>({
|
|
|
233
86
|
name,
|
|
234
87
|
allowZoomAndDrag,
|
|
235
88
|
showZoomControls,
|
|
236
|
-
showCountrySidebar,
|
|
237
89
|
}: InnerGeoMercator<T>) => {
|
|
238
90
|
const theme = useTheme();
|
|
239
91
|
const { hideTooltip, showTooltip, tooltipData, tooltipLeft = 0, tooltipTop = 0 } = useTooltip<T>();
|
|
240
92
|
|
|
241
|
-
|
|
242
|
-
const sidebarTotalWidth = showCountrySidebar ? 320 + parseInt(theme.spacing['4']) : 0;
|
|
243
|
-
|
|
244
|
-
// Adjust map width to account for sidebar
|
|
245
|
-
const mapWidth = width - sidebarTotalWidth;
|
|
246
|
-
|
|
247
|
-
const centerX = mapWidth / 2;
|
|
93
|
+
const centerX = width / 2;
|
|
248
94
|
const centerY = height / 2;
|
|
249
|
-
const scale = Math.min(
|
|
95
|
+
const scale = Math.min(width, height) * 0.25;
|
|
250
96
|
|
|
251
97
|
const colorScale = scaleLinear({
|
|
252
98
|
domain: [0, Math.max(...data.map((d) => yAccessor(d)))],
|
|
@@ -277,7 +123,7 @@ const Chart = <T,>({
|
|
|
277
123
|
<svg
|
|
278
124
|
id={name}
|
|
279
125
|
name={name}
|
|
280
|
-
width={
|
|
126
|
+
width={width}
|
|
281
127
|
height={height}
|
|
282
128
|
ref={zoom?.containerRef}
|
|
283
129
|
style={{
|
|
@@ -285,7 +131,7 @@ const Chart = <T,>({
|
|
|
285
131
|
cursor: allowZoomAndDrag && zoom ? (zoom.isDragging ? 'grabbing' : 'grab') : 'default',
|
|
286
132
|
}}
|
|
287
133
|
>
|
|
288
|
-
<rect x={0} y={0} width={
|
|
134
|
+
<rect x={0} y={0} width={width} height={height} fill={theme.colors.background} rx={10} />
|
|
289
135
|
<Mercator<FeatureShape>
|
|
290
136
|
data={world.features}
|
|
291
137
|
scale={zoom?.transformMatrix.scaleX || scale}
|
|
@@ -327,7 +173,7 @@ const Chart = <T,>({
|
|
|
327
173
|
<rect
|
|
328
174
|
x={0}
|
|
329
175
|
y={0}
|
|
330
|
-
width={
|
|
176
|
+
width={width}
|
|
331
177
|
height={height}
|
|
332
178
|
rx={14}
|
|
333
179
|
fill="transparent"
|
|
@@ -346,23 +192,20 @@ const Chart = <T,>({
|
|
|
346
192
|
);
|
|
347
193
|
|
|
348
194
|
const renderContent = (zoomProps?: any) => (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
{
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
</MapContainer>
|
|
359
|
-
{showCountrySidebar && <CountrySidebar data={data} xAccessor={xAccessor} yAccessor={yAccessor} />}
|
|
360
|
-
</FlexContainer>
|
|
195
|
+
<>
|
|
196
|
+
{renderMap(zoomProps)}
|
|
197
|
+
{showZoomControls && zoomProps && <ZoomControls zoom={zoomProps} />}
|
|
198
|
+
{tooltipData && (
|
|
199
|
+
<StyledTooltip top={tooltipTop} left={tooltipLeft} style={getDefaultTooltipStyles(theme)}>
|
|
200
|
+
{tooltipAccessor ? tooltipAccessor(tooltipData) : `${xAccessor(tooltipData)}: ${yAccessor(tooltipData)}`}
|
|
201
|
+
</StyledTooltip>
|
|
202
|
+
)}
|
|
203
|
+
</>
|
|
361
204
|
);
|
|
362
205
|
|
|
363
206
|
return allowZoomAndDrag ? (
|
|
364
207
|
<Zoom<SVGSVGElement>
|
|
365
|
-
width={
|
|
208
|
+
width={width}
|
|
366
209
|
height={height}
|
|
367
210
|
scaleXMin={100}
|
|
368
211
|
scaleXMax={1000}
|
|
@@ -2,53 +2,187 @@ import React from 'react';
|
|
|
2
2
|
import { HeatMap, HeatmapProps } from '.';
|
|
3
3
|
import { Meta, StoryFn } from '@storybook/react';
|
|
4
4
|
import { styled } from '../../../styled';
|
|
5
|
-
import { DateTime } from 'luxon';
|
|
6
5
|
|
|
7
6
|
export default {
|
|
8
7
|
title: 'Charts/Heatmap',
|
|
9
8
|
component: HeatMap,
|
|
10
|
-
|
|
9
|
+
args: {
|
|
10
|
+
showMonthLabels: true,
|
|
11
|
+
showDayLabels: true,
|
|
12
|
+
},
|
|
13
|
+
argTypes: {
|
|
14
|
+
showMonthLabels: { control: 'boolean' },
|
|
15
|
+
showDayLabels: { control: 'boolean' },
|
|
16
|
+
},
|
|
17
|
+
} as Meta<HeatmapProps<ContributionData>>;
|
|
11
18
|
|
|
12
19
|
const Wrapper = styled.div`
|
|
13
|
-
height:
|
|
14
|
-
width:
|
|
20
|
+
height: 200px;
|
|
21
|
+
width: 100%;
|
|
15
22
|
`;
|
|
16
23
|
|
|
17
|
-
interface
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
interface ContributionData {
|
|
25
|
+
date: Date;
|
|
26
|
+
count: number;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
// Generate realistic contribution data (GitHub-style)
|
|
30
|
+
const generateContributionData = (): ContributionData[] => {
|
|
31
|
+
const data: ContributionData[] = [];
|
|
32
|
+
const endDate = new Date();
|
|
33
|
+
const startDate = new Date();
|
|
34
|
+
startDate.setDate(startDate.getDate() - 364); // 1 year ago
|
|
35
|
+
|
|
36
|
+
// Generate contributions with realistic patterns
|
|
37
|
+
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
|
38
|
+
const dayOfWeek = d.getDay();
|
|
39
|
+
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
|
|
40
|
+
|
|
41
|
+
// Simulate realistic patterns:
|
|
42
|
+
// - Less activity on weekends
|
|
43
|
+
// - Random spikes in activity
|
|
44
|
+
// - Some periods of no activity
|
|
45
|
+
let count = 0;
|
|
46
|
+
|
|
47
|
+
if (Math.random() > 0.3) {
|
|
48
|
+
if (isWeekend) {
|
|
49
|
+
count = Math.floor(Math.random() * 5);
|
|
50
|
+
} else {
|
|
51
|
+
count = Math.floor(Math.random() * 15);
|
|
52
|
+
}
|
|
53
|
+
if (Math.random() > 0.9) {
|
|
54
|
+
count += Math.floor(Math.random() * 20);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
data.push({
|
|
59
|
+
date: new Date(d),
|
|
60
|
+
count,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return data;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const Default: StoryFn<HeatmapProps<ContributionData>> = (args) => {
|
|
68
|
+
const data = React.useMemo(() => generateContributionData(), []);
|
|
41
69
|
|
|
42
70
|
return (
|
|
43
71
|
<Wrapper>
|
|
44
|
-
<HeatMap<
|
|
72
|
+
<HeatMap<ContributionData>
|
|
73
|
+
name="contributions"
|
|
74
|
+
data={data}
|
|
75
|
+
dateAccessor={(d) => d.date}
|
|
76
|
+
valueAccessor={(d) => d.count}
|
|
77
|
+
showMonthLabels={args.showMonthLabels}
|
|
78
|
+
showDayLabels={args.showDayLabels}
|
|
79
|
+
/>
|
|
80
|
+
</Wrapper>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const LastThreeMonths: StoryFn<HeatmapProps<ContributionData>> = (args) => {
|
|
85
|
+
const data = React.useMemo(() => {
|
|
86
|
+
const result: ContributionData[] = [];
|
|
87
|
+
const endDate = new Date();
|
|
88
|
+
const startDate = new Date();
|
|
89
|
+
startDate.setMonth(startDate.getMonth() - 3);
|
|
90
|
+
|
|
91
|
+
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
|
92
|
+
result.push({
|
|
93
|
+
date: new Date(d),
|
|
94
|
+
count: Math.floor(Math.random() * 10),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
const startDate = new Date();
|
|
102
|
+
startDate.setMonth(startDate.getMonth() - 3);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Wrapper>
|
|
106
|
+
<HeatMap<ContributionData>
|
|
107
|
+
name="contributions-3months"
|
|
108
|
+
data={data}
|
|
109
|
+
dateAccessor={(d) => d.date}
|
|
110
|
+
valueAccessor={(d) => d.count}
|
|
111
|
+
startDate={startDate}
|
|
112
|
+
endDate={new Date()}
|
|
113
|
+
showMonthLabels={args.showMonthLabels}
|
|
114
|
+
showDayLabels={args.showDayLabels}
|
|
115
|
+
/>
|
|
116
|
+
</Wrapper>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Data interface for categorical mode (24h x 7-day)
|
|
121
|
+
interface ActivityData {
|
|
122
|
+
hour: number; // 0-23
|
|
123
|
+
day: number; // 0-6 (Mon-Sun)
|
|
124
|
+
value: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Generate realistic 24h x 7-day activity data
|
|
128
|
+
const generate24x7Data = (): ActivityData[] => {
|
|
129
|
+
const data: ActivityData[] = [];
|
|
130
|
+
|
|
131
|
+
for (let day = 0; day < 7; day++) {
|
|
132
|
+
for (let hour = 0; hour < 24; hour++) {
|
|
133
|
+
// Base activity level
|
|
134
|
+
let value = 5;
|
|
135
|
+
|
|
136
|
+
// Peak hours (lunch and evening)
|
|
137
|
+
if (hour >= 11 && hour <= 13) value = 40; // Lunch peak
|
|
138
|
+
if (hour >= 18 && hour <= 21) value = 60; // Evening peak
|
|
139
|
+
|
|
140
|
+
// Night hours (low activity)
|
|
141
|
+
if (hour >= 0 && hour <= 6) value = 2;
|
|
142
|
+
|
|
143
|
+
// Weekend boost
|
|
144
|
+
if (day === 5 || day === 6) value *= 1.5;
|
|
145
|
+
|
|
146
|
+
// Friday evening extra boost
|
|
147
|
+
if (day === 4 && hour >= 18) value *= 1.3;
|
|
148
|
+
|
|
149
|
+
// Add some randomness
|
|
150
|
+
value = Math.floor(value + Math.random() * 20 - 10);
|
|
151
|
+
value = Math.max(0, value); // Ensure non-negative
|
|
152
|
+
|
|
153
|
+
data.push({ hour, day, value });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return data;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const WeeklyActivity: StoryFn<HeatmapProps<ActivityData>> = (args) => {
|
|
161
|
+
const data = React.useMemo(() => generate24x7Data(), []);
|
|
162
|
+
|
|
163
|
+
const hourLabels = Array.from({ length: 24 }, (_, i) =>
|
|
164
|
+
i === 0 ? '12am' : i < 12 ? `${i}am` : i === 12 ? '12pm' : `${i - 12}pm`,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<Wrapper style={{ height: '250px' }}>
|
|
171
|
+
<HeatMap<ActivityData>
|
|
172
|
+
name="weekly-activity"
|
|
45
173
|
data={data}
|
|
46
|
-
xAccessor={
|
|
47
|
-
yAccessor={
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
174
|
+
xAccessor={(d) => d.hour}
|
|
175
|
+
yAccessor={(d) => d.day}
|
|
176
|
+
valueAccessor={(d) => d.value}
|
|
177
|
+
xCategories={hourLabels}
|
|
178
|
+
yCategories={dayLabels}
|
|
179
|
+
showDayLabels={args.showDayLabels}
|
|
180
|
+
tooltip={{
|
|
181
|
+
accessor: (d) => {
|
|
182
|
+
const activityData = d as ActivityData;
|
|
183
|
+
return `${dayLabels[activityData.day]}, ${hourLabels[activityData.hour]}\n${activityData.value} activities`;
|
|
184
|
+
},
|
|
185
|
+
}}
|
|
52
186
|
/>
|
|
53
187
|
</Wrapper>
|
|
54
188
|
);
|