@spteck/fluentui-react-charts 0.1.8

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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +462 -0
  3. package/dist/charts/BarChart/BarChart.d.ts +16 -0
  4. package/dist/charts/BarChart/index.d.ts +1 -0
  5. package/dist/charts/ComboChart/ComboChart.d.ts +16 -0
  6. package/dist/charts/ComboChart/index.d.ts +1 -0
  7. package/dist/charts/Doughnut/DoughnutChart.d.ts +14 -0
  8. package/dist/charts/Doughnut/index.d.ts +1 -0
  9. package/dist/charts/PieChart/PieChart.d.ts +14 -0
  10. package/dist/charts/PieChart/index.d.ts +1 -0
  11. package/dist/charts/areaChart/AreaChart.d.ts +15 -0
  12. package/dist/charts/areaChart/index.d.ts +1 -0
  13. package/dist/charts/barHorizontalChart/BarHotizontalChart.d.ts +15 -0
  14. package/dist/charts/barHorizontalChart/index.d.ts +1 -0
  15. package/dist/charts/bubbleChart/BubbleChart.d.ts +15 -0
  16. package/dist/charts/bubbleChart/index.d.ts +1 -0
  17. package/dist/charts/floatBarChart/FloatBarChart.d.ts +14 -0
  18. package/dist/charts/floatBarChart/index.d.ts +1 -0
  19. package/dist/charts/lineChart/LineChart.d.ts +14 -0
  20. package/dist/charts/lineChart/index.d.ts +1 -0
  21. package/dist/charts/polarChart/PolarChart.d.ts +14 -0
  22. package/dist/charts/polarChart/index.d.ts +1 -0
  23. package/dist/charts/radarChart/RadarChart.d.ts +14 -0
  24. package/dist/charts/radarChart/index.d.ts +1 -0
  25. package/dist/charts/scatterChart/ScatterChart.d.ts +14 -0
  26. package/dist/charts/scatterChart/index.d.ts +1 -0
  27. package/dist/charts/stackedLineChart/StackedLineChart.d.ts +14 -0
  28. package/dist/charts/stackedLineChart/index.d.ts +1 -0
  29. package/dist/charts/steamChart/SteamChart.d.ts +14 -0
  30. package/dist/charts/steamChart/index.d.ts +1 -0
  31. package/dist/components/DashBoard.d.ts +3 -0
  32. package/dist/components/RenderLegend/RenderLegend.d.ts +11 -0
  33. package/dist/components/RenderTooltip/RenderTooltip.d.ts +14 -0
  34. package/dist/components/buttonMenu/ButtonMenu.d.ts +3 -0
  35. package/dist/components/buttonMenu/IButtonMenuOption.d.ts +10 -0
  36. package/dist/components/buttonMenu/IButtonMenuProps.d.ts +37 -0
  37. package/dist/components/index.d.ts +15 -0
  38. package/dist/components/legendContainer/LegendContainer.d.ts +16 -0
  39. package/dist/components/legendeButton/LegendButton.d.ts +11 -0
  40. package/dist/components/renderSliceLegend/RenderSliceLegend.d.ts +9 -0
  41. package/dist/components/renderValueLegend/RenderValueLegend.d.ts +13 -0
  42. package/dist/components/stack/IStackProps.d.ts +76 -0
  43. package/dist/components/stack/Stack.d.ts +8 -0
  44. package/dist/components/themeProvider/ThemeProvider.d.ts +15 -0
  45. package/dist/constants/Constants.d.ts +1 -0
  46. package/dist/fluentui-react-charts.cjs.development.js +2916 -0
  47. package/dist/fluentui-react-charts.cjs.development.js.map +1 -0
  48. package/dist/fluentui-react-charts.cjs.production.min.js +2 -0
  49. package/dist/fluentui-react-charts.cjs.production.min.js.map +1 -0
  50. package/dist/fluentui-react-charts.esm.js +2905 -0
  51. package/dist/fluentui-react-charts.esm.js.map +1 -0
  52. package/dist/graphGlobalStyles/useGraphGlobalStyles.d.ts +5 -0
  53. package/dist/hooks/index.d.ts +1 -0
  54. package/dist/hooks/useGraphUtils.d.ts +38 -0
  55. package/dist/hooks/useResponsiveLegend.d.ts +8 -0
  56. package/dist/index.d.ts +3 -0
  57. package/dist/index.js +8 -0
  58. package/dist/models/IChart.d.ts +25 -0
  59. package/dist/models/index.d.ts +1 -0
  60. package/package.json +66 -0
  61. package/src/assets/sample1.png +0 -0
  62. package/src/assets/sample2.png +0 -0
  63. package/src/assets/sample3.png +0 -0
  64. package/src/charts/BarChart/BarChart.tsx +227 -0
  65. package/src/charts/BarChart/README.MD +335 -0
  66. package/src/charts/BarChart/index.ts +1 -0
  67. package/src/charts/ComboChart/ComboChart.tsx +209 -0
  68. package/src/charts/ComboChart/README.MD +347 -0
  69. package/src/charts/ComboChart/index.ts +1 -0
  70. package/src/charts/Doughnut/DoughnutChart.tsx +152 -0
  71. package/src/charts/Doughnut/README.MD +296 -0
  72. package/src/charts/Doughnut/index.ts +1 -0
  73. package/src/charts/PieChart/PieChart.tsx +148 -0
  74. package/src/charts/PieChart/README.MD +315 -0
  75. package/src/charts/PieChart/index.ts +1 -0
  76. package/src/charts/areaChart/AreaChart.tsx +195 -0
  77. package/src/charts/areaChart/README.MD +236 -0
  78. package/src/charts/areaChart/index.ts +1 -0
  79. package/src/charts/barHorizontalChart/BarHotizontalChart.tsx +200 -0
  80. package/src/charts/barHorizontalChart/README.MD +278 -0
  81. package/src/charts/barHorizontalChart/index.ts +2 -0
  82. package/src/charts/bubbleChart/BubbleChart.tsx +184 -0
  83. package/src/charts/bubbleChart/README.MD +275 -0
  84. package/src/charts/bubbleChart/index.ts +1 -0
  85. package/src/charts/floatBarChart/FloatBarChart.tsx +178 -0
  86. package/src/charts/floatBarChart/README.MD +354 -0
  87. package/src/charts/floatBarChart/index.ts +1 -0
  88. package/src/charts/lineChart/LineChart.tsx +200 -0
  89. package/src/charts/lineChart/README.MD +354 -0
  90. package/src/charts/lineChart/index.ts +1 -0
  91. package/src/charts/polarChart/PolarChart.tsx +161 -0
  92. package/src/charts/polarChart/README.MD +336 -0
  93. package/src/charts/polarChart/index.ts +1 -0
  94. package/src/charts/radarChart/README.MD +388 -0
  95. package/src/charts/radarChart/RadarChart.tsx +173 -0
  96. package/src/charts/radarChart/index.ts +1 -0
  97. package/src/charts/scatterChart/README.MD +335 -0
  98. package/src/charts/scatterChart/ScatterChart.tsx +155 -0
  99. package/src/charts/scatterChart/index.ts +1 -0
  100. package/src/charts/stackedLineChart/README.MD +396 -0
  101. package/src/charts/stackedLineChart/StackedLineChart.tsx +188 -0
  102. package/src/charts/stackedLineChart/index.ts +1 -0
  103. package/src/charts/steamChart/README.MD +414 -0
  104. package/src/charts/steamChart/SteamChart.tsx +236 -0
  105. package/src/charts/steamChart/index.ts +1 -0
  106. package/src/components/DashBoard.tsx +409 -0
  107. package/src/components/RenderLegend/RenderLegend.tsx +40 -0
  108. package/src/components/RenderTooltip/RenderTooltip.tsx +111 -0
  109. package/src/components/buttonMenu/ButtonMenu.tsx +186 -0
  110. package/src/components/buttonMenu/IButtonMenuOption.ts +9 -0
  111. package/src/components/buttonMenu/IButtonMenuProps.tsx +40 -0
  112. package/src/components/index.ts +15 -0
  113. package/src/components/legendContainer/LegendContainer.tsx +118 -0
  114. package/src/components/legendeButton/LegendButton.tsx +57 -0
  115. package/src/components/renderSliceLegend/RenderSliceLegend.tsx +46 -0
  116. package/src/components/renderValueLegend/RenderValueLegend.tsx +43 -0
  117. package/src/components/stack/IStackProps.tsx +94 -0
  118. package/src/components/stack/Stack.tsx +103 -0
  119. package/src/components/themeProvider/ThemeProvider.tsx +48 -0
  120. package/src/constants/Constants.tsx +23 -0
  121. package/src/graphGlobalStyles/useGraphGlobalStyles.ts +28 -0
  122. package/src/hooks/index.ts +1 -0
  123. package/src/hooks/useGraphUtils.tsx +314 -0
  124. package/src/hooks/useResponsiveLegend.ts +35 -0
  125. package/src/index.tsx +4 -0
  126. package/src/models/IChart.ts +50 -0
  127. package/src/models/index.ts +1 -0
@@ -0,0 +1,388 @@
1
+ # RadarChart Component
2
+
3
+ A powerful radar (spider/web) chart component built with Chart.js and Fluent UI React. This component displays multivariate data on a circular grid with multiple axes radiating from the center, making it perfect for comparing multiple quantitative variables across different entities or showing performance profiles.
4
+
5
+ ## Features
6
+
7
+ - **Multi-dimensional Data Visualization**: Display multiple metrics on radial axes
8
+ - **Multiple Series Support**: Compare different entities or time periods on the same chart
9
+ - **Interactive Legend**: Toggle series visibility with click interactions
10
+ - **Filled Areas**: Semi-transparent fill areas for better visual comparison
11
+ - **Fluent UI Integration**: Seamless integration with Fluent UI themes and design system
12
+ - **Data Labels**: Optional display of values directly on data points
13
+ - **Responsive Design**: Automatically adapts to container dimensions
14
+ - **TypeScript Support**: Full TypeScript support with generic types
15
+ - **Custom Tooltips**: Rich tooltips showing detailed metric information
16
+ - **Radial Grid**: Concentric circles and radial lines for value reference
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install chart.js react-chartjs-2 chartjs-plugin-datalabels @fluentui/react-components
22
+ ```
23
+
24
+ ## Basic Usage
25
+
26
+ ```tsx
27
+ import React from 'react';
28
+ import { RadarChart } from './components/radarChart/RadarChart';
29
+ import { webLightTheme } from '@fluentui/react-components';
30
+
31
+ interface SkillAssessment {
32
+ skill: string;
33
+ score: number;
34
+ }
35
+
36
+ const skillData: SkillAssessment[] = [
37
+ { skill: 'Communication', score: 85 },
38
+ { skill: 'Technical Skills', score: 92 },
39
+ { skill: 'Leadership', score: 78 },
40
+ { skill: 'Problem Solving', score: 88 },
41
+ { skill: 'Teamwork', score: 90 },
42
+ { skill: 'Creativity', score: 82 },
43
+ ];
44
+
45
+ function App() {
46
+ return (
47
+ <div style={{ width: '600px', height: '500px' }}>
48
+ <RadarChart
49
+ data={[
50
+ { label: 'Employee Skills', data: skillData }
51
+ ]}
52
+ getLabel={(datum) => datum.skill}
53
+ getValue={(datum) => datum.score}
54
+ title="Skill Assessment Profile"
55
+ theme={webLightTheme}
56
+ />
57
+ </div>
58
+ );
59
+ }
60
+ ```
61
+
62
+ ## Props
63
+
64
+ ### RadarChartProps<T>
65
+
66
+ | Prop | Type | Required | Default | Description |
67
+ |------|------|----------|---------|-------------|
68
+ | `data` | `{ label: string; data: T[] }[]` | Yes | - | Array of data series with labels and data points |
69
+ | `getLabel` | `(datum: T) => string` | Yes | - | Function to extract the axis label from each data point |
70
+ | `getValue` | `(datum: T) => number` | Yes | - | Function to extract the value from each data point |
71
+ | `title` | `string` | No | - | Chart title displayed at the top |
72
+ | `showDataLabels` | `boolean` | No | `false` | Whether to show data values on data points |
73
+ | `theme` | `Theme` | No | `webLightTheme` | Fluent UI theme object for styling |
74
+
75
+ ## Advanced Usage
76
+
77
+ ### Employee Performance Comparison
78
+
79
+ ```tsx
80
+ interface PerformanceMetric {
81
+ metric: string;
82
+ employee1: number;
83
+ employee2: number;
84
+ employee3: number;
85
+ }
86
+
87
+ const performanceData: PerformanceMetric[] = [
88
+ { metric: 'Quality', employee1: 88, employee2: 92, employee3: 85 },
89
+ { metric: 'Productivity', employee1: 85, employee2: 78, employee3: 90 },
90
+ { metric: 'Innovation', employee1: 90, employee2: 95, employee3: 82 },
91
+ { metric: 'Collaboration', employee1: 82, employee2: 88, employee3: 95 },
92
+ { metric: 'Leadership', employee1: 78, employee2: 85, employee3: 88 },
93
+ { metric: 'Communication', employee1: 92, employee2: 90, employee3: 90 },
94
+ ];
95
+
96
+ <RadarChart
97
+ data={[
98
+ {
99
+ label: 'Alice Johnson',
100
+ data: performanceData.map(d => ({ metric: d.metric, value: d.employee1 }))
101
+ },
102
+ {
103
+ label: 'Bob Smith',
104
+ data: performanceData.map(d => ({ metric: d.metric, value: d.employee2 }))
105
+ },
106
+ {
107
+ label: 'Carol Davis',
108
+ data: performanceData.map(d => ({ metric: d.metric, value: d.employee3 }))
109
+ }
110
+ ]}
111
+ getLabel={(datum) => datum.metric}
112
+ getValue={(datum) => datum.value}
113
+ title="Employee Performance Comparison"
114
+ showDataLabels={true}
115
+ theme={webLightTheme}
116
+ />
117
+ ```
118
+
119
+ ### Product Feature Analysis
120
+
121
+ ```tsx
122
+ interface ProductFeature {
123
+ feature: string;
124
+ currentVersion: number;
125
+ competitorA: number;
126
+ competitorB: number;
127
+ targetVersion: number;
128
+ }
129
+
130
+ const featureData: ProductFeature[] = [
131
+ { feature: 'User Experience', currentVersion: 85, competitorA: 90, competitorB: 82, targetVersion: 95 },
132
+ { feature: 'Performance', currentVersion: 88, competitorA: 85, competitorB: 92, targetVersion: 95 },
133
+ { feature: 'Security', currentVersion: 92, competitorA: 88, competitorB: 90, targetVersion: 98 },
134
+ { feature: 'Scalability', currentVersion: 78, competitorA: 85, competitorB: 80, targetVersion: 90 },
135
+ { feature: 'Integration', currentVersion: 82, competitorA: 88, competitorB: 85, targetVersion: 92 },
136
+ { feature: 'Support', currentVersion: 90, competitorA: 85, competitorB: 88, targetVersion: 95 },
137
+ ];
138
+
139
+ <RadarChart
140
+ data={[
141
+ {
142
+ label: 'Our Product',
143
+ data: featureData.map(d => ({ feature: d.feature, value: d.currentVersion }))
144
+ },
145
+ {
146
+ label: 'Competitor A',
147
+ data: featureData.map(d => ({ feature: d.feature, value: d.competitorA }))
148
+ },
149
+ {
150
+ label: 'Target Goals',
151
+ data: featureData.map(d => ({ feature: d.feature, value: d.targetVersion }))
152
+ }
153
+ ]}
154
+ getLabel={(datum) => datum.feature}
155
+ getValue={(datum) => datum.value}
156
+ title="Product Feature Competitive Analysis"
157
+ theme={webLightTheme}
158
+ />
159
+ ```
160
+
161
+ ### Financial Health Assessment
162
+
163
+ ```tsx
164
+ interface FinancialMetric {
165
+ category: string;
166
+ q1Score: number;
167
+ q2Score: number;
168
+ q3Score: number;
169
+ q4Score: number;
170
+ }
171
+
172
+ const financialData: FinancialMetric[] = [
173
+ { category: 'Liquidity', q1Score: 85, q2Score: 88, q3Score: 82, q4Score: 90 },
174
+ { category: 'Profitability', q1Score: 78, q2Score: 82, q3Score: 85, q4Score: 88 },
175
+ { category: 'Efficiency', q1Score: 88, q2Score: 85, q3Score: 90, q4Score: 92 },
176
+ { category: 'Leverage', q1Score: 75, q2Score: 78, q3Score: 80, q4Score: 82 },
177
+ { category: 'Growth', q1Score: 82, q2Score: 85, q3Score: 88, q4Score: 90 },
178
+ ];
179
+
180
+ <RadarChart
181
+ data={[
182
+ {
183
+ label: 'Q1 2024',
184
+ data: financialData.map(d => ({ category: d.category, value: d.q1Score }))
185
+ },
186
+ {
187
+ label: 'Q4 2024',
188
+ data: financialData.map(d => ({ category: d.category, value: d.q4Score }))
189
+ }
190
+ ]}
191
+ getLabel={(datum) => datum.category}
192
+ getValue={(datum) => datum.value}
193
+ title="Financial Health: Q1 vs Q4 Comparison"
194
+ showDataLabels={true}
195
+ theme={webLightTheme}
196
+ />
197
+ ```
198
+
199
+ ### Technology Stack Evaluation
200
+
201
+ ```tsx
202
+ interface TechnologyAssessment {
203
+ criterion: string;
204
+ react: number;
205
+ vue: number;
206
+ angular: number;
207
+ }
208
+
209
+ const techData: TechnologyAssessment[] = [
210
+ { criterion: 'Learning Curve', react: 85, vue: 90, angular: 70 },
211
+ { criterion: 'Performance', react: 88, vue: 85, angular: 82 },
212
+ { criterion: 'Community Support', react: 95, vue: 85, angular: 88 },
213
+ { criterion: 'Ecosystem', react: 92, vue: 82, angular: 90 },
214
+ { criterion: 'Developer Experience', react: 90, vue: 88, angular: 78 },
215
+ { criterion: 'Enterprise Readiness', react: 85, vue: 80, angular: 92 },
216
+ ];
217
+
218
+ <RadarChart
219
+ data={[
220
+ {
221
+ label: 'React',
222
+ data: techData.map(d => ({ criterion: d.criterion, value: d.react }))
223
+ },
224
+ {
225
+ label: 'Vue.js',
226
+ data: techData.map(d => ({ criterion: d.criterion, value: d.vue }))
227
+ },
228
+ {
229
+ label: 'Angular',
230
+ data: techData.map(d => ({ criterion: d.criterion, value: d.angular }))
231
+ }
232
+ ]}
233
+ getLabel={(datum) => datum.criterion}
234
+ getValue={(datum) => datum.value}
235
+ title="Frontend Framework Comparison"
236
+ theme={webLightTheme}
237
+ />
238
+ ```
239
+
240
+ ## Data Structure
241
+
242
+ The component expects data in the following format:
243
+
244
+ ```tsx
245
+ interface ChartSeries<T> {
246
+ label: string; // Series name (appears in legend)
247
+ data: T[]; // Array of data points
248
+ }
249
+ ```
250
+
251
+ Each data point `T` should contain:
252
+
253
+ - A label value (axis name) - string
254
+ - A numeric value (distance from center) - number
255
+
256
+ ## Radar Chart Characteristics
257
+
258
+ ### Axes Configuration
259
+
260
+ - **Radial Axes**: Each metric becomes a spoke radiating from center
261
+ - **Equal Spacing**: Axes are evenly distributed around the circle
262
+ - **Scale**: All axes share the same scale (0 to maximum value)
263
+
264
+ ### Visual Elements
265
+
266
+ - **Points**: Data values plotted on each axis
267
+ - **Lines**: Connect points to form polygonal shapes
268
+ - **Fill Areas**: Semi-transparent areas enclosed by the polygon
269
+ - **Grid**: Concentric circles and radial lines for reference
270
+
271
+ ## Use Cases
272
+
273
+ Radar charts are particularly effective for:
274
+
275
+ ### Performance Analysis
276
+
277
+ - **Employee Evaluation**: Multi-dimensional skill assessment
278
+ - **Product Comparison**: Feature-by-feature analysis
279
+ - **Team Performance**: Various performance metrics
280
+
281
+ ### Competitive Analysis
282
+
283
+ - **Market Positioning**: Compare products across multiple criteria
284
+ - **SWOT Analysis**: Strengths, weaknesses, opportunities, threats
285
+ - **Vendor Evaluation**: Multi-criteria decision analysis
286
+
287
+ ### Quality Assessment
288
+
289
+ - **Customer Satisfaction**: Various satisfaction dimensions
290
+ - **System Performance**: Multiple performance metrics
291
+ - **Risk Assessment**: Different risk factors
292
+
293
+ ### Portfolio Analysis
294
+
295
+ - **Investment Options**: Risk vs return across multiple factors
296
+ - **Project Evaluation**: Multiple project criteria
297
+ - **Technology Selection**: Various technical and business criteria
298
+
299
+ ## Interactive Features
300
+
301
+ ### Legend Controls
302
+
303
+ - Click legend items to show/hide data series
304
+ - Visual feedback on hover states
305
+ - Color-coded entries matching chart areas
306
+ - Series can be toggled independently
307
+
308
+ ### Point Interactions
309
+
310
+ - Hover effects on data points
311
+ - Rich tooltips showing metric details
312
+ - Smooth transitions and animations
313
+
314
+ ### Radial Grid
315
+
316
+ - Concentric circles showing value scales
317
+ - Radial lines for each metric axis
318
+ - Theme-aware styling and colors
319
+
320
+ ## Styling and Theme Integration
321
+
322
+ The component uses Fluent UI theme tokens:
323
+
324
+ ```tsx
325
+ // Area styling
326
+ backgroundColor: seriesColor + '33' (20% opacity)
327
+ borderColor: seriesColor
328
+ borderWidth: 2
329
+ pointBackgroundColor: seriesColor
330
+
331
+ // Grid styling
332
+ angleLines: { color: theme.colorNeutralStroke2 }
333
+ grid: { color: theme.colorNeutralStroke2 }
334
+
335
+ // Typography
336
+ fontFamily: theme.fontFamilyBase
337
+ fontSize: theme.fontSizeBase200
338
+ color: theme.colorNeutralForeground1
339
+
340
+ // Point labels (axis names)
341
+ color: theme.colorNeutralForeground1
342
+ font: { family: fontFamily, size: fontSize }
343
+ ```
344
+
345
+ ## Performance Optimizations
346
+
347
+ The component includes several React optimizations:
348
+
349
+ ````tsx
350
+ // Memoized color calculations
351
+ const seriesColors = useMemo(() => {
352
+ return data.reduce((acc, series, idx) => {
353
+ const base = getFluentPalette(theme)[
354
+ idx % getFluentPalette(theme).length
355
+ ];
356
+ acc[series.label] = lightenColor(base, 0.3);
357
+ return acc;
358
+ }, {} as Record<string, string>);
359
+ }, [data, getFluentPalette, lightenColor, theme]);
360
+
361
+ // Memoized axis labels
362
+ const allLabels = useMemo(() => {
363
+ const set = new Set<string>();
364
+ data.forEach(series => {
365
+ series.data.forEach(d => set.add(getLabel(d)));
366
+ });
367
+ return Array.from(set);
368
+ }, [data, getLabel]);
369
+
370
+ // Memoized chart data transformation
371
+ const chartData = useMemo(() => {
372
+ return {
373
+ labels: allLabels,
374
+ datasets: data
375
+ .filter(series => visibleSeries.includes(series.label))
376
+ .map(series => ({
377
+ label: series.label,
378
+ data: allLabels.map(label => {
379
+ const match = series.data.find(d => getLabel(d) === label);
380
+ return match ? getValue(match) : 0;
381
+ }),
382
+ backgroundColor: seriesColors[series.label] + '33',
383
+ borderColor: seriesColors[series.label],
384
+ borderWidth: 2,
385
+ pointBackgroundColor: seriesColors[series.label],
386
+ })),
387
+ };
388
+ }, [data, visibleSeries, allLabels, getLabel, getValue, seriesColors]);
@@ -0,0 +1,173 @@
1
+ import {
2
+ Chart as ChartJS,
3
+ ChartOptions,
4
+ Filler,
5
+ Legend,
6
+ LineElement,
7
+ PointElement,
8
+ RadialLinearScale,
9
+ Title,
10
+ Tooltip,
11
+ } from 'chart.js';
12
+ import React, { useMemo, useState } from 'react';
13
+ import { Theme, webLightTheme } from '@fluentui/react-components';
14
+ import { createFluentTooltip, useGraphUtils } from '../../hooks/useGraphUtils';
15
+
16
+ import ChartDataLabels from 'chartjs-plugin-datalabels';
17
+ import { Radar } from 'react-chartjs-2';
18
+ import RenderLegend from '../../components/RenderLegend/RenderLegend';
19
+ import { useGraphGlobalStyles } from '../../graphGlobalStyles/useGraphGlobalStyles';
20
+
21
+ ChartJS.register(ChartDataLabels);
22
+ ChartJS.register(
23
+ RadialLinearScale,
24
+ PointElement,
25
+ LineElement,
26
+ Filler,
27
+ Tooltip,
28
+ Legend,
29
+ Title
30
+ );
31
+
32
+ export interface RadarChartProps<T> {
33
+ data: { label: string; data: T[] }[];
34
+ getLabel: (datum: T) => string;
35
+ getValue: (datum: T) => number;
36
+ title?: string;
37
+ showDataLabels?: boolean;
38
+ theme?: Theme;
39
+ }
40
+
41
+ export default function RadarChart<T extends object>({
42
+ data,
43
+ getLabel,
44
+ getValue,
45
+ title,
46
+ showDataLabels = false,
47
+ theme = webLightTheme,
48
+ }: RadarChartProps<T>) {
49
+ const [visibleSeries, setVisibleSeries] = useState(() =>
50
+ data.map(s => s.label)
51
+ );
52
+
53
+ const styles = useGraphGlobalStyles();
54
+ const { lightenColor, getFluentPalette } = useGraphUtils(theme);
55
+
56
+ const seriesColors = useMemo(() => {
57
+ return data.reduce((acc, series, idx) => {
58
+ const base = getFluentPalette(theme)[
59
+ idx % getFluentPalette(theme).length
60
+ ];
61
+ acc[series.label] = lightenColor(base, 0.3);
62
+ return acc;
63
+ }, {} as Record<string, string>);
64
+ }, [data, getFluentPalette, lightenColor, theme]);
65
+
66
+ const toggleSeries = (label: string) => {
67
+ setVisibleSeries(prev =>
68
+ prev.includes(label) ? prev.filter(l => l !== label) : [...prev, label]
69
+ );
70
+ };
71
+
72
+ const allLabels = useMemo(() => {
73
+ const set = new Set<string>();
74
+ data.forEach(series => {
75
+ series.data.forEach(d => set.add(getLabel(d)));
76
+ });
77
+ return Array.from(set);
78
+ }, [data, getLabel]);
79
+
80
+ const chartData = useMemo(() => {
81
+ return {
82
+ labels: allLabels,
83
+ datasets: data
84
+ .filter(series => visibleSeries.includes(series.label))
85
+ .map(series => ({
86
+ label: series.label,
87
+ data: allLabels.map(label => {
88
+ const match = series.data.find(d => getLabel(d) === label);
89
+ return match ? getValue(match) : 0;
90
+ }),
91
+ backgroundColor: seriesColors[series.label] + '33',
92
+ borderColor: seriesColors[series.label],
93
+ borderWidth: 2,
94
+ pointBackgroundColor: seriesColors[series.label],
95
+ })),
96
+ };
97
+ }, [data, visibleSeries, allLabels, getLabel, getValue, seriesColors]);
98
+
99
+ const { fontFamily, fontSize, labelColor, gridColor } = useMemo(() => ({
100
+ fontFamily: theme.fontFamilyBase,
101
+ fontSize: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
102
+ labelColor: theme.colorNeutralForeground1,
103
+ gridColor: theme.colorNeutralStroke2,
104
+ }), [theme]);
105
+
106
+ const options = useMemo<ChartOptions<'radar'>>(() => ({
107
+ responsive: true,
108
+ maintainAspectRatio: false,
109
+ plugins: {
110
+ title: {
111
+ display: !!title,
112
+ text: title,
113
+ font: {
114
+ size: 14,
115
+ family: theme.fontFamilyBase,
116
+ weight: theme.fontWeightSemibold,
117
+ },
118
+ color: theme.colorNeutralForeground1,
119
+ padding: {
120
+ top: 20,
121
+ bottom: 20,
122
+ },
123
+ },
124
+ datalabels: {
125
+ display: showDataLabels,
126
+ color: theme.colorNeutralForeground1,
127
+ font: {
128
+ family: theme.fontFamilyBase,
129
+ size: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
130
+ },
131
+ },
132
+ tooltip: createFluentTooltip<'radar'>(theme),
133
+ legend: { display: false },
134
+ },
135
+ scales: {
136
+ r: {
137
+ angleLines: { color: gridColor },
138
+ grid: { color: gridColor },
139
+ pointLabels: {
140
+ color: labelColor,
141
+ font: { family: fontFamily, size: fontSize },
142
+ },
143
+ ticks: {
144
+ color: labelColor,
145
+ font: { family: fontFamily, size: fontSize },
146
+ },
147
+ },
148
+ },
149
+ }), [
150
+ theme,
151
+ title,
152
+ showDataLabels,
153
+ createFluentTooltip,
154
+ gridColor,
155
+ labelColor,
156
+ fontFamily,
157
+ fontSize,
158
+ ]);
159
+
160
+ return (
161
+ <div className={styles.chartWithLegend}>
162
+ <div className={styles.chartArea}>
163
+ <Radar data={chartData} options={options} />
164
+ </div>
165
+ <RenderLegend
166
+ data={data}
167
+ visibleSeries={visibleSeries}
168
+ seriesColors={seriesColors}
169
+ toggleSeries={toggleSeries}
170
+ />
171
+ </div>
172
+ );
173
+ }
@@ -0,0 +1 @@
1
+ export * from './RadarChart';