@takaro/lib-components 0.4.9 → 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.
Files changed (57) hide show
  1. package/package.json +1 -1
  2. package/src/components/actions/Button/__snapshots__/Button.test.tsx.snap +1 -1
  3. package/src/components/actions/IconButton/__snapshots__/IconButton.test.tsx.snap +1 -1
  4. package/src/components/charts/AreaChart/AreaChart.stories.tsx +11 -7
  5. package/src/components/charts/AreaChart/index.tsx +114 -63
  6. package/src/components/charts/BarChart/BarChart.stories.tsx +33 -10
  7. package/src/components/charts/BarChart/index.tsx +280 -147
  8. package/src/components/charts/EmptyChart.tsx +45 -0
  9. package/src/components/charts/GeoMercator/GeoMercator.stories.tsx +15 -9
  10. package/src/components/charts/GeoMercator/index.tsx +15 -172
  11. package/src/components/charts/Heatmap/Heatmap.stories.tsx +167 -33
  12. package/src/components/charts/Heatmap/index.tsx +427 -193
  13. package/src/components/charts/LineChart/LineChart.stories.tsx +77 -3
  14. package/src/components/charts/LineChart/index.tsx +200 -79
  15. package/src/components/charts/PieChart/PieChart.stories.tsx +128 -20
  16. package/src/components/charts/PieChart/index.tsx +353 -59
  17. package/src/components/charts/PointHighlight.tsx +2 -2
  18. package/src/components/charts/RadarChart/RadarChart.stories.tsx +14 -5
  19. package/src/components/charts/RadarChart/index.tsx +94 -45
  20. package/src/components/charts/RadialBarChart/RadialBarChart.stories.tsx +26 -1
  21. package/src/components/charts/RadialBarChart/index.tsx +100 -34
  22. package/src/components/charts/RadialLineChart/RadialLineChart.stories.tsx +19 -2
  23. package/src/components/charts/RadialLineChart/index.tsx +116 -26
  24. package/src/components/charts/index.tsx +0 -26
  25. package/src/components/charts/util.ts +50 -12
  26. package/src/components/data/CountryList/index.tsx +146 -0
  27. package/src/components/data/Stats/Sparkline.tsx +48 -0
  28. package/src/components/data/Stats/Stat.tsx +15 -4
  29. package/src/components/data/Stats/context.tsx +1 -1
  30. package/src/components/data/Stats/index.tsx +8 -3
  31. package/src/components/data/index.ts +3 -0
  32. package/src/components/feedback/IconTooltip/index.tsx +9 -6
  33. package/src/components/feedback/ProgressBar/ProgressBar.stories.tsx +13 -14
  34. package/src/components/feedback/ProgressBar/index.tsx +1 -1
  35. package/src/components/inputs/DurationField/__tests__/Generic.test.tsx +12 -0
  36. package/src/components/visual/Card/CardTitle.tsx +7 -1
  37. package/src/components/visual/Card/index.tsx +0 -4
  38. package/src/helpers/formatNumber.ts +6 -0
  39. package/src/helpers/index.ts +1 -0
  40. package/src/components/charts/echarts/EChartsArea.stories.tsx +0 -139
  41. package/src/components/charts/echarts/EChartsArea.tsx +0 -139
  42. package/src/components/charts/echarts/EChartsBar.stories.tsx +0 -141
  43. package/src/components/charts/echarts/EChartsBar.tsx +0 -133
  44. package/src/components/charts/echarts/EChartsBase.tsx +0 -264
  45. package/src/components/charts/echarts/EChartsFunnel.stories.tsx +0 -164
  46. package/src/components/charts/echarts/EChartsFunnel.tsx +0 -114
  47. package/src/components/charts/echarts/EChartsHeatmap.stories.tsx +0 -168
  48. package/src/components/charts/echarts/EChartsHeatmap.tsx +0 -141
  49. package/src/components/charts/echarts/EChartsLine.stories.tsx +0 -132
  50. package/src/components/charts/echarts/EChartsLine.tsx +0 -111
  51. package/src/components/charts/echarts/EChartsPie.stories.tsx +0 -131
  52. package/src/components/charts/echarts/EChartsPie.tsx +0 -124
  53. package/src/components/charts/echarts/EChartsRadialBar.stories.tsx +0 -124
  54. package/src/components/charts/echarts/EChartsRadialBar.tsx +0 -118
  55. package/src/components/charts/echarts/EChartsScatter.stories.tsx +0 -166
  56. package/src/components/charts/echarts/EChartsScatter.tsx +0 -135
  57. package/src/components/charts/echarts/index.ts +0 -26
@@ -1,4 +1,4 @@
1
- import { MouseEvent, useCallback } from 'react';
1
+ import { MouseEvent, useCallback, useState, useMemo } from 'react';
2
2
 
3
3
  import { ParentSize } from '@visx/responsive';
4
4
  import { Group } from '@visx/group';
@@ -6,21 +6,99 @@ import { Pie } from '@visx/shape';
6
6
  import { scaleOrdinal } from '@visx/scale';
7
7
  import { useTooltipInPortal, useTooltip } from '@visx/tooltip';
8
8
  import { localPoint } from '@visx/event';
9
- import { shade } from 'polished';
9
+ import { LegendOrdinal, LegendItem, LegendLabel } from '@visx/legend';
10
+ import { motion } from 'framer-motion';
10
11
 
11
12
  import { useTheme } from '../../../hooks';
12
- import { getChartColors, getDefaultTooltipStyles, InnerChartProps, Margin } from '../util';
13
+ import {
14
+ getChartColors,
15
+ getDefaultTooltipStyles,
16
+ InnerChartProps,
17
+ LegendPosition,
18
+ TooltipConfig,
19
+ ChartProps,
20
+ } from '../util';
21
+ import { styled } from '../../../styled';
22
+ import { EmptyChart } from '../EmptyChart';
13
23
 
14
- type PieChartVariant = 'pie' | 'donut';
24
+ type LabelPosition = 'inside' | 'outside';
15
25
 
16
- export interface PieChartProps<T> {
17
- name: string;
26
+ const ChartWrapper = styled.div<{ $legendPosition: LegendPosition }>`
27
+ display: flex;
28
+ width: 100%;
29
+ height: 100%;
30
+
31
+ ${({ $legendPosition }) => {
32
+ switch ($legendPosition) {
33
+ case 'top':
34
+ case 'bottom':
35
+ return 'flex-direction: column;';
36
+ case 'left':
37
+ return 'flex-direction: row;';
38
+ case 'right':
39
+ return 'flex-direction: row-reverse;';
40
+ default:
41
+ return '';
42
+ }
43
+ }}
44
+ `;
45
+
46
+ const LegendContainer = styled.div<{ $position: LegendPosition }>`
47
+ display: flex;
48
+ flex-direction: ${({ $position }) => ($position === 'top' || $position === 'bottom' ? 'row' : 'column')};
49
+ flex-wrap: wrap;
50
+ gap: ${({ theme }) => theme.spacing[1]};
51
+ font-size: ${({ theme }) => theme.fontSize.small};
52
+
53
+ ${({ $position }) => {
54
+ switch ($position) {
55
+ case 'top':
56
+ return 'justify-content: center; margin-bottom: 16px;';
57
+ case 'bottom':
58
+ return 'justify-content: center; margin-top: 16px;';
59
+ case 'left':
60
+ return 'margin-right: 16px; justify-content: center;';
61
+ case 'right':
62
+ return 'margin-left: 16px; justify-content: center;';
63
+ default:
64
+ return '';
65
+ }
66
+ }}
67
+ `;
68
+
69
+ const SvgContainer = styled.div`
70
+ flex: 1;
71
+ min-width: 0;
72
+ min-height: 0;
73
+ display: flex;
74
+ `;
75
+
76
+ export interface PieChartProps<T> extends ChartProps {
18
77
  data: T[];
19
- variant: PieChartVariant;
20
- margin?: Margin;
21
78
  xAccessor: (d: T) => string;
22
79
  yAccessor: (d: T) => number;
23
- tooltipAccessor: (d: T) => string;
80
+ /** Tooltip configuration */
81
+ tooltip?: TooltipConfig<T>;
82
+ /** Label accessor - receives data item, percentage, and value. If not provided, shows name if space allows */
83
+ labelAccessor?: (d: T, percentage: number, value: number) => string;
84
+ /** Position labels inside or outside the slices. Default: 'inside' */
85
+ labelPosition?: LabelPosition;
86
+ /** Click handler for pie slices */
87
+ onSliceClick?: (d: T, index: number) => void;
88
+ /** Show legend and its position. Default: 'none' */
89
+ legendPosition?: LegendPosition;
90
+ /** Enable animation on mount and interactions. Default: true */
91
+ animate?: boolean;
92
+ /** Custom colors for slices. If not provided, uses theme colors */
93
+ colors?: string[];
94
+ /** Inner radius as percentage of outer radius (0-1). 0 = pie chart, >0 = donut chart. Example: 0.6 = 60% of outer radius. Default: 0 */
95
+ innerRadius?: number;
96
+ /** Gap between slices in radians. 0 = no gap, 0.02 = small gap, 0.05 = large gap. Default: 0.005 */
97
+ padAngle?: number;
98
+ /** Corner radius in pixels for rounded corners. 0 = sharp corners. Default: 3. Note: With small innerRadius + large padAngle, set to 0 for consistent slice lengths. */
99
+ cornerRadius?: number;
100
+ /** Content to render in the center when innerRadius > 0. Receives total value and data array */
101
+ centerContent?: (total: number, data: T[]) => React.ReactNode;
24
102
  }
25
103
 
26
104
  const defaultMargin = { top: 10, right: 0, bottom: 25, left: 40 };
@@ -28,33 +106,108 @@ export const PieChart = <T,>({
28
106
  data,
29
107
  yAccessor,
30
108
  xAccessor,
31
- tooltipAccessor,
109
+ tooltip,
110
+ labelAccessor,
111
+ labelPosition = 'inside',
32
112
  name,
33
- margin = defaultMargin,
34
- variant,
113
+ margin,
114
+ colors,
115
+ innerRadius = 0,
116
+ padAngle = 0.005,
117
+ cornerRadius = 3,
118
+ onSliceClick,
119
+ legendPosition = 'none',
120
+ animate = true,
121
+ centerContent,
35
122
  }: PieChartProps<T>) => {
36
- return (
37
- <>
38
- <ParentSize>
39
- {(parent) => (
40
- <Chart<T>
41
- name={name}
42
- data={data}
43
- width={parent.width}
44
- height={parent.height}
45
- margin={margin}
46
- variant={variant}
47
- yAccessor={yAccessor}
48
- xAccessor={xAccessor}
49
- tooltipAccessor={tooltipAccessor}
50
- />
123
+ const theme = useTheme();
124
+ const chartColors = colors || getChartColors(theme);
125
+
126
+ const adjustedMargin =
127
+ margin || (labelPosition === 'outside' ? { top: 40, right: 40, bottom: 40, left: 40 } : defaultMargin);
128
+ const total = useMemo(() => data.reduce((sum, d) => sum + yAccessor(d), 0), [data, yAccessor]);
129
+ const legendGlyphSize = 15;
130
+
131
+ const ordinalColorScale = useMemo(
132
+ () =>
133
+ scaleOrdinal({
134
+ domain: data.map(xAccessor),
135
+ range: chartColors,
136
+ }),
137
+ [data, xAccessor, chartColors],
138
+ );
139
+
140
+ const renderLegend = () => (
141
+ <LegendContainer $position={legendPosition}>
142
+ <LegendOrdinal scale={ordinalColorScale}>
143
+ {(labels) => (
144
+ <div
145
+ style={{
146
+ display: 'flex',
147
+ flexDirection: legendPosition === 'top' || legendPosition === 'bottom' ? 'row' : 'column',
148
+ flexWrap: 'wrap',
149
+ gap: '8px',
150
+ }}
151
+ >
152
+ {labels.map((label, i) => (
153
+ <LegendItem key={`legend-item-${i}`} margin="0">
154
+ <svg width={legendGlyphSize} height={legendGlyphSize} style={{ marginRight: '4px' }}>
155
+ <rect fill={label.value} width={legendGlyphSize} height={legendGlyphSize} rx={2} />
156
+ </svg>
157
+ <LegendLabel align="left" margin="0">
158
+ {label.text}
159
+ </LegendLabel>
160
+ </LegendItem>
161
+ ))}
162
+ </div>
51
163
  )}
52
- </ParentSize>
53
- </>
164
+ </LegendOrdinal>
165
+ </LegendContainer>
166
+ );
167
+
168
+ return (
169
+ <ChartWrapper $legendPosition={legendPosition}>
170
+ {legendPosition !== 'none' && (legendPosition === 'top' || legendPosition === 'left') && renderLegend()}
171
+
172
+ <SvgContainer>
173
+ <ParentSize>
174
+ {total > 0
175
+ ? (parent) => (
176
+ <Chart<T>
177
+ name={name}
178
+ data={data}
179
+ width={parent.width}
180
+ height={parent.height}
181
+ margin={adjustedMargin}
182
+ yAccessor={yAccessor}
183
+ xAccessor={xAccessor}
184
+ labelAccessor={labelAccessor}
185
+ labelPosition={labelPosition}
186
+ ordinalColorScale={ordinalColorScale}
187
+ total={total}
188
+ onSliceClick={onSliceClick}
189
+ animate={animate}
190
+ innerRadius={innerRadius}
191
+ padAngle={padAngle}
192
+ cornerRadius={cornerRadius}
193
+ centerContent={centerContent}
194
+ tooltip={tooltip}
195
+ />
196
+ )
197
+ : () => <EmptyChart />}
198
+ </ParentSize>
199
+ </SvgContainer>
200
+
201
+ {legendPosition !== 'none' && (legendPosition === 'bottom' || legendPosition === 'right') && renderLegend()}
202
+ </ChartWrapper>
54
203
  );
55
204
  };
56
205
 
57
- type InnerPieChartProps<T> = InnerChartProps & PieChartProps<T>;
206
+ type InnerPieChartProps<T> = InnerChartProps &
207
+ Omit<PieChartProps<T>, 'colors' | 'legendPosition'> & {
208
+ ordinalColorScale: ReturnType<typeof scaleOrdinal<string, string>>;
209
+ total: number;
210
+ };
58
211
 
59
212
  const Chart = <T,>({
60
213
  width,
@@ -63,9 +216,18 @@ const Chart = <T,>({
63
216
  data,
64
217
  name,
65
218
  height,
66
- variant,
67
- tooltipAccessor,
219
+ labelAccessor,
220
+ labelPosition,
221
+ tooltip,
68
222
  margin = defaultMargin,
223
+ ordinalColorScale,
224
+ total,
225
+ onSliceClick,
226
+ animate,
227
+ innerRadius = 0,
228
+ padAngle = 0.005,
229
+ cornerRadius = 3,
230
+ centerContent,
69
231
  }: InnerPieChartProps<T>) => {
70
232
  const theme = useTheme();
71
233
  const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, hideTooltip, showTooltip } = useTooltip<string>();
@@ -74,31 +236,59 @@ const Chart = <T,>({
74
236
  scroll: true,
75
237
  });
76
238
 
239
+ const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
240
+ const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
241
+
77
242
  const innerWidth = width - margin.left - margin.right;
78
243
  const innerHeight = height - margin.top - margin.bottom;
79
244
  const radius = Math.min(innerWidth, innerHeight) / 2;
80
245
  const centerX = innerWidth / 2;
81
246
  const centerY = innerHeight / 2;
82
- const donutThickness = 50;
83
247
 
84
- const getArcColor = scaleOrdinal({
85
- domain: data.map(xAccessor),
86
- range: getChartColors(theme),
87
- });
248
+ // Calculate inner radius as percentage of outer radius
249
+ const calculatedInnerRadius = radius * innerRadius;
250
+
251
+ // When innerRadius is 0, force cornerRadius to 0 to prevent center artifact
252
+ // (you can't geometrically round a point where all slices converge)
253
+ const actualCornerRadius = innerRadius === 0 ? 0 : cornerRadius;
254
+
255
+ // When innerRadius is 0, remove stroke to prevent white rectangle artifact at center
256
+ // (overlapping stroke borders create a visible square where slices meet)
257
+ const actualStrokeWidth = innerRadius === 0 ? 0 : 2;
258
+
88
259
  const pieSortValues = (a: number, b: number) => b - a;
89
260
 
90
261
  const handleMouseOver = useCallback(
91
- (event: MouseEvent, data: T) => {
262
+ (event: MouseEvent, datum: T, index: number, percentage: number, value: number) => {
92
263
  const target = event.target as SVGElement;
93
264
  const coords = localPoint(target.ownerSVGElement!, event);
94
265
 
266
+ setHoveredIndex(index);
267
+
268
+ const defaultTooltip = `${xAccessor(datum)}: ${value.toFixed(2)} (${percentage.toFixed(1)}%)`;
269
+
95
270
  showTooltip({
96
271
  tooltipLeft: coords?.x,
97
272
  tooltipTop: coords?.y,
98
- tooltipData: tooltipAccessor(data),
273
+ tooltipData: tooltip?.accessor ? tooltip.accessor(datum) : defaultTooltip,
99
274
  });
100
275
  },
101
- [data, tooltipAccessor],
276
+ [tooltip?.accessor, xAccessor, showTooltip],
277
+ );
278
+
279
+ const handleMouseLeave = useCallback(() => {
280
+ setHoveredIndex(null);
281
+ hideTooltip();
282
+ }, [hideTooltip]);
283
+
284
+ const handleClick = useCallback(
285
+ (datum: T, index: number) => {
286
+ setSelectedIndex(index === selectedIndex ? null : index);
287
+ if (onSliceClick) {
288
+ onSliceClick(datum, index);
289
+ }
290
+ },
291
+ [onSliceClick, selectedIndex],
102
292
  );
103
293
 
104
294
  return width < 10 ? null : (
@@ -107,9 +297,9 @@ const Chart = <T,>({
107
297
  <Pie
108
298
  data={data}
109
299
  outerRadius={radius}
110
- innerRadius={variant === 'donut' ? radius - donutThickness : 0}
111
- cornerRadius={3}
112
- padAngle={0.02}
300
+ innerRadius={calculatedInnerRadius}
301
+ cornerRadius={actualCornerRadius}
302
+ padAngle={padAngle}
113
303
  pieSortValues={pieSortValues}
114
304
  pieValue={yAccessor}
115
305
  >
@@ -118,35 +308,139 @@ const Chart = <T,>({
118
308
  const [centroidX, centroidY] = pie.path.centroid(arc);
119
309
  const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1;
120
310
  const arcPath = pie.path(arc) ?? '';
121
- const arcFill = getArcColor(xAccessor(arc.data));
311
+ const arcFill = ordinalColorScale(xAccessor(arc.data));
312
+ const value = yAccessor(arc.data);
313
+ const percentage = (value / total) * 100;
314
+
315
+ const isHovered = hoveredIndex === index;
316
+ const isSelected = selectedIndex === index;
317
+
318
+ // Calculate expanded position for hover/select effect (only if animate is enabled)
319
+ const angle = (arc.startAngle + arc.endAngle) / 2;
320
+ const expandDistance = animate && (isHovered || isSelected) ? 10 : 0;
321
+ const translateX = Math.cos(angle - Math.PI / 2) * expandDistance;
322
+ const translateY = Math.sin(angle - Math.PI / 2) * expandDistance;
323
+
324
+ // Calculate label position
325
+ let labelX = centroidX;
326
+ let labelY = centroidY;
327
+
328
+ if (labelPosition === 'outside') {
329
+ const labelRadius = radius + 20;
330
+ labelX = Math.cos(angle - Math.PI / 2) * labelRadius;
331
+ labelY = Math.sin(angle - Math.PI / 2) * labelRadius;
332
+ }
333
+
334
+ const labelText = labelAccessor
335
+ ? labelAccessor(arc.data, percentage, value)
336
+ : hasSpaceForLabel
337
+ ? xAccessor(arc.data)
338
+ : '';
339
+
122
340
  return (
123
- <g key={`arc-${arc.data}-${index}`}>
124
- <path
341
+ <g
342
+ key={`arc-${xAccessor(arc.data)}-${index}`}
343
+ style={{
344
+ transform: `translate(${translateX}px, ${translateY}px)`,
345
+ transition: animate ? 'transform 0.2s ease-out' : 'none',
346
+ }}
347
+ >
348
+ <motion.path
125
349
  d={arcPath}
126
- fill={shade(0.5, arcFill)}
127
- stroke={arcFill}
128
- strokeWidth={1}
129
- onMouseOut={hideTooltip}
130
- onMouseOver={(e) => handleMouseOver(e, arc.data)}
350
+ fill={arcFill}
351
+ stroke={theme.colors.background}
352
+ strokeWidth={actualStrokeWidth}
353
+ onMouseOver={(e) => handleMouseOver(e, arc.data, index, percentage, value)}
354
+ onMouseLeave={handleMouseLeave}
355
+ onClick={() => handleClick(arc.data, index)}
356
+ style={{
357
+ cursor: onSliceClick ? 'pointer' : 'default',
358
+ transition: animate ? 'opacity 0.2s ease-out' : 'none',
359
+ filter: animate && (isHovered || isSelected) ? 'brightness(1.1)' : 'none',
360
+ }}
361
+ initial={animate ? { opacity: 0 } : { opacity: 0.9 }}
362
+ animate={{
363
+ opacity: isHovered || isSelected ? 1 : hoveredIndex !== null ? 0.6 : 0.9,
364
+ }}
365
+ transition={
366
+ animate
367
+ ? {
368
+ opacity: {
369
+ duration: 0.3,
370
+ delay: (arc.startAngle / (Math.PI * 2)) * 0.6,
371
+ ease: 'linear',
372
+ },
373
+ }
374
+ : { duration: 0 }
375
+ }
131
376
  />
132
- {hasSpaceForLabel && (
133
- <text
134
- x={centroidX}
135
- y={centroidY}
377
+ {labelText && (
378
+ <motion.text
379
+ x={labelX}
380
+ y={labelY}
136
381
  dy=".33em"
137
- fill="white"
138
- fontSize={9}
382
+ fill={labelPosition === 'outside' ? theme.colors.text : 'white'}
383
+ fontSize={labelPosition === 'outside' ? 12 : 11}
384
+ fontWeight={animate && (isHovered || isSelected) ? 600 : 500}
139
385
  textAnchor="middle"
140
386
  pointerEvents="none"
387
+ style={{
388
+ transition: animate ? 'font-weight 0.2s ease-out' : 'none',
389
+ }}
390
+ initial={animate ? { opacity: 0 } : { opacity: 1 }}
391
+ animate={{ opacity: 1 }}
392
+ transition={
393
+ animate
394
+ ? {
395
+ duration: 0.3,
396
+ delay: 1.0,
397
+ ease: 'easeOut',
398
+ }
399
+ : { duration: 0 }
400
+ }
141
401
  >
142
- {xAccessor(arc.data)}
143
- </text>
402
+ {labelText}
403
+ </motion.text>
144
404
  )}
145
405
  </g>
146
406
  );
147
407
  });
148
408
  }}
149
409
  </Pie>
410
+ {innerRadius > 0 && centerContent && (
411
+ <foreignObject
412
+ x={-calculatedInnerRadius}
413
+ y={-calculatedInnerRadius}
414
+ width={calculatedInnerRadius * 2}
415
+ height={calculatedInnerRadius * 2}
416
+ style={{ pointerEvents: 'none' }}
417
+ >
418
+ <motion.div
419
+ style={{
420
+ width: '100%',
421
+ height: '100%',
422
+ display: 'flex',
423
+ alignItems: 'center',
424
+ justifyContent: 'center',
425
+ textAlign: 'center',
426
+ color: theme.colors.text,
427
+ }}
428
+ initial={animate ? { opacity: 0, scale: 0.8 } : { opacity: 1, scale: 1 }}
429
+ animate={{ opacity: 1, scale: 1 }}
430
+ transition={
431
+ animate
432
+ ? {
433
+ duration: 0.4,
434
+ delay: 1.1,
435
+ ease: 'easeOut',
436
+ }
437
+ : { duration: 0 }
438
+ }
439
+ >
440
+ {centerContent(total, data)}
441
+ </motion.div>
442
+ </foreignObject>
443
+ )}
150
444
  </Group>
151
445
  {tooltipOpen && tooltipData && (
152
446
  <TooltipInPortal
@@ -17,8 +17,8 @@ export const PointHighlight: FC<PointHighlightProps> = ({ margin, yMax, tooltipL
17
17
  return (
18
18
  <Group left={margin.left} top={margin.top}>
19
19
  <Line
20
- from={{ x: tooltipLeft, y: margin.top }}
21
- to={{ x: tooltipLeft, y: yMax + margin.top }}
20
+ from={{ x: tooltipLeft, y: 0 }}
21
+ to={{ x: tooltipLeft, y: yMax }}
22
22
  stroke={theme.colors.backgroundAccent}
23
23
  strokeWidth={1}
24
24
  pointerEvents="none"
@@ -7,14 +7,19 @@ import letterFrequency, { LetterFrequency } from '@visx/mock-data/lib/mocks/lett
7
7
  export default {
8
8
  title: 'Charts/RadarChart',
9
9
  component: RadarChart,
10
+ args: {
11
+ levels: 5,
12
+ items: 6,
13
+ animate: true,
14
+ },
10
15
  } as Meta<RadarChartProps<LetterFrequency>>;
11
16
 
12
17
  const Wrapper = styled.div`
13
- height: 50vh;
14
- width: 100%;
18
+ height: 400px;
19
+ width: 400px;
15
20
  `;
16
21
 
17
- export const Default: StoryFn<RadarChartProps<LetterFrequency>> = () => {
22
+ export const Default: StoryFn<RadarChartProps<LetterFrequency>> = (args) => {
18
23
  const getLetter = (d: LetterFrequency) => d.letter;
19
24
  const getLetterFrequency = (d: LetterFrequency) => Number(d.frequency);
20
25
  const tooltipAccessor = (d: LetterFrequency) => {
@@ -22,7 +27,7 @@ export const Default: StoryFn<RadarChartProps<LetterFrequency>> = () => {
22
27
  };
23
28
 
24
29
  // only show the first few letters
25
- const data = letterFrequency.slice(2, 9);
30
+ const data = letterFrequency.slice(0, (args as any).items);
26
31
 
27
32
  return (
28
33
  <Wrapper>
@@ -30,8 +35,12 @@ export const Default: StoryFn<RadarChartProps<LetterFrequency>> = () => {
30
35
  name="letterFrequency"
31
36
  yAccessor={getLetterFrequency}
32
37
  xAccessor={getLetter}
33
- tooltipAccessor={tooltipAccessor}
38
+ tooltip={{
39
+ accessor: tooltipAccessor,
40
+ }}
34
41
  data={data}
42
+ levels={args.levels}
43
+ animate={args.animate}
35
44
  />
36
45
  </Wrapper>
37
46
  );