@redsift/charts 8.0.0 → 8.0.1

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 (218) hide show
  1. package/coverage/clover.xml +1070 -0
  2. package/coverage/coverage-final.json +60 -0
  3. package/coverage/lcov-report/ChartEmptyState/ChartEmptyState.tsx.html +679 -0
  4. package/coverage/lcov-report/ChartEmptyState/index.html +146 -0
  5. package/coverage/lcov-report/ChartEmptyState/index.ts.html +91 -0
  6. package/coverage/lcov-report/ChartEmptyState/styles.ts.html +184 -0
  7. package/coverage/lcov-report/HorizontalBarChart/HorizontalBarChart.tsx.html +775 -0
  8. package/coverage/lcov-report/HorizontalBarChart/HorizontalBarChartAxisBottom.tsx.html +259 -0
  9. package/coverage/lcov-report/HorizontalBarChart/HorizontalBarChartBar.tsx.html +421 -0
  10. package/coverage/lcov-report/HorizontalBarChart/index.html +191 -0
  11. package/coverage/lcov-report/HorizontalBarChart/index.ts.html +94 -0
  12. package/coverage/lcov-report/HorizontalBarChart/styles.ts.html +451 -0
  13. package/coverage/lcov-report/HorizontalBarChart/types.ts.html +433 -0
  14. package/coverage/lcov-report/PieChart/PieChart.tsx.html +1105 -0
  15. package/coverage/lcov-report/PieChart/PieChartArc.tsx.html +412 -0
  16. package/coverage/lcov-report/PieChart/index.html +176 -0
  17. package/coverage/lcov-report/PieChart/index.ts.html +94 -0
  18. package/coverage/lcov-report/PieChart/styles.ts.html +616 -0
  19. package/coverage/lcov-report/PieChart/types.ts.html +472 -0
  20. package/coverage/lcov-report/PieChart.tsx.html +1045 -0
  21. package/coverage/lcov-report/PieChartArc.tsx.html +271 -0
  22. package/coverage/lcov-report/StaticPieChart.tsx.html +286 -0
  23. package/coverage/lcov-report/base.css +224 -0
  24. package/coverage/lcov-report/block-navigation.js +87 -0
  25. package/coverage/lcov-report/components/Arc/Arc.tsx.html +304 -0
  26. package/coverage/lcov-report/components/Arc/index.html +146 -0
  27. package/coverage/lcov-report/components/Arc/index.ts.html +94 -0
  28. package/coverage/lcov-report/components/Arc/styles.ts.html +208 -0
  29. package/coverage/lcov-report/components/Arcs/Arcs.tsx.html +427 -0
  30. package/coverage/lcov-report/components/Arcs/index.html +146 -0
  31. package/coverage/lcov-report/components/Arcs/index.ts.html +94 -0
  32. package/coverage/lcov-report/components/Arcs/styles.ts.html +106 -0
  33. package/coverage/lcov-report/components/Axis/Axis.tsx.html +754 -0
  34. package/coverage/lcov-report/components/Axis/computeTicks.ts.html +481 -0
  35. package/coverage/lcov-report/components/Axis/index.html +176 -0
  36. package/coverage/lcov-report/components/Axis/index.ts.html +94 -0
  37. package/coverage/lcov-report/components/Axis/styles.ts.html +148 -0
  38. package/coverage/lcov-report/components/Axis/types.ts.html +253 -0
  39. package/coverage/lcov-report/components/Bar/Bar.tsx.html +421 -0
  40. package/coverage/lcov-report/components/Bar/index.html +161 -0
  41. package/coverage/lcov-report/components/Bar/index.ts.html +94 -0
  42. package/coverage/lcov-report/components/Bar/styles.ts.html +247 -0
  43. package/coverage/lcov-report/components/Bar/types.ts.html +178 -0
  44. package/coverage/lcov-report/components/BarChart/BarChart.tsx.html +355 -0
  45. package/coverage/lcov-report/components/BarChart/EmptyBarChart.tsx.html +259 -0
  46. package/coverage/lcov-report/components/BarChart/LoadingBarChart.tsx.html +145 -0
  47. package/coverage/lcov-report/components/BarChart/RenderedBarChart.tsx.html +496 -0
  48. package/coverage/lcov-report/components/BarChart/index.html +206 -0
  49. package/coverage/lcov-report/components/BarChart/index.ts.html +94 -0
  50. package/coverage/lcov-report/components/BarChart/styles.ts.html +190 -0
  51. package/coverage/lcov-report/components/BarChart/utils.ts.html +145 -0
  52. package/coverage/lcov-report/components/ChartContainer/ChartContainer.tsx.html +433 -0
  53. package/coverage/lcov-report/components/ChartContainer/index.html +146 -0
  54. package/coverage/lcov-report/components/ChartContainer/index.ts.html +94 -0
  55. package/coverage/lcov-report/components/ChartContainer/intl/index.html +116 -0
  56. package/coverage/lcov-report/components/ChartContainer/intl/index.ts.html +106 -0
  57. package/coverage/lcov-report/components/ChartContainer/styles.ts.html +211 -0
  58. package/coverage/lcov-report/components/DataPoint/DataPoint.tsx.html +442 -0
  59. package/coverage/lcov-report/components/DataPoint/index.html +146 -0
  60. package/coverage/lcov-report/components/DataPoint/index.ts.html +94 -0
  61. package/coverage/lcov-report/components/DataPoint/styles.ts.html +109 -0
  62. package/coverage/lcov-report/components/Dot/Dot.tsx.html +232 -0
  63. package/coverage/lcov-report/components/Dot/index.html +146 -0
  64. package/coverage/lcov-report/components/Dot/index.ts.html +94 -0
  65. package/coverage/lcov-report/components/Dot/styles.ts.html +184 -0
  66. package/coverage/lcov-report/components/Legend/Legend.tsx.html +268 -0
  67. package/coverage/lcov-report/components/Legend/index.html +146 -0
  68. package/coverage/lcov-report/components/Legend/index.ts.html +94 -0
  69. package/coverage/lcov-report/components/Legend/styles.ts.html +130 -0
  70. package/coverage/lcov-report/components/LegendItem/LegendItem.tsx.html +403 -0
  71. package/coverage/lcov-report/components/LegendItem/index.html +146 -0
  72. package/coverage/lcov-report/components/LegendItem/index.ts.html +94 -0
  73. package/coverage/lcov-report/components/LegendItem/styles.ts.html +205 -0
  74. package/coverage/lcov-report/components/PieChart/EmptyPieChart.tsx.html +343 -0
  75. package/coverage/lcov-report/components/PieChart/LoadingPieChart.tsx.html +145 -0
  76. package/coverage/lcov-report/components/PieChart/PieChart.tsx.html +388 -0
  77. package/coverage/lcov-report/components/PieChart/RenderedPieChart.tsx.html +571 -0
  78. package/coverage/lcov-report/components/PieChart/index.html +221 -0
  79. package/coverage/lcov-report/components/PieChart/index.ts.html +94 -0
  80. package/coverage/lcov-report/components/PieChart/styles.ts.html +376 -0
  81. package/coverage/lcov-report/components/PieChart/types.ts.html +352 -0
  82. package/coverage/lcov-report/components/PieChart/utils.ts.html +199 -0
  83. package/coverage/lcov-report/components/ScatterPlot/EmptyScatterPlot.tsx.html +295 -0
  84. package/coverage/lcov-report/components/ScatterPlot/LoadingScatterPlot.tsx.html +145 -0
  85. package/coverage/lcov-report/components/ScatterPlot/RenderedScatterPlot.tsx.html +910 -0
  86. package/coverage/lcov-report/components/ScatterPlot/ScatterPlot.tsx.html +379 -0
  87. package/coverage/lcov-report/components/ScatterPlot/index.html +221 -0
  88. package/coverage/lcov-report/components/ScatterPlot/index.ts.html +94 -0
  89. package/coverage/lcov-report/components/ScatterPlot/styles.ts.html +196 -0
  90. package/coverage/lcov-report/components/ScatterPlot/types.ts.html +376 -0
  91. package/coverage/lcov-report/components/ScatterPlot/utils.ts.html +388 -0
  92. package/coverage/lcov-report/favicon.png +0 -0
  93. package/coverage/lcov-report/hooks/index.html +176 -0
  94. package/coverage/lcov-report/hooks/index.ts.html +97 -0
  95. package/coverage/lcov-report/hooks/useBrush.tsx.html +430 -0
  96. package/coverage/lcov-report/hooks/useColor.tsx.html +163 -0
  97. package/coverage/lcov-report/hooks/useFormatCategoricalData.tsx.html +289 -0
  98. package/coverage/lcov-report/hooks/useZoom.tsx.html +235 -0
  99. package/coverage/lcov-report/index.html +311 -0
  100. package/coverage/lcov-report/index.ts.html +94 -0
  101. package/coverage/lcov-report/prettify.css +1 -0
  102. package/coverage/lcov-report/prettify.js +2 -0
  103. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  104. package/coverage/lcov-report/sorter.js +196 -0
  105. package/coverage/lcov-report/styles.ts.html +631 -0
  106. package/coverage/lcov-report/types.ts.html +457 -0
  107. package/coverage/lcov.info +2272 -0
  108. package/dist/index.js +6326 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/package.json +98 -0
  111. package/index.ts +1 -0
  112. package/jest.config.js +3 -0
  113. package/package.json +2 -3
  114. package/rollup.config.js +13 -0
  115. package/src/components/Arc/Arc.test.tsx +92 -0
  116. package/src/components/Arc/Arc.tsx +73 -0
  117. package/src/components/Arc/index.ts +3 -0
  118. package/src/components/Arc/styles.ts +41 -0
  119. package/src/components/Arc/types.ts +18 -0
  120. package/src/components/Arcs/Arcs.stories.tsx +177 -0
  121. package/src/components/Arcs/Arcs.tsx +114 -0
  122. package/src/components/Arcs/index.ts +3 -0
  123. package/src/components/Arcs/styles.ts +7 -0
  124. package/src/components/Arcs/types.ts +26 -0
  125. package/src/components/Axis/Axis.stories.tsx +297 -0
  126. package/src/components/Axis/Axis.tsx +223 -0
  127. package/src/components/Axis/computeTicks.ts +132 -0
  128. package/src/components/Axis/index.ts +3 -0
  129. package/src/components/Axis/styles.ts +21 -0
  130. package/src/components/Axis/types.ts +56 -0
  131. package/src/components/Bar/Bar.stories.tsx +152 -0
  132. package/src/components/Bar/Bar.test.tsx +158 -0
  133. package/src/components/Bar/Bar.tsx +112 -0
  134. package/src/components/Bar/index.ts +3 -0
  135. package/src/components/Bar/styles.ts +54 -0
  136. package/src/components/Bar/types.ts +31 -0
  137. package/src/components/BarChart/BarChart.stories.tsx +80 -0
  138. package/src/components/BarChart/BarChart.test.tsx +189 -0
  139. package/src/components/BarChart/BarChart.tsx +90 -0
  140. package/src/components/BarChart/EmptyBarChart.tsx +58 -0
  141. package/src/components/BarChart/LoadingBarChart.tsx +20 -0
  142. package/src/components/BarChart/RenderedBarChart.tsx +137 -0
  143. package/src/components/BarChart/__snapshots__/BarChart.test.tsx.snap +6932 -0
  144. package/src/components/BarChart/index.ts +3 -0
  145. package/src/components/BarChart/styles.ts +35 -0
  146. package/src/components/BarChart/types.ts +58 -0
  147. package/src/components/BarChart/utils.ts +20 -0
  148. package/src/components/ChartContainer/ChartContainer.stories.tsx +81 -0
  149. package/src/components/ChartContainer/ChartContainer.test.tsx +75 -0
  150. package/src/components/ChartContainer/ChartContainer.tsx +116 -0
  151. package/src/components/ChartContainer/index.ts +3 -0
  152. package/src/components/ChartContainer/intl/en-US.json +3 -0
  153. package/src/components/ChartContainer/intl/fr-FR.json +3 -0
  154. package/src/components/ChartContainer/intl/index.ts +7 -0
  155. package/src/components/ChartContainer/styles.ts +42 -0
  156. package/src/components/ChartContainer/types.ts +24 -0
  157. package/src/components/DataPoint/DataPoint.tsx +119 -0
  158. package/src/components/DataPoint/index.ts +3 -0
  159. package/src/components/DataPoint/styles.ts +8 -0
  160. package/src/components/DataPoint/types.ts +33 -0
  161. package/src/components/Dot/Dot.stories.tsx +157 -0
  162. package/src/components/Dot/Dot.test.tsx +136 -0
  163. package/src/components/Dot/Dot.tsx +49 -0
  164. package/src/components/Dot/index.ts +3 -0
  165. package/src/components/Dot/styles.ts +33 -0
  166. package/src/components/Dot/types.ts +16 -0
  167. package/src/components/Legend/Legend.stories.tsx +108 -0
  168. package/src/components/Legend/Legend.tsx +61 -0
  169. package/src/components/Legend/index.ts +3 -0
  170. package/src/components/Legend/styles.ts +15 -0
  171. package/src/components/Legend/types.ts +27 -0
  172. package/src/components/LegendItem/LegendItem.test.tsx +69 -0
  173. package/src/components/LegendItem/LegendItem.tsx +106 -0
  174. package/src/components/LegendItem/index.ts +3 -0
  175. package/src/components/LegendItem/styles.ts +40 -0
  176. package/src/components/LegendItem/types.ts +30 -0
  177. package/src/components/PieChart/EmptyPieChart.tsx +86 -0
  178. package/src/components/PieChart/LoadingPieChart.tsx +20 -0
  179. package/src/components/PieChart/PieChart.stories.tsx +91 -0
  180. package/src/components/PieChart/PieChart.test.tsx +201 -0
  181. package/src/components/PieChart/PieChart.tsx +101 -0
  182. package/src/components/PieChart/RenderedPieChart.tsx +162 -0
  183. package/src/components/PieChart/__snapshots__/PieChart.stories.storyshot +7843 -0
  184. package/src/components/PieChart/__snapshots__/PieChart.test.tsx.snap +20540 -0
  185. package/src/components/PieChart/index.ts +3 -0
  186. package/src/components/PieChart/styles.ts +97 -0
  187. package/src/components/PieChart/types.ts +89 -0
  188. package/src/components/PieChart/utils.ts +38 -0
  189. package/src/components/ScatterPlot/EmptyScatterPlot.tsx +70 -0
  190. package/src/components/ScatterPlot/LoadingScatterPlot.tsx +20 -0
  191. package/src/components/ScatterPlot/RenderedScatterPlot.tsx +275 -0
  192. package/src/components/ScatterPlot/ScatterPlot.stories.tsx +95 -0
  193. package/src/components/ScatterPlot/ScatterPlot.test.tsx +44 -0
  194. package/src/components/ScatterPlot/ScatterPlot.tsx +98 -0
  195. package/src/components/ScatterPlot/__snapshots__/ScatterPlot.test.tsx.snap +88352 -0
  196. package/src/components/ScatterPlot/index.ts +3 -0
  197. package/src/components/ScatterPlot/styles.ts +37 -0
  198. package/src/components/ScatterPlot/types.ts +97 -0
  199. package/src/components/ScatterPlot/utils.ts +101 -0
  200. package/src/config.ts +10 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/hooks/useBrush.tsx +115 -0
  203. package/src/hooks/useColor.tsx +26 -0
  204. package/src/hooks/useFormatCategoricalData.tsx +68 -0
  205. package/src/hooks/useZoom.tsx +50 -0
  206. package/src/index.ts +15 -0
  207. package/src/scheme.ts +221 -0
  208. package/src/types/data.ts +52 -0
  209. package/src/types/index.ts +5 -0
  210. package/src/types/legend.ts +19 -0
  211. package/src/types/scale.ts +79 -0
  212. package/src/types/size.ts +17 -0
  213. package/src/types/theme.ts +27 -0
  214. package/tsconfig.json +3 -0
  215. package/index.js +0 -2606
  216. package/index.js.map +0 -1
  217. /package/{CONTRIBUTING.md → dist/CONTRIBUTING.md} +0 -0
  218. /package/{index.d.ts → dist/index.d.ts} +0 -0
@@ -0,0 +1,106 @@
1
+ import React, { KeyboardEventHandler, forwardRef, useId } from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ import { Comp, Number, Text } from '@redsift/design-system';
5
+
6
+ import { LegendItemProps } from './types';
7
+ import { StyledLegendItem } from './styles';
8
+ import { monochrome } from '../../scheme';
9
+ import { LabelVariant } from '../../types';
10
+
11
+ const COMPONENT_NAME = 'LegendItem';
12
+ const CLASSNAME = 'redsift-legend-item';
13
+ const DEFAULT_PROPS: Partial<LegendItemProps> = {
14
+ color: monochrome,
15
+ index: 0,
16
+ };
17
+
18
+ export const LegendItem: Comp<LegendItemProps, HTMLLIElement> = forwardRef(
19
+ (props, ref) => {
20
+ const {
21
+ index,
22
+ isSelected: propsIsSelected,
23
+ labelDecorator,
24
+ onClick,
25
+ role,
26
+ } = props;
27
+
28
+ const {
29
+ className,
30
+ color,
31
+ data,
32
+ id: propsId,
33
+ total,
34
+ variant,
35
+ ...forwardedProps
36
+ } = props;
37
+ const id = propsId ?? useId();
38
+
39
+ const text = labelDecorator ? labelDecorator(data) : data.data.key;
40
+ const isEmpty = data.data.value === 0;
41
+
42
+ const isSelectable = role === 'option';
43
+ const isSelected = isSelectable && propsIsSelected === true;
44
+ const isDeselected = isSelectable && propsIsSelected === false;
45
+
46
+ const onKeyDown: KeyboardEventHandler<HTMLLIElement> = (event) => {
47
+ if (onClick) {
48
+ event.stopPropagation();
49
+
50
+ if (event.code === 'Enter' || event.code === 'Space') {
51
+ event.preventDefault();
52
+ onClick(data);
53
+ }
54
+ }
55
+ };
56
+
57
+ return (
58
+ <StyledLegendItem
59
+ {...forwardedProps}
60
+ className={classNames(LegendItem.className, className, `_${index}`, {
61
+ selected: isSelected,
62
+ deselected: isDeselected,
63
+ })}
64
+ color={isDeselected ? 'var(--redsift-color-neutral-lightgrey)' : color}
65
+ ref={ref}
66
+ aria-labelledby={!isEmpty ? `${id}-title` : undefined}
67
+ aria-selected={isSelected ? true : isDeselected ? false : undefined}
68
+ id={id}
69
+ onClick={onClick ? () => onClick(data) : undefined}
70
+ onKeyDown={onClick ? (e) => onKeyDown(e) : undefined}
71
+ role={role ? role : onClick ? 'button' : undefined}
72
+ tabIndex={onClick ? 0 : undefined}
73
+ $clickable={Boolean(onClick)}
74
+ >
75
+ <div />
76
+ {variant === LabelVariant.value ? (
77
+ <>
78
+ <Number
79
+ as="b"
80
+ maximumFractionDigits={2}
81
+ value={data.data.value}
82
+ variant="inherit"
83
+ />
84
+ <Text variant="caption">{text}</Text>
85
+ </>
86
+ ) : variant === LabelVariant.percent && total ? (
87
+ <>
88
+ <Number
89
+ as="b"
90
+ maximumFractionDigits={2}
91
+ type="percent"
92
+ value={data.data.value / total}
93
+ variant="inherit"
94
+ />
95
+ <Text variant="caption">{text}</Text>
96
+ </>
97
+ ) : (
98
+ <Text variant="caption">{text}</Text>
99
+ )}
100
+ </StyledLegendItem>
101
+ );
102
+ }
103
+ );
104
+ LegendItem.className = CLASSNAME;
105
+ LegendItem.defaultProps = DEFAULT_PROPS;
106
+ LegendItem.displayName = COMPONENT_NAME;
@@ -0,0 +1,3 @@
1
+ export * from './LegendItem';
2
+ export * from './types';
3
+ export * from './styles';
@@ -0,0 +1,40 @@
1
+ import styled, { css } from 'styled-components';
2
+ import { StyledLegendItemProps } from './types';
3
+
4
+ /**
5
+ * Component style.
6
+ */
7
+ export const StyledLegendItem = styled.li<StyledLegendItemProps>`
8
+ display: flex;
9
+ align-items: center;
10
+ gap: 8px;
11
+ font-size: var(--redsift-typography-caption-font-size);
12
+
13
+ > div {
14
+ height: 16px;
15
+ width: 16px;
16
+ min-width: 16px;
17
+ background-color: ${({ color }) => color};
18
+ }
19
+
20
+ ${({ $clickable }) =>
21
+ $clickable
22
+ ? css`
23
+ cursor: pointer;
24
+
25
+ &:hover,
26
+ &:focus {
27
+ outline: none;
28
+ > div {
29
+ opacity: 0.7;
30
+ }
31
+ }
32
+
33
+ &:focus-visible {
34
+ > div {
35
+ outline: 4px solid var(--redsift-color-default-primary);
36
+ }
37
+ }
38
+ `
39
+ : ''}}
40
+ `;
@@ -0,0 +1,30 @@
1
+ import { ComponentProps } from 'react';
2
+ import { LabelVariant, LegendItemDatum } from '../../types';
3
+ import { DataPointProps, StyledDataPointProps } from '../DataPoint';
4
+
5
+ /**
6
+ * Component props.
7
+ */
8
+ export interface LegendItemProps
9
+ extends Pick<
10
+ DataPointProps<LegendItemDatum>,
11
+ | 'color'
12
+ | 'data'
13
+ | 'id'
14
+ | 'index'
15
+ | 'isSelected'
16
+ | 'labelDecorator'
17
+ | 'onClick'
18
+ | 'previousData'
19
+ | 'role'
20
+ | 'tooltipVariant'
21
+ >,
22
+ Omit<ComponentProps<'li'>, 'onClick' | 'role'> {
23
+ /** Total used to compute percentage. */
24
+ total?: number;
25
+ /** Variant. */
26
+ variant?: LabelVariant;
27
+ }
28
+
29
+ export type StyledLegendItemProps = Pick<StyledDataPointProps, '$clickable'> &
30
+ Omit<LegendItemProps, 'data'>;
@@ -0,0 +1,86 @@
1
+ import React, { forwardRef, RefObject } from 'react';
2
+ import { arc as d3arc } from 'd3';
3
+
4
+ import { ArcDatum } from '../../types';
5
+ import { PieChartProps, PieChartVariant } from './types';
6
+ import { StyledPieChart, StyledPieChartEmptyText } from './styles';
7
+ import { Arcs } from '../Arcs';
8
+ import { empty } from '../../scheme';
9
+ import { sizeToDimension } from './utils';
10
+
11
+ export const EmptyPieChart = forwardRef<HTMLDivElement, PieChartProps>(
12
+ (props, ref) => {
13
+ const {
14
+ className,
15
+ emptyComponent,
16
+ localeText,
17
+ size,
18
+ variant,
19
+ ...forwardedProps
20
+ } = props;
21
+
22
+ const isDonut =
23
+ variant === PieChartVariant.donut ||
24
+ variant === PieChartVariant.spacedDonut;
25
+
26
+ const { width, height, fontSize, innerRadius } = sizeToDimension(size!);
27
+ const externalRadiusPadding = 8;
28
+
29
+ const createArc = d3arc<ArcDatum>()
30
+ .innerRadius(isDonut ? innerRadius : 0)
31
+ .outerRadius(width / 2 - externalRadiusPadding);
32
+
33
+ return (
34
+ <StyledPieChart
35
+ {...forwardedProps}
36
+ className={className}
37
+ ref={ref as RefObject<HTMLDivElement>}
38
+ >
39
+ {emptyComponent ?? (
40
+ <>
41
+ <StyledPieChartEmptyText
42
+ $maxWidth={innerRadius * 2}
43
+ $textSize={fontSize / 2}
44
+ $isDonut={isDonut}
45
+ >
46
+ <span>{localeText?.emptyChartText}</span>
47
+ </StyledPieChartEmptyText>
48
+ <svg width={width} height={height}>
49
+ <Arcs
50
+ arcs={[
51
+ {
52
+ createArc,
53
+ previousData: {
54
+ data: {
55
+ key: '',
56
+ value: 0,
57
+ },
58
+ startAngle: 0,
59
+ endAngle: 0,
60
+ padAngle: 0,
61
+ value: 0,
62
+ index: 0,
63
+ },
64
+ data: {
65
+ data: {
66
+ key: 'No Data',
67
+ value: 0,
68
+ },
69
+ index: 0,
70
+ value: 0,
71
+ startAngle: 0,
72
+ endAngle: 2 * Math.PI,
73
+ padAngle: 0,
74
+ },
75
+ color: empty,
76
+ },
77
+ ]}
78
+ transform={`translate(${width / 2} ${height / 2})`}
79
+ />
80
+ </svg>
81
+ </>
82
+ )}
83
+ </StyledPieChart>
84
+ );
85
+ }
86
+ );
@@ -0,0 +1,20 @@
1
+ import React, { forwardRef, RefObject } from 'react';
2
+
3
+ import { PieChartProps } from './types';
4
+ import { StyledPieChart } from './styles';
5
+
6
+ export const LoadingPieChart = forwardRef<HTMLDivElement, PieChartProps>(
7
+ (props, ref) => {
8
+ const { className, ...forwardedProps } = props;
9
+
10
+ return (
11
+ <StyledPieChart
12
+ {...forwardedProps}
13
+ className={className}
14
+ ref={ref as RefObject<HTMLDivElement>}
15
+ >
16
+ Loading...
17
+ </StyledPieChart>
18
+ );
19
+ }
20
+ );
@@ -0,0 +1,91 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { DataVizColorPalette } from '@redsift/design-system';
4
+ import { PieChart } from '.';
5
+ import { PieChartLabelVariant, PieChartVariant } from './types';
6
+ import {
7
+ CategoryData,
8
+ ChartSize,
9
+ ChartTheme,
10
+ ColorTheme,
11
+ TooltipVariant,
12
+ } from '../../types';
13
+
14
+ const themes = {
15
+ ...ColorTheme,
16
+ 'success-warning-danger': {
17
+ success: 'Bread',
18
+ warning: 'Tea',
19
+ danger: 'Coffee',
20
+ },
21
+ 'success-warning-danger-neutral': {
22
+ success: 'Bread',
23
+ warning: 'Tea',
24
+ danger: 'Coffee',
25
+ neutral: 'Pastry',
26
+ },
27
+ custom: {
28
+ Coffee: DataVizColorPalette.purple,
29
+ Bread: DataVizColorPalette.pink,
30
+ Tea: DataVizColorPalette.aqua,
31
+ },
32
+ };
33
+
34
+ const meta: Meta<typeof PieChart> = {
35
+ title: 'Charts/PieChart',
36
+ component: PieChart as any,
37
+ argTypes: {
38
+ variant: {
39
+ options: Object.values(PieChartVariant),
40
+ control: { type: 'select' },
41
+ },
42
+ labelVariant: {
43
+ options: Object.values(PieChartLabelVariant),
44
+ control: { type: 'select' },
45
+ },
46
+ tooltipVariant: {
47
+ options: Object.values(TooltipVariant),
48
+ control: { type: 'select' },
49
+ },
50
+ size: {
51
+ options: Object.values(ChartSize),
52
+ control: { type: 'select' },
53
+ },
54
+ theme: {
55
+ options: Object.keys(themes),
56
+ mapping: themes,
57
+ control: {
58
+ type: 'select',
59
+ },
60
+ },
61
+ },
62
+ };
63
+ export default meta;
64
+ type Story = StoryObj<typeof PieChart>;
65
+
66
+ const data: CategoryData = [
67
+ { key: 'Cookies', value: 540 },
68
+ { key: 'Coffee', value: 5471 },
69
+ { key: 'Bread', value: 3325 },
70
+ { key: 'Tea', value: 1435 },
71
+ { key: 'Pastry', value: 856 },
72
+ { key: 'Sandwich', value: 771 },
73
+ { key: 'Croissant', value: 771 },
74
+ { key: 'Hot chocolate', value: 588 },
75
+ { key: 'Toast', value: 318 },
76
+ ];
77
+
78
+ export const Controls: Story = {
79
+ args: {
80
+ title: 'Pie Chart',
81
+ variant: 'spacedDonut',
82
+ labelVariant: 'externalLabelValue',
83
+ tooltipVariant: 'value',
84
+ size: 'medium',
85
+ theme: 'success-warning-danger-neutral' as ChartTheme,
86
+ caping: 4,
87
+ others: true,
88
+ onSliceClick: (datum) => console.log(datum),
89
+ data: data,
90
+ },
91
+ };
@@ -0,0 +1,201 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+
5
+ import { composeStory } from '@storybook/react';
6
+ import Meta, { Controls } from './PieChart.stories';
7
+
8
+ import { PieChart } from './PieChart';
9
+ import { PieChartLabelVariant, PieChartVariant } from './types';
10
+ import { ChartSize, ChartTheme, ColorTheme, TooltipVariant } from '../../types';
11
+ import { DataVizColorPalette } from '@redsift/design-system';
12
+
13
+ jest.mock('react', () => ({
14
+ ...jest.requireActual('react'),
15
+ useId: () => '123',
16
+ }));
17
+
18
+ const themes = {
19
+ ...ColorTheme,
20
+ 'success-warning-danger': {
21
+ success: 'Bread',
22
+ warning: 'Tea',
23
+ danger: 'Coffee',
24
+ },
25
+ 'success-warning-danger-neutral': {
26
+ success: 'Bread',
27
+ warning: 'Tea',
28
+ danger: 'Coffee',
29
+ neutral: 'Pastry',
30
+ },
31
+ custom: {
32
+ Coffee: DataVizColorPalette.purple,
33
+ Bread: DataVizColorPalette.pink,
34
+ Tea: DataVizColorPalette.aqua,
35
+ },
36
+ };
37
+
38
+ const data = [
39
+ { key: 'Dogs', value: 300 },
40
+ { key: 'Cats', value: 500 },
41
+ { key: 'Fish', value: 150 },
42
+ ];
43
+
44
+ describe('PieChart', () => {
45
+ const onClickSpy = jest.fn();
46
+
47
+ afterEach(() => {
48
+ onClickSpy.mockClear();
49
+ });
50
+
51
+ it('should match snapshot', () => {
52
+ const ComposedStory = composeStory(Controls, Meta);
53
+ const tree = render(
54
+ <>
55
+ {Object.values(PieChartVariant).map((variant, i) =>
56
+ Object.values(ChartSize).map((size, j) =>
57
+ Object.values(PieChartLabelVariant).map((labelVariant, k) => (
58
+ <ComposedStory
59
+ key={`${variant}-${size}-${labelVariant}-${
60
+ Object.values(TooltipVariant)[k]
61
+ }`}
62
+ variant={variant}
63
+ labelVariant={labelVariant}
64
+ tooltipVariant={Object.values(TooltipVariant)[k]}
65
+ size={size}
66
+ theme={
67
+ Object.keys(themes)[
68
+ (i * Object.values(ChartSize).length +
69
+ j * Object.values(PieChartLabelVariant).length +
70
+ k) %
71
+ Object.keys(themes).length
72
+ ] as ChartTheme
73
+ }
74
+ />
75
+ ))
76
+ )
77
+ )}
78
+ </>
79
+ );
80
+ expect(tree).toMatchSnapshot();
81
+ });
82
+
83
+ it('handles default', () => {
84
+ const { queryByText } = render(<PieChart aria-label="Chart" data={data} />);
85
+
86
+ expect(queryByText('Cats')).toBe(null);
87
+ expect(queryByText('Dogs')).toBe(null);
88
+ expect(queryByText('Fish')).toBe(null);
89
+ });
90
+
91
+ it('handles internal labels', () => {
92
+ const { getByText } = render(
93
+ <PieChart
94
+ aria-label="Chart"
95
+ data={data}
96
+ labelVariant={PieChartLabelVariant.internal}
97
+ />
98
+ );
99
+
100
+ expect(getByText('Cats')).toBeVisible();
101
+ expect(getByText('Dogs')).toBeVisible();
102
+ });
103
+
104
+ it('supports custom className', () => {
105
+ const tree = render(
106
+ <PieChart aria-label="Chart" data={data} className="test" />
107
+ );
108
+ const component = tree.asFragment().firstChild;
109
+
110
+ expect(component).toHaveAttribute('class', expect.stringContaining('test'));
111
+ });
112
+
113
+ it('supports custom data attributes', () => {
114
+ const tree = render(
115
+ <PieChart aria-label="Chart" data={data} data-testid="test" />
116
+ );
117
+ const component = tree.asFragment().firstChild;
118
+ expect(component).toHaveAttribute('data-testid', 'test');
119
+ });
120
+
121
+ it('can be labelled using a title', () => {
122
+ const { getByLabelText } = render(<PieChart data={data} title="Label" />);
123
+ const component = getByLabelText('Label');
124
+ expect(component).toHaveAttribute('aria-labelledby', 'id123__title');
125
+ });
126
+
127
+ it('can be labelled using an aria-label', () => {
128
+ const { getByLabelText } = render(
129
+ <PieChart data={data} aria-label="Label" />
130
+ );
131
+ const component = getByLabelText('Label');
132
+ expect(component).toHaveAttribute('aria-label', 'Label');
133
+ });
134
+
135
+ it('can be labelled using an aria-labelledby', () => {
136
+ const { getByLabelText } = render(
137
+ <>
138
+ <h1 id="123">Label</h1>
139
+ <PieChart data={data} aria-labelledby="123" />
140
+ </>
141
+ );
142
+ const component = getByLabelText('Label');
143
+ expect(component).toHaveAttribute('aria-labelledby', '123');
144
+ });
145
+
146
+ it('supports click on the pie slices', async () => {
147
+ const user = userEvent.setup();
148
+ const { getAllByRole } = render(
149
+ <PieChart aria-label="Chart" data={data} onSliceClick={onClickSpy} />
150
+ );
151
+
152
+ const slices = getAllByRole('button');
153
+ expect(slices.length).toBe(3);
154
+
155
+ await user.click(slices[0]);
156
+ await user.click(slices[1]);
157
+ await user.click(slices[2]);
158
+
159
+ expect(onClickSpy).toHaveBeenCalledTimes(3);
160
+ });
161
+
162
+ it('supports keydown on the pie slices', async () => {
163
+ const { getAllByRole } = render(
164
+ <PieChart aria-label="Chart" data={data} onSliceClick={onClickSpy} />
165
+ );
166
+
167
+ const slices = getAllByRole('button');
168
+ expect(slices.length).toBe(3);
169
+
170
+ userEvent.keyboard('[Tab]');
171
+ userEvent.keyboard('[Enter]');
172
+ userEvent.keyboard('[Tab]');
173
+ userEvent.keyboard('[Space]');
174
+ userEvent.keyboard('[Tab]');
175
+ userEvent.keyboard('[Enter]');
176
+
177
+ expect(onClickSpy).toHaveBeenCalledTimes(3);
178
+ });
179
+
180
+ it('shows a tooltip when hovering on the pie slices ', async () => {
181
+ const user = userEvent.setup();
182
+ const { getAllByRole } = render(
183
+ <PieChart
184
+ aria-label="Chart"
185
+ data={data}
186
+ // The onSliceClick gives the slice the role "button" which is then used to access the slices.
187
+ onSliceClick={onClickSpy}
188
+ />
189
+ );
190
+
191
+ const slices = getAllByRole('button');
192
+ expect(slices.length).toBe(3);
193
+
194
+ await user.hover(slices[0]);
195
+ await waitFor(() => {
196
+ expect(screen.getByRole('tooltip', { exact: true })).toBeInTheDocument();
197
+ });
198
+ const tooltip = screen.getByRole('tooltip');
199
+ expect(tooltip.textContent).toContain('Cats');
200
+ });
201
+ });
@@ -0,0 +1,101 @@
1
+ import React, { forwardRef, useId } from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ import { Comp } from '@redsift/design-system';
5
+
6
+ import { ChartSize, ColorTheme, TooltipVariant } from '../../types';
7
+ import { PieChartLabelVariant, PieChartProps, PieChartVariant } from './types';
8
+ import { LoadingPieChart } from './LoadingPieChart';
9
+ import { EmptyPieChart } from './EmptyPieChart';
10
+ import { RenderedPieChart } from './RenderedPieChart';
11
+
12
+ const COMPONENT_NAME = 'PieChart';
13
+ const CLASSNAME = 'redsift-piechart';
14
+ const DEFAULT_PROPS: Partial<PieChartProps> = {
15
+ isSliceSelected: () => true,
16
+ labelVariant: PieChartLabelVariant.none,
17
+ others: true,
18
+ size: ChartSize.medium,
19
+ theme: ColorTheme.default,
20
+ variant: PieChartVariant.plain,
21
+ tooltipVariant: TooltipVariant.value,
22
+ localeText: {
23
+ emptyChartText: 'No Data',
24
+ },
25
+ };
26
+
27
+ export const PieChart: Comp<PieChartProps, HTMLDivElement> = forwardRef(
28
+ (props, ref) => {
29
+ const {
30
+ caping,
31
+ chartRef,
32
+ className,
33
+ data: propsData,
34
+ emptyComponent,
35
+ id: propsId,
36
+ isSliceSelected,
37
+ labelDecorator,
38
+ labelVariant,
39
+ localeText,
40
+ middleText,
41
+ onSliceClick,
42
+ others,
43
+ size,
44
+ sliceRole,
45
+ subtext,
46
+ text,
47
+ theme,
48
+ tooltipVariant,
49
+ variant,
50
+ ...forwardedProps
51
+ } = props;
52
+ const id = propsId ?? useId();
53
+
54
+ if (propsData === undefined || propsData === null) {
55
+ return <LoadingPieChart id={id} {...forwardedProps} ref={ref} />;
56
+ }
57
+
58
+ if (propsData.length === 0) {
59
+ return (
60
+ <EmptyPieChart
61
+ data={propsData}
62
+ emptyComponent={emptyComponent}
63
+ id={id}
64
+ localeText={localeText}
65
+ size={size}
66
+ variant={variant}
67
+ {...forwardedProps}
68
+ ref={ref}
69
+ />
70
+ );
71
+ }
72
+
73
+ return (
74
+ <RenderedPieChart
75
+ caping={caping}
76
+ chartRef={chartRef}
77
+ className={classNames(PieChart.className, className)}
78
+ data={propsData}
79
+ id={id}
80
+ isSliceSelected={isSliceSelected}
81
+ labelDecorator={labelDecorator}
82
+ labelVariant={labelVariant}
83
+ middleText={middleText}
84
+ onSliceClick={onSliceClick}
85
+ others={others}
86
+ size={size}
87
+ sliceRole={sliceRole}
88
+ subtext={subtext}
89
+ text={text}
90
+ theme={theme}
91
+ tooltipVariant={tooltipVariant}
92
+ variant={variant}
93
+ {...forwardedProps}
94
+ ref={ref}
95
+ />
96
+ );
97
+ }
98
+ );
99
+ PieChart.className = CLASSNAME;
100
+ PieChart.defaultProps = DEFAULT_PROPS;
101
+ PieChart.displayName = COMPONENT_NAME;