pixel-react 1.1.1 → 1.1.2
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/.yarn/install-state.gz +0 -0
- package/lib/components/Charts/DashboardDonutChart/DashboardDonutChart.d.ts +5 -0
- package/lib/components/Charts/DashboardDonutChart/DashboardDonutChart.stories.d.ts +7 -0
- package/lib/components/Charts/DashboardDonutChart/index.d.ts +1 -0
- package/lib/components/Charts/DashboardDonutChart/types.d.ts +21 -0
- package/lib/components/Charts/PieChart/PieChart.d.ts +5 -0
- package/lib/components/Charts/PieChart/PieChart.stories.d.ts +7 -0
- package/lib/components/Charts/PieChart/index.d.ts +1 -0
- package/lib/components/Charts/PieChart/types.d.ts +27 -0
- package/lib/components/MultiSelect/MultiSelect.d.ts +1 -1
- package/lib/components/MultiSelect/MultiSelectTypes.d.ts +1 -1
- package/lib/components/NLPInput/NlpInput.d.ts +4 -0
- package/lib/components/NLPInput/NlpInput.stories.d.ts +7 -0
- package/lib/components/NLPInput/components/NlpDropDown/NlpDropDownType.d.ts +19 -0
- package/lib/components/NLPInput/components/NlpDropDown/NlpDropdown.d.ts +4 -0
- package/lib/components/NLPInput/index.d.ts +1 -0
- package/lib/components/NLPInput/type.d.ts +70 -0
- package/lib/components/Table/Table.d.ts +1 -1
- package/lib/index.d.ts +73 -18
- package/lib/index.esm.js +1412 -774
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1414 -773
- package/lib/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/getEncryptedData/getEncryptedData.d.ts +2 -0
- package/lib/utils/getEncryptedData/getEncryptedData.stories.d.ts +6 -0
- package/package.json +2 -1
- package/src/StyleGuide/ColorPalette/ColorPalette.tsx +2 -4
- package/src/StyleGuide/ColorPalette/colorPaletteList.ts +95 -20
- package/src/assets/Themes/BaseTheme.scss +2 -3
- package/src/assets/Themes/DarkTheme.scss +9 -8
- package/src/assets/icons/wswb_delete_icon.svg +4 -0
- package/src/assets/icons/wswb_plus_icon.svg +5 -0
- package/src/components/Charts/DashboardDonutChart/DashboardDonutChart.scss +145 -0
- package/src/components/Charts/DashboardDonutChart/DashboardDonutChart.stories.tsx +52 -0
- package/src/components/Charts/DashboardDonutChart/DashboardDonutChart.tsx +335 -0
- package/src/components/Charts/DashboardDonutChart/index.ts +1 -0
- package/src/components/Charts/DashboardDonutChart/types.ts +33 -0
- package/src/components/Charts/PieChart/PieChart.scss +39 -0
- package/src/components/Charts/PieChart/PieChart.stories.tsx +46 -0
- package/src/components/Charts/PieChart/PieChart.tsx +193 -0
- package/src/components/Charts/PieChart/index.ts +1 -0
- package/src/components/Charts/PieChart/types.ts +28 -0
- package/src/components/Icon/iconList.ts +6 -0
- package/src/components/Modal/modal.scss +1 -1
- package/src/components/MultiSelect/MultiSelect.stories.tsx +2 -3
- package/src/components/MultiSelect/MultiSelect.tsx +35 -23
- package/src/components/MultiSelect/MultiSelectTypes.ts +1 -1
- package/src/components/NLPInput/NLPInput.scss +246 -0
- package/src/components/NLPInput/NlpInput.stories.tsx +136 -0
- package/src/components/NLPInput/NlpInput.tsx +374 -0
- package/src/components/NLPInput/components/NlpDropDown/NlpDropDownType.ts +60 -0
- package/src/components/NLPInput/components/NlpDropDown/NlpDropdown.scss +83 -0
- package/src/components/NLPInput/components/NlpDropDown/NlpDropdown.tsx +180 -0
- package/src/components/NLPInput/index.ts +1 -0
- package/src/components/NLPInput/type.tsx +124 -0
- package/src/components/Table/Table.scss +5 -0
- package/src/components/Table/Table.stories.tsx +12 -0
- package/src/components/Table/Table.tsx +25 -14
- package/src/components/TextArea/Textarea.scss +1 -1
- package/src/index.ts +6 -1
- package/src/utils/getEncryptedData/getEncryptedData.stories.tsx +55 -0
- package/src/utils/getEncryptedData/getEncryptedData.ts +10 -0
- package/lib/components/AddButton/AddButton.d.ts +0 -5
- package/lib/components/AddButton/AddButton.stories.d.ts +0 -6
- package/lib/components/AddButton/index.d.ts +0 -1
- package/lib/components/AddButton/types.d.ts +0 -4
@@ -0,0 +1,335 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { DashboardDonutChartProps, ChartItem, LegendType } from './types';
|
3
|
+
import Typography from '../../Typography';
|
4
|
+
import './DashBoardDonutChart.scss';
|
5
|
+
import classNames from 'classnames';
|
6
|
+
|
7
|
+
const calculateArc = (
|
8
|
+
x: number,
|
9
|
+
y: number,
|
10
|
+
radius: number,
|
11
|
+
startAngle: number,
|
12
|
+
endAngle: number
|
13
|
+
): string => {
|
14
|
+
const startX = x + radius * Math.cos(startAngle);
|
15
|
+
const startY = y + radius * Math.sin(startAngle);
|
16
|
+
const endX = x + radius * Math.cos(endAngle);
|
17
|
+
const endY = y + radius * Math.sin(endAngle);
|
18
|
+
const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0;
|
19
|
+
|
20
|
+
return `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY}`;
|
21
|
+
};
|
22
|
+
|
23
|
+
const colorMapping = [
|
24
|
+
'var(--status-success-text-color)',
|
25
|
+
'var(--status-rejected-text-color)',
|
26
|
+
'var(--status-warning-text-color)',
|
27
|
+
'var(--status-skipped-text-color)',
|
28
|
+
'var(--brand-color)',
|
29
|
+
];
|
30
|
+
|
31
|
+
const DashboardDonutChart: React.FC<DashboardDonutChartProps> = ({
|
32
|
+
radius = 60,
|
33
|
+
lineWidth = 15,
|
34
|
+
statusValues = [],
|
35
|
+
gapAngle = 0,
|
36
|
+
legendDetailsType = '',
|
37
|
+
isLegendDetails = true,
|
38
|
+
legendType = 'numberLegend',
|
39
|
+
showOnlyLabel = false,
|
40
|
+
}) => {
|
41
|
+
const [hoveredItemIndex, setHoveredItemIndex] = useState<number | null>(null);
|
42
|
+
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
|
43
|
+
const [showTooltip, setShowTooltip] = useState<boolean>(false);
|
44
|
+
const total = statusValues?.reduce((acc, { value }) => acc + value, 0);
|
45
|
+
const nonZeroValues = statusValues?.filter(({ value }) => value > 0);
|
46
|
+
|
47
|
+
// Calculate angles and gaps
|
48
|
+
const TOTAL_GAP_ANGLE = gapAngle * nonZeroValues.length;
|
49
|
+
let remainingAngle = 2 * Math.PI - TOTAL_GAP_ANGLE;
|
50
|
+
let currentAngle = Math.PI / 2;
|
51
|
+
|
52
|
+
const MIN_PERCENTAGE = 1;
|
53
|
+
const MIN_ANGLE = (MIN_PERCENTAGE / 100) * (2 * Math.PI);
|
54
|
+
let minAngleTotal = 0;
|
55
|
+
|
56
|
+
// Adjust for small angles
|
57
|
+
nonZeroValues.forEach(({ value }) => {
|
58
|
+
const valuePercentage = value / total;
|
59
|
+
const angle = Math.max(valuePercentage * (2 * Math.PI), MIN_ANGLE);
|
60
|
+
minAngleTotal += angle;
|
61
|
+
remainingAngle -= angle;
|
62
|
+
});
|
63
|
+
|
64
|
+
const handleMouseEnter = (itemIndex: number) => {
|
65
|
+
setHoveredItemIndex(itemIndex);
|
66
|
+
setShowTooltip(true);
|
67
|
+
};
|
68
|
+
const handleMouseLeave = () => {
|
69
|
+
setTooltipPosition({ x: 0, y: 0 });
|
70
|
+
setHoveredItemIndex(null);
|
71
|
+
setShowTooltip(false);
|
72
|
+
};
|
73
|
+
const handleMouseMove = (event: React.MouseEvent) => {
|
74
|
+
setTooltipPosition({
|
75
|
+
x: event.clientX + 10,
|
76
|
+
y: event.clientY + 10,
|
77
|
+
});
|
78
|
+
};
|
79
|
+
|
80
|
+
const SVG_PADDING = 4;
|
81
|
+
const DONUT_SVG_SIZE = (radius + lineWidth) * 2 + SVG_PADDING * 2;
|
82
|
+
|
83
|
+
const renderArc = (chartItem: ChartItem, endAngle: number, i: number) => {
|
84
|
+
const isFullCircle = nonZeroValues.length === 1;
|
85
|
+
|
86
|
+
// Full circle handling
|
87
|
+
const foregroundArcPath = isFullCircle
|
88
|
+
? calculateArc(0, 0, radius, 0, 2 * Math.PI)
|
89
|
+
: calculateArc(0, 0, radius, currentAngle, endAngle);
|
90
|
+
|
91
|
+
currentAngle = endAngle + gapAngle;
|
92
|
+
|
93
|
+
return (
|
94
|
+
<g key={i}>
|
95
|
+
{/* Main arc */}
|
96
|
+
<path
|
97
|
+
d={foregroundArcPath}
|
98
|
+
fill="none"
|
99
|
+
stroke={
|
100
|
+
chartItem?.color
|
101
|
+
? chartItem.color
|
102
|
+
: colorMapping[i % colorMapping.length]
|
103
|
+
}
|
104
|
+
strokeWidth={lineWidth}
|
105
|
+
onMouseEnter={() => handleMouseEnter(i)}
|
106
|
+
onMouseLeave={handleMouseLeave}
|
107
|
+
strokeOpacity={0.8}
|
108
|
+
onMouseMove={handleMouseMove}
|
109
|
+
/>
|
110
|
+
</g>
|
111
|
+
);
|
112
|
+
};
|
113
|
+
|
114
|
+
const renderTooltip = () => {
|
115
|
+
return (
|
116
|
+
<div
|
117
|
+
className="ff-donut-chart-tooltip"
|
118
|
+
style={{
|
119
|
+
left: tooltipPosition.x,
|
120
|
+
top: tooltipPosition.y,
|
121
|
+
}}
|
122
|
+
>
|
123
|
+
<Typography fontSize={12}>
|
124
|
+
{hoveredItemIndex !== null &&
|
125
|
+
`${nonZeroValues[hoveredItemIndex]?.key} : `}
|
126
|
+
</Typography>
|
127
|
+
<Typography fontSize={12}>
|
128
|
+
{hoveredItemIndex !== null && nonZeroValues[hoveredItemIndex]?.value}
|
129
|
+
</Typography>
|
130
|
+
</div>
|
131
|
+
);
|
132
|
+
};
|
133
|
+
|
134
|
+
const renderLegend = (legendData: ChartItem[], legendType: LegendType) => {
|
135
|
+
switch (legendType) {
|
136
|
+
case 'numberLegend':
|
137
|
+
return (
|
138
|
+
<div className="ff-legend-container numberLegend">
|
139
|
+
{legendData.map((item, index) => (
|
140
|
+
<div className="ff-legend-item" key={index}>
|
141
|
+
<Typography
|
142
|
+
fontSize={22}
|
143
|
+
fontWeight="semi-bold"
|
144
|
+
className="ff-legend-value"
|
145
|
+
color={
|
146
|
+
item.color
|
147
|
+
? item.color
|
148
|
+
: colorMapping[index % colorMapping.length]
|
149
|
+
}
|
150
|
+
>
|
151
|
+
{item.value}
|
152
|
+
</Typography>
|
153
|
+
<Typography fontSize={12} className="ff-legend-key">
|
154
|
+
{item.key}
|
155
|
+
</Typography>
|
156
|
+
</div>
|
157
|
+
))}
|
158
|
+
</div>
|
159
|
+
);
|
160
|
+
|
161
|
+
case 'pillLegend':
|
162
|
+
return (
|
163
|
+
<div className="ff-legend-container pillLegend">
|
164
|
+
{legendData.map((item, index) => (
|
165
|
+
<div className="ff-legend-item" key={index}>
|
166
|
+
<span
|
167
|
+
className="ff-legend-capsule"
|
168
|
+
style={{
|
169
|
+
backgroundColor: item.color
|
170
|
+
? item.color
|
171
|
+
: colorMapping[index % colorMapping.length],
|
172
|
+
}}
|
173
|
+
>
|
174
|
+
<Typography fontSize={10}>{item.value}</Typography>
|
175
|
+
</span>
|
176
|
+
<Typography className="ff-legend-key">{item.key}</Typography>
|
177
|
+
</div>
|
178
|
+
))}
|
179
|
+
</div>
|
180
|
+
);
|
181
|
+
|
182
|
+
case 'memoryLegend':
|
183
|
+
return (
|
184
|
+
<div className="ff-legend-container memoryLegend">
|
185
|
+
{legendData.map((item, index) => (
|
186
|
+
<React.Fragment key={index}>
|
187
|
+
<div className="ff-legend-item">
|
188
|
+
<Typography
|
189
|
+
fontSize={22}
|
190
|
+
fontWeight="medium"
|
191
|
+
className="ff-legend-value"
|
192
|
+
color={
|
193
|
+
item.color
|
194
|
+
? item.color
|
195
|
+
: colorMapping[index % colorMapping.length]
|
196
|
+
}
|
197
|
+
>
|
198
|
+
{item.value}
|
199
|
+
</Typography>
|
200
|
+
<Typography className="ff-legend-key">{item.key}</Typography>
|
201
|
+
</div>
|
202
|
+
</React.Fragment>
|
203
|
+
))}
|
204
|
+
</div>
|
205
|
+
);
|
206
|
+
|
207
|
+
case 'tableLegend':
|
208
|
+
return (
|
209
|
+
<div className="ff-legend-table-wrapper">
|
210
|
+
<table className="ff-legend-table tableLegend">
|
211
|
+
<thead>
|
212
|
+
<tr>
|
213
|
+
<th className="ff-table-header">Name</th>
|
214
|
+
<th className="ff-table-header">%</th>
|
215
|
+
<th className="ff-table-header">Count</th>
|
216
|
+
</tr>
|
217
|
+
</thead>
|
218
|
+
<tbody>
|
219
|
+
{legendData.map((item, index) => (
|
220
|
+
<tr
|
221
|
+
className="ff-legend-item"
|
222
|
+
key={index}
|
223
|
+
onMouseEnter={() => setHoveredItemIndex(index)}
|
224
|
+
onMouseLeave={() => setHoveredItemIndex(null)}
|
225
|
+
>
|
226
|
+
<td className="ff-legend-name">
|
227
|
+
<span
|
228
|
+
className="ff-legend-capsule"
|
229
|
+
style={{
|
230
|
+
backgroundColor: item.color
|
231
|
+
? item.color
|
232
|
+
: colorMapping[index % colorMapping.length],
|
233
|
+
}}
|
234
|
+
>
|
235
|
+
<Typography fontSize={10}>{item.value}</Typography>
|
236
|
+
</span>
|
237
|
+
<Typography fontSize={10}>{item.key}</Typography>
|
238
|
+
</td>
|
239
|
+
<td className="ff-legend-percentage">
|
240
|
+
{((item.value / total) * 100).toFixed(1)}
|
241
|
+
</td>
|
242
|
+
<td className="ff-legend-count">{item.value}</td>
|
243
|
+
</tr>
|
244
|
+
))}
|
245
|
+
</tbody>
|
246
|
+
</table>
|
247
|
+
</div>
|
248
|
+
);
|
249
|
+
|
250
|
+
default:
|
251
|
+
return null;
|
252
|
+
}
|
253
|
+
};
|
254
|
+
|
255
|
+
return (
|
256
|
+
<div
|
257
|
+
className={classNames('ff-dashboard-donut-chart-section', {
|
258
|
+
'ff-dashboard-donut-chart-section-table-legend':
|
259
|
+
legendType === 'tableLegend',
|
260
|
+
})}
|
261
|
+
>
|
262
|
+
<div className="ff-dashboard-donut-chart-svg-container">
|
263
|
+
<svg
|
264
|
+
width={DONUT_SVG_SIZE}
|
265
|
+
height={DONUT_SVG_SIZE}
|
266
|
+
viewBox={`0 0 ${DONUT_SVG_SIZE} ${DONUT_SVG_SIZE}`}
|
267
|
+
>
|
268
|
+
<g
|
269
|
+
transform={`translate(${radius + lineWidth + SVG_PADDING}, ${
|
270
|
+
radius + lineWidth + SVG_PADDING
|
271
|
+
})`}
|
272
|
+
>
|
273
|
+
{nonZeroValues?.map((status, i) => {
|
274
|
+
const valuePercentage = status.value / total;
|
275
|
+
let angle = Math.max(valuePercentage * (2 * Math.PI), MIN_ANGLE);
|
276
|
+
angle += remainingAngle * (valuePercentage / total);
|
277
|
+
const endAngle = currentAngle + angle;
|
278
|
+
|
279
|
+
return renderArc(status, endAngle, i);
|
280
|
+
})}
|
281
|
+
{showOnlyLabel ? (
|
282
|
+
<text
|
283
|
+
x="0"
|
284
|
+
y="5"
|
285
|
+
className="ff-svg-label-text"
|
286
|
+
textAnchor="middle"
|
287
|
+
fill={colorMapping[3]}
|
288
|
+
>
|
289
|
+
{legendDetailsType}
|
290
|
+
</text>
|
291
|
+
) : (
|
292
|
+
(legendType !== 'tableLegend' || hoveredItemIndex !== null) && (
|
293
|
+
<>
|
294
|
+
<text x="0" y="5" textAnchor="middle" fill={colorMapping[3]}>
|
295
|
+
{legendType === 'tableLegend' &&
|
296
|
+
hoveredItemIndex !== null &&
|
297
|
+
nonZeroValues[hoveredItemIndex]
|
298
|
+
? `${(
|
299
|
+
(nonZeroValues[hoveredItemIndex].value / total) *
|
300
|
+
100
|
301
|
+
).toFixed(1)}%`
|
302
|
+
: total}
|
303
|
+
</text>
|
304
|
+
|
305
|
+
<text
|
306
|
+
x="0"
|
307
|
+
y="26"
|
308
|
+
textAnchor="middle"
|
309
|
+
fill="var(--text-color)"
|
310
|
+
>
|
311
|
+
{legendType === 'tableLegend' &&
|
312
|
+
hoveredItemIndex !== null &&
|
313
|
+
nonZeroValues[hoveredItemIndex]
|
314
|
+
? nonZeroValues[hoveredItemIndex].key
|
315
|
+
: legendDetailsType}
|
316
|
+
</text>
|
317
|
+
</>
|
318
|
+
)
|
319
|
+
)}
|
320
|
+
</g>
|
321
|
+
</svg>
|
322
|
+
{legendType === 'tableLegend' && (
|
323
|
+
<div>
|
324
|
+
<Typography fontWeight="semi-bold">{legendDetailsType} </Typography>
|
325
|
+
<Typography> {`Total ${legendDetailsType} - ${total}`} </Typography>
|
326
|
+
</div>
|
327
|
+
)}
|
328
|
+
{showTooltip && renderTooltip()}
|
329
|
+
</div>
|
330
|
+
{isLegendDetails && renderLegend(nonZeroValues, legendType)}
|
331
|
+
</div>
|
332
|
+
);
|
333
|
+
};
|
334
|
+
|
335
|
+
export default DashboardDonutChart;
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from './DashboardDonutChart';
|
@@ -0,0 +1,33 @@
|
|
1
|
+
export type Status =
|
2
|
+
| 'passed'
|
3
|
+
| 'failed'
|
4
|
+
| 'warning'
|
5
|
+
| 'skipped'
|
6
|
+
| 'Passed'
|
7
|
+
| 'Failed'
|
8
|
+
| 'Skipped'
|
9
|
+
| 'Warning';
|
10
|
+
|
11
|
+
export type StatusValue = {
|
12
|
+
status: Status;
|
13
|
+
value: number;
|
14
|
+
};
|
15
|
+
|
16
|
+
export type ChartItem = {
|
17
|
+
key: string;
|
18
|
+
value: number;
|
19
|
+
color?: string;
|
20
|
+
};
|
21
|
+
|
22
|
+
export type LegendType = 'numberLegend' | 'pillLegend' | 'memoryLegend' | 'tableLegend'
|
23
|
+
|
24
|
+
export type DashboardDonutChartProps = {
|
25
|
+
radius: number;
|
26
|
+
lineWidth: number;
|
27
|
+
statusValues: ChartItem[];
|
28
|
+
legendDetailsType: string;
|
29
|
+
isLegendDetails: boolean;
|
30
|
+
gapAngle?: number;
|
31
|
+
legendType: LegendType;
|
32
|
+
showOnlyLabel : boolean;
|
33
|
+
};
|
@@ -0,0 +1,39 @@
|
|
1
|
+
.ff-pie-chart-container {
|
2
|
+
display: flex;
|
3
|
+
flex-direction: column;
|
4
|
+
align-items: center;
|
5
|
+
font-size: var(--fontSize);
|
6
|
+
.ff-pie-chart-border {
|
7
|
+
display: inline-block;
|
8
|
+
border: 2px solid var(--pie-chart-border-color);
|
9
|
+
border-radius: 50%;
|
10
|
+
}
|
11
|
+
|
12
|
+
.ff-pie-chart-tooltip {
|
13
|
+
position: absolute;
|
14
|
+
top: 50%;
|
15
|
+
left: 50%;
|
16
|
+
transform: translate(-50%, -100%);
|
17
|
+
padding: 8px;
|
18
|
+
border-radius: 4px;
|
19
|
+
background-color: var(--tooltip-bg-color);
|
20
|
+
color: var(--primary-icon-color);
|
21
|
+
border: 10px solid;
|
22
|
+
font-size: 14px;
|
23
|
+
font-weight: 400;
|
24
|
+
pointer-events: none;
|
25
|
+
opacity: 0.8;
|
26
|
+
}
|
27
|
+
|
28
|
+
.ff-pie-chart-legend {
|
29
|
+
display: grid;
|
30
|
+
grid-template-columns: repeat(3, 1fr);
|
31
|
+
gap: 16px;
|
32
|
+
.ff-pie-chart-legend-item {
|
33
|
+
display: flex;
|
34
|
+
flex-direction: column;
|
35
|
+
align-items: center;
|
36
|
+
text-align: center;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
2
|
+
import PieChart from './PieChart';
|
3
|
+
|
4
|
+
const meta: Meta<typeof PieChart> = {
|
5
|
+
title: 'Components/PieChart',
|
6
|
+
component: PieChart,
|
7
|
+
parameters: {
|
8
|
+
layout: 'centered',
|
9
|
+
},
|
10
|
+
tags: ['autodocs'],
|
11
|
+
argTypes: {
|
12
|
+
radius: { control: 'number' },
|
13
|
+
colors: { control: 'object' },
|
14
|
+
data: { control: 'object' },
|
15
|
+
chartBorder: { control: 'boolean' },
|
16
|
+
},
|
17
|
+
};
|
18
|
+
|
19
|
+
export default meta;
|
20
|
+
type Story = StoryObj<typeof PieChart>;
|
21
|
+
|
22
|
+
export const PieChartDashBoard: Story = {
|
23
|
+
args: {
|
24
|
+
radius: 55,
|
25
|
+
colors: ['#71347B', '#4C90FF', '#F8A509'],
|
26
|
+
data: [
|
27
|
+
{ label: 'SuperAdmin', value: 2 },
|
28
|
+
{ label: 'Admin', value: 6 },
|
29
|
+
{ label: 'Users', value: 2 },
|
30
|
+
],
|
31
|
+
chartBorder: false,
|
32
|
+
},
|
33
|
+
};
|
34
|
+
|
35
|
+
export const PieChartDashBoardWithBorder: Story = {
|
36
|
+
args: {
|
37
|
+
radius: 55,
|
38
|
+
colors: ['#b6b6b6', '#08CB84', '#BE3131'],
|
39
|
+
data: [
|
40
|
+
{ label: 'All User', value: 0 },
|
41
|
+
{ label: 'Active', value: 8 },
|
42
|
+
{ label: 'Pending', value: 2 },
|
43
|
+
],
|
44
|
+
chartBorder: true,
|
45
|
+
},
|
46
|
+
};
|
@@ -0,0 +1,193 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import './PieChart.scss';
|
3
|
+
import { PieChartProps, ArcParams, ArcResult, ArcAnglesResult } from './types';
|
4
|
+
import Typography from '../../Typography';
|
5
|
+
|
6
|
+
const calculateArc = ({
|
7
|
+
x,
|
8
|
+
y,
|
9
|
+
radius,
|
10
|
+
startAngle,
|
11
|
+
endAngle,
|
12
|
+
}: ArcParams): ArcResult => {
|
13
|
+
const startX = x + radius * Math.cos(startAngle);
|
14
|
+
const startY = y + radius * Math.sin(startAngle);
|
15
|
+
const endX = x + radius * Math.cos(endAngle);
|
16
|
+
const endY = y + radius * Math.sin(endAngle);
|
17
|
+
const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0;
|
18
|
+
|
19
|
+
return `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY} L ${x} ${y} Z`;
|
20
|
+
};
|
21
|
+
|
22
|
+
const calculateArcAngles = (
|
23
|
+
value: number,
|
24
|
+
total: number,
|
25
|
+
currentAngle: number,
|
26
|
+
radius: number
|
27
|
+
): ArcAnglesResult => {
|
28
|
+
if (total === 0) {
|
29
|
+
return {
|
30
|
+
endAngle: currentAngle,
|
31
|
+
backgroundArcPath: '',
|
32
|
+
foregroundArcPath: '',
|
33
|
+
percentage: 0,
|
34
|
+
};
|
35
|
+
}
|
36
|
+
|
37
|
+
const percentage = value / total;
|
38
|
+
const angleIncrement = percentage * 2 * Math.PI;
|
39
|
+
const startAngle = currentAngle;
|
40
|
+
const endAngle = startAngle + angleIncrement;
|
41
|
+
|
42
|
+
const path = calculateArc({
|
43
|
+
x: 0,
|
44
|
+
y: 0,
|
45
|
+
radius,
|
46
|
+
startAngle,
|
47
|
+
endAngle,
|
48
|
+
});
|
49
|
+
|
50
|
+
return {
|
51
|
+
endAngle,
|
52
|
+
backgroundArcPath: path,
|
53
|
+
foregroundArcPath: path,
|
54
|
+
percentage,
|
55
|
+
};
|
56
|
+
};
|
57
|
+
|
58
|
+
type Tooltip = {
|
59
|
+
label: string;
|
60
|
+
value: number;
|
61
|
+
color: string;
|
62
|
+
};
|
63
|
+
|
64
|
+
const PieChart: React.FC<PieChartProps> = ({
|
65
|
+
radius = 15,
|
66
|
+
colors = [],
|
67
|
+
data = [],
|
68
|
+
chartBorder = false,
|
69
|
+
}) => {
|
70
|
+
const [tooltip, setTooltip] = useState<Tooltip | null>(null);
|
71
|
+
const [tooltipPosition, setTooltipPosition] = useState<{
|
72
|
+
x: number;
|
73
|
+
y: number;
|
74
|
+
}>({ x: 0, y: 0 });
|
75
|
+
|
76
|
+
const total = data.reduce((acc, item) => acc + item.value, 0);
|
77
|
+
let currentAngle = -Math.PI / 2;
|
78
|
+
const svgSize = 2 * (radius + 5);
|
79
|
+
|
80
|
+
const chartData =
|
81
|
+
chartBorder && data.length > 0
|
82
|
+
? [
|
83
|
+
{
|
84
|
+
label: data[0]?.label || '',
|
85
|
+
value: data.slice(1).reduce((acc, item) => acc + item.value, 0),
|
86
|
+
},
|
87
|
+
...data.slice(1),
|
88
|
+
]
|
89
|
+
: data;
|
90
|
+
|
91
|
+
const handleMouseMove = (e: React.MouseEvent) => {
|
92
|
+
const { clientX: x, clientY: y } = e;
|
93
|
+
setTooltipPosition({ x, y });
|
94
|
+
};
|
95
|
+
|
96
|
+
const handleMouseEnter = (
|
97
|
+
item: { label: string; value: number },
|
98
|
+
color: string
|
99
|
+
) => {
|
100
|
+
setTooltip({ label: item.label, value: item.value, color });
|
101
|
+
};
|
102
|
+
|
103
|
+
const handleMouseLeave = () => {
|
104
|
+
setTooltip(null);
|
105
|
+
};
|
106
|
+
const legendItems = chartData.map((item, index) => (
|
107
|
+
<div key={item.label} className="ff-pie-chart-legend-item">
|
108
|
+
<Typography
|
109
|
+
as="div"
|
110
|
+
fontSize={24}
|
111
|
+
fontWeight="semi-bold"
|
112
|
+
lineHeight="36px"
|
113
|
+
color={colors[index % colors.length] || ''}
|
114
|
+
>
|
115
|
+
{item.value}
|
116
|
+
</Typography>
|
117
|
+
<Typography
|
118
|
+
as="div"
|
119
|
+
fontSize={10}
|
120
|
+
fontWeight="regular"
|
121
|
+
lineHeight="18px"
|
122
|
+
className="ff-Pie-chart-legend-value"
|
123
|
+
>
|
124
|
+
{item.label}
|
125
|
+
</Typography>
|
126
|
+
</div>
|
127
|
+
));
|
128
|
+
|
129
|
+
return (
|
130
|
+
<div className="ff-pie-chart-container" onMouseMove={handleMouseMove}>
|
131
|
+
<div
|
132
|
+
className={` ${chartBorder ? 'ff-pie-chart-border' : ''}`}
|
133
|
+
style={{ width: svgSize, height: svgSize }}
|
134
|
+
>
|
135
|
+
<svg
|
136
|
+
width={svgSize}
|
137
|
+
height={svgSize}
|
138
|
+
viewBox={`0 0 ${svgSize} ${svgSize}`}
|
139
|
+
>
|
140
|
+
<g transform={`translate(${radius + 5}, ${radius + 5})`}>
|
141
|
+
{chartData.map((item, index) => {
|
142
|
+
const { endAngle, backgroundArcPath } = calculateArcAngles(
|
143
|
+
item.value,
|
144
|
+
total,
|
145
|
+
currentAngle,
|
146
|
+
radius
|
147
|
+
);
|
148
|
+
currentAngle = endAngle;
|
149
|
+
const color = colors[index % colors.length] || '';
|
150
|
+
|
151
|
+
return (
|
152
|
+
<g key={item.label}>
|
153
|
+
<path
|
154
|
+
d={backgroundArcPath}
|
155
|
+
fill={color}
|
156
|
+
stroke="white"
|
157
|
+
strokeWidth={0.5}
|
158
|
+
onMouseEnter={() => handleMouseEnter(item, color)}
|
159
|
+
onMouseLeave={handleMouseLeave}
|
160
|
+
/>
|
161
|
+
<text
|
162
|
+
x={(radius / 2) * Math.cos((currentAngle + endAngle) / 2)}
|
163
|
+
y={(radius / 2) * Math.sin((currentAngle + endAngle) / 2)}
|
164
|
+
fill="white"
|
165
|
+
textAnchor="middle"
|
166
|
+
dominantBaseline="central"
|
167
|
+
></text>
|
168
|
+
</g>
|
169
|
+
);
|
170
|
+
})}
|
171
|
+
</g>
|
172
|
+
</svg>
|
173
|
+
</div>
|
174
|
+
|
175
|
+
{tooltip && (
|
176
|
+
<div
|
177
|
+
className="ff-pie-chart-tooltip"
|
178
|
+
style={{
|
179
|
+
top: tooltipPosition.y,
|
180
|
+
left: tooltipPosition.x,
|
181
|
+
border: `2px solid ${tooltip.color}`,
|
182
|
+
}}
|
183
|
+
>
|
184
|
+
{tooltip.label} : {tooltip.value}
|
185
|
+
</div>
|
186
|
+
)}
|
187
|
+
|
188
|
+
<div className="ff-pie-chart-legend">{legendItems}</div>
|
189
|
+
</div>
|
190
|
+
);
|
191
|
+
};
|
192
|
+
|
193
|
+
export default PieChart;
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from './PieChart';
|
@@ -0,0 +1,28 @@
|
|
1
|
+
export type Status = {
|
2
|
+
status: 'Passed';
|
3
|
+
value: number;
|
4
|
+
};
|
5
|
+
|
6
|
+
export interface PieChartProps {
|
7
|
+
radius: number;
|
8
|
+
data: Array<{ label: string; value: number }>;
|
9
|
+
colors: string[];
|
10
|
+
chartBorder: boolean;
|
11
|
+
}
|
12
|
+
|
13
|
+
export type ArcParams = {
|
14
|
+
x: number;
|
15
|
+
y: number;
|
16
|
+
radius: number;
|
17
|
+
startAngle: number;
|
18
|
+
endAngle: number;
|
19
|
+
};
|
20
|
+
|
21
|
+
export type ArcResult = string;
|
22
|
+
|
23
|
+
export type ArcAnglesResult = {
|
24
|
+
endAngle: number;
|
25
|
+
backgroundArcPath: string;
|
26
|
+
foregroundArcPath: string;
|
27
|
+
percentage: number;
|
28
|
+
};
|
@@ -65,6 +65,10 @@ import AndroidIcon from '../../assets/icons/android_icon.svg?react';
|
|
65
65
|
|
66
66
|
import SwitchLicenseIcon from '../../assets/icons/switch_license_icon.svg?react';
|
67
67
|
import FireflinkLogo from '../../assets/icons/fireflink_logo.svg?react';
|
68
|
+
import WSWBDeleteIcon from '../../assets/icons/wswb_delete_icon.svg?react'
|
69
|
+
import WSWBPlusIcon from '../../assets/icons/wswb_plus_icon.svg?react'
|
70
|
+
|
71
|
+
|
68
72
|
//icons
|
69
73
|
Components['delete_info'] = DeleteInfoIcon;
|
70
74
|
Components['success'] = ToastSuccessIcon;
|
@@ -130,5 +134,7 @@ Components['android_icon'] = AndroidIcon;
|
|
130
134
|
|
131
135
|
Components['select_license'] = SwitchLicenseIcon;
|
132
136
|
Components['fireflink-logo'] = FireflinkLogo;
|
137
|
+
Components['wswb_delete_icon'] = WSWBDeleteIcon;
|
138
|
+
Components['wswb_plus_icon'] = WSWBPlusIcon;
|
133
139
|
|
134
140
|
export default Components;
|