@spteck/fluentui-react-charts 1.0.7 → 1.0.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.
- package/dist/charts/BarChart/BarChart.d.ts +2 -1
- package/dist/charts/ComboChart/ComboChart.d.ts +2 -1
- package/dist/charts/Doughnut/DoughnutChart.d.ts +2 -1
- package/dist/charts/PieChart/PieChart.d.ts +2 -1
- package/dist/charts/areaChart/AreaChart.d.ts +2 -1
- package/dist/charts/barHorizontalChart/BarHotizontalChart.d.ts +2 -1
- package/dist/charts/bubbleChart/BubbleChart.d.ts +2 -1
- package/dist/charts/floatBarChart/FloatBarChart.d.ts +2 -1
- package/dist/charts/index.d.ts +14 -0
- package/dist/charts/lineChart/LineChart.d.ts +2 -1
- package/dist/charts/polarChart/PolarChart.d.ts +2 -1
- package/dist/charts/radarChart/RadarChart.d.ts +2 -1
- package/dist/charts/scatterChart/ScatterChart.d.ts +2 -1
- package/dist/charts/stackedLineChart/StackedLineChart.d.ts +2 -1
- package/dist/charts/steamChart/SteamChart.d.ts +2 -1
- package/dist/components/index.d.ts +0 -14
- package/dist/fluentui-react-charts.cjs.development.js +1086 -1072
- package/dist/fluentui-react-charts.cjs.development.js.map +1 -1
- package/dist/fluentui-react-charts.cjs.production.min.js +1 -1
- package/dist/fluentui-react-charts.cjs.production.min.js.map +1 -1
- package/dist/fluentui-react-charts.esm.js +1074 -1074
- package/dist/fluentui-react-charts.esm.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/package.json +3 -3
- package/src/assets/sample1.png +0 -0
- package/src/assets/sample2.png +0 -0
- package/src/assets/sample3.png +0 -0
- package/src/charts/BarChart/BarChart.tsx +0 -227
- package/src/charts/BarChart/README.MD +0 -335
- package/src/charts/BarChart/index.ts +0 -1
- package/src/charts/ComboChart/ComboChart.tsx +0 -209
- package/src/charts/ComboChart/README.MD +0 -347
- package/src/charts/ComboChart/index.ts +0 -1
- package/src/charts/Doughnut/DoughnutChart.tsx +0 -152
- package/src/charts/Doughnut/README.MD +0 -296
- package/src/charts/Doughnut/index.ts +0 -1
- package/src/charts/PieChart/PieChart.tsx +0 -148
- package/src/charts/PieChart/README.MD +0 -315
- package/src/charts/PieChart/index.ts +0 -1
- package/src/charts/areaChart/AreaChart.tsx +0 -195
- package/src/charts/areaChart/README.MD +0 -236
- package/src/charts/areaChart/index.ts +0 -1
- package/src/charts/barHorizontalChart/BarHotizontalChart.tsx +0 -200
- package/src/charts/barHorizontalChart/README.MD +0 -278
- package/src/charts/barHorizontalChart/index.ts +0 -2
- package/src/charts/bubbleChart/BubbleChart.tsx +0 -184
- package/src/charts/bubbleChart/README.MD +0 -275
- package/src/charts/bubbleChart/index.ts +0 -1
- package/src/charts/floatBarChart/FloatBarChart.tsx +0 -178
- package/src/charts/floatBarChart/README.MD +0 -354
- package/src/charts/floatBarChart/index.ts +0 -1
- package/src/charts/lineChart/LineChart.tsx +0 -200
- package/src/charts/lineChart/README.MD +0 -354
- package/src/charts/lineChart/index.ts +0 -1
- package/src/charts/polarChart/PolarChart.tsx +0 -161
- package/src/charts/polarChart/README.MD +0 -336
- package/src/charts/polarChart/index.ts +0 -1
- package/src/charts/radarChart/README.MD +0 -388
- package/src/charts/radarChart/RadarChart.tsx +0 -173
- package/src/charts/radarChart/index.ts +0 -1
- package/src/charts/scatterChart/README.MD +0 -335
- package/src/charts/scatterChart/ScatterChart.tsx +0 -155
- package/src/charts/scatterChart/index.ts +0 -1
- package/src/charts/stackedLineChart/README.MD +0 -396
- package/src/charts/stackedLineChart/StackedLineChart.tsx +0 -188
- package/src/charts/stackedLineChart/index.ts +0 -1
- package/src/charts/steamChart/README.MD +0 -414
- package/src/charts/steamChart/SteamChart.tsx +0 -236
- package/src/charts/steamChart/index.ts +0 -1
- package/src/components/RenderLabel/RenderLabel.tsx +0 -39
- package/src/components/RenderLabel/index.ts +0 -2
- package/src/components/RenderLabel/useRenderLabelStylesStyles.ts +0 -25
- package/src/components/RenderLegend/RenderLegend.tsx +0 -40
- package/src/components/RenderTooltip/RenderTooltip.tsx +0 -111
- package/src/components/buttonMenu/ButtonMenu.tsx +0 -186
- package/src/components/buttonMenu/IButtonMenuOption.ts +0 -9
- package/src/components/buttonMenu/IButtonMenuProps.tsx +0 -40
- package/src/components/dashboard/DashBoard.tsx +0 -314
- package/src/components/dashboard/ExampleDashboardUsage.tsx +0 -114
- package/src/components/dashboard/IDashboardProps.tsx +0 -11
- package/src/components/dashboard/NoDashboards.tsx +0 -26
- package/src/components/dashboard/index.ts +0 -3
- package/src/components/dashboard/selectZoom/SelectZoom.tsx +0 -184
- package/src/components/dashboard/useDashboardStyles.ts +0 -76
- package/src/components/index.ts +0 -17
- package/src/components/legendContainer/LegendContainer.tsx +0 -118
- package/src/components/legendeButton/LegendButton.tsx +0 -57
- package/src/components/renderSliceLegend/RenderSliceLegend.tsx +0 -46
- package/src/components/renderValueLegend/RenderValueLegend.tsx +0 -43
- package/src/components/stack/IStackProps.tsx +0 -94
- package/src/components/stack/Stack.tsx +0 -103
- package/src/components/svgImages/BusinessReportIcon.tsx +0 -218
- package/src/components/themeProvider/ThemeProvider.tsx +0 -48
- package/src/constants/Constants.tsx +0 -23
- package/src/graphGlobalStyles/useGraphGlobalStyles.ts +0 -28
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useChartFactory.tsx +0 -136
- package/src/hooks/useChartUtils.tsx +0 -187
- package/src/hooks/useIndexedDBCache.ts +0 -119
- package/src/hooks/useResponsiveLegend.ts +0 -35
- package/src/index.tsx +0 -5
- package/src/models/ChartDatum.ts +0 -4
- package/src/models/ICardChartContainer.tsx +0 -11
- package/src/models/IChart.ts +0 -50
- package/src/models/index.ts +0 -3
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
# PieChart Component
|
|
2
|
-
|
|
3
|
-
A customizable pie chart component built with Chart.js and Fluent UI React. This component displays data as circular segments, making it perfect for showing proportions, percentages, and part-to-whole relationships with clear visual representation of each category's contribution to the total.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Circular Data Visualization**: Display proportional data in an intuitive circular format
|
|
8
|
-
- **Value Aggregation**: Automatically aggregates values across multiple series for the same labels
|
|
9
|
-
- **Interactive Value Legend**: Toggle segments visibility with click interactions and display actual values
|
|
10
|
-
- **Fluent UI Integration**: Seamless integration with Fluent UI themes and design system
|
|
11
|
-
- **Data Labels**: Optional display of values directly on chart segments
|
|
12
|
-
- **Responsive Design**: Automatically adapts to container dimensions
|
|
13
|
-
- **TypeScript Support**: Full TypeScript support with generic types
|
|
14
|
-
- **Custom Tooltips**: Rich tooltips showing detailed segment information
|
|
15
|
-
- **Full Circle**: Classic pie chart design showing complete data distribution
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm install chart.js react-chartjs-2 chartjs-plugin-datalabels @fluentui/react-components
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Basic Usage
|
|
24
|
-
|
|
25
|
-
```tsx
|
|
26
|
-
import React from 'react';
|
|
27
|
-
import { PieChart } from './components/PieChart/PieChart';
|
|
28
|
-
import { webLightTheme } from '@fluentui/react-components';
|
|
29
|
-
|
|
30
|
-
interface SalesData {
|
|
31
|
-
category: string;
|
|
32
|
-
amount: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const salesData: SalesData[] = [
|
|
36
|
-
{ category: 'Electronics', amount: 125000 },
|
|
37
|
-
{ category: 'Clothing', amount: 85000 },
|
|
38
|
-
{ category: 'Books', amount: 45000 },
|
|
39
|
-
{ category: 'Home & Garden', amount: 95000 },
|
|
40
|
-
{ category: 'Sports', amount: 65000 },
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
function App() {
|
|
44
|
-
return (
|
|
45
|
-
<div style={{ width: '600px', height: '400px' }}>
|
|
46
|
-
<PieChart
|
|
47
|
-
data={[
|
|
48
|
-
{ label: 'Sales by Category', data: salesData }
|
|
49
|
-
]}
|
|
50
|
-
getLabel={(datum) => datum.category}
|
|
51
|
-
getValue={(datum) => datum.amount}
|
|
52
|
-
title="Sales Distribution by Category"
|
|
53
|
-
theme={webLightTheme}
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Props
|
|
61
|
-
|
|
62
|
-
### PieChartProps<T>
|
|
63
|
-
|
|
64
|
-
| Prop | Type | Required | Default | Description |
|
|
65
|
-
|------|------|----------|---------|-------------|
|
|
66
|
-
| `data` | `{ label: string; data: T[] }[]` | Yes | - | Array of data series with labels and data points |
|
|
67
|
-
| `getLabel` | `(datum: T) => string` | Yes | - | Function to extract the segment label from each data point |
|
|
68
|
-
| `getValue` | `(datum: T) => number` | Yes | - | Function to extract the segment value from each data point |
|
|
69
|
-
| `title` | `string` | No | - | Chart title displayed at the top |
|
|
70
|
-
| `showDataLabels` | `boolean` | No | `false` | Whether to show data values on chart segments |
|
|
71
|
-
| `theme` | `Theme` | No | `webLightTheme` | Fluent UI theme object for styling |
|
|
72
|
-
|
|
73
|
-
## Advanced Usage
|
|
74
|
-
|
|
75
|
-
### Revenue Breakdown
|
|
76
|
-
|
|
77
|
-
```tsx
|
|
78
|
-
interface RevenueData {
|
|
79
|
-
source: string;
|
|
80
|
-
amount: number;
|
|
81
|
-
percentage: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const revenueData: RevenueData[] = [
|
|
85
|
-
{ source: 'Direct Sales', amount: 450000, percentage: 45 },
|
|
86
|
-
{ source: 'Online Store', amount: 280000, percentage: 28 },
|
|
87
|
-
{ source: 'Retail Partners', amount: 180000, percentage: 18 },
|
|
88
|
-
{ source: 'Subscription', amount: 90000, percentage: 9 },
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
<PieChart
|
|
92
|
-
data={[{ label: 'Revenue Sources', data: revenueData }]}
|
|
93
|
-
getLabel={(datum) => datum.source}
|
|
94
|
-
getValue={(datum) => datum.amount}
|
|
95
|
-
title="Revenue Breakdown by Source"
|
|
96
|
-
showDataLabels={true}
|
|
97
|
-
theme={webLightTheme}
|
|
98
|
-
/>
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Market Share Analysis
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
interface MarketData {
|
|
105
|
-
company: string;
|
|
106
|
-
marketShare: number;
|
|
107
|
-
revenue: number;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const marketData: MarketData[] = [
|
|
111
|
-
{ company: 'Our Company', marketShare: 28.5, revenue: 2850000 },
|
|
112
|
-
{ company: 'Competitor A', marketShare: 22.1, revenue: 2210000 },
|
|
113
|
-
{ company: 'Competitor B', marketShare: 18.7, revenue: 1870000 },
|
|
114
|
-
{ company: 'Competitor C', marketShare: 15.3, revenue: 1530000 },
|
|
115
|
-
{ company: 'Others', marketShare: 15.4, revenue: 1540000 },
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
<PieChart
|
|
119
|
-
data={[{ label: 'Market Share', data: marketData }]}
|
|
120
|
-
getLabel={(datum) => datum.company}
|
|
121
|
-
getValue={(datum) => datum.marketShare}
|
|
122
|
-
title="Industry Market Share Distribution"
|
|
123
|
-
showDataLabels={true}
|
|
124
|
-
theme={webLightTheme}
|
|
125
|
-
/>
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Budget Allocation
|
|
129
|
-
|
|
130
|
-
```tsx
|
|
131
|
-
interface BudgetCategory {
|
|
132
|
-
department: string;
|
|
133
|
-
allocation: number;
|
|
134
|
-
spent: number;
|
|
135
|
-
priority: 'high' | 'medium' | 'low';
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const budgetData: BudgetCategory[] = [
|
|
139
|
-
{ department: 'Engineering', allocation: 450000, spent: 425000, priority: 'high' },
|
|
140
|
-
{ department: 'Marketing', allocation: 280000, spent: 265000, priority: 'high' },
|
|
141
|
-
{ department: 'Sales', allocation: 320000, spent: 310000, priority: 'high' },
|
|
142
|
-
{ department: 'Operations', allocation: 180000, spent: 175000, priority: 'medium' },
|
|
143
|
-
{ department: 'HR', allocation: 120000, spent: 115000, priority: 'medium' },
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
<PieChart
|
|
147
|
-
data={[{ label: 'Budget Allocation', data: budgetData }]}
|
|
148
|
-
getLabel={(datum) => datum.department}
|
|
149
|
-
getValue={(datum) => datum.allocation}
|
|
150
|
-
title="Annual Budget Distribution"
|
|
151
|
-
showDataLabels={true}
|
|
152
|
-
theme={webLightTheme}
|
|
153
|
-
/>
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Multiple Series Aggregation
|
|
157
|
-
|
|
158
|
-
```tsx
|
|
159
|
-
interface RegionalSales {
|
|
160
|
-
product: string;
|
|
161
|
-
region: string;
|
|
162
|
-
sales: number;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const salesData: RegionalSales[] = [
|
|
166
|
-
{ product: 'Product A', region: 'North', sales: 15000 },
|
|
167
|
-
{ product: 'Product A', region: 'South', sales: 12000 },
|
|
168
|
-
{ product: 'Product B', region: 'North', sales: 18000 },
|
|
169
|
-
{ product: 'Product B', region: 'South', sales: 14000 },
|
|
170
|
-
{ product: 'Product C', region: 'North', sales: 10000 },
|
|
171
|
-
{ product: 'Product C', region: 'South', sales: 8000 },
|
|
172
|
-
];
|
|
173
|
-
|
|
174
|
-
// This will automatically aggregate sales by product across all regions
|
|
175
|
-
<PieChart
|
|
176
|
-
data={[
|
|
177
|
-
{ label: 'North Region', data: salesData.filter(d => d.region === 'North') },
|
|
178
|
-
{ label: 'South Region', data: salesData.filter(d => d.region === 'South') }
|
|
179
|
-
]}
|
|
180
|
-
getLabel={(datum) => datum.product}
|
|
181
|
-
getValue={(datum) => datum.sales}
|
|
182
|
-
title="Total Product Sales (All Regions Combined)"
|
|
183
|
-
theme={webLightTheme}
|
|
184
|
-
/>
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## Data Structure
|
|
188
|
-
|
|
189
|
-
The component expects data in the following format:
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
interface ChartSeries<T> {
|
|
193
|
-
label: string; // Series name (for multiple data sources)
|
|
194
|
-
data: T[]; // Array of data points
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Each data point `T` should contain:
|
|
199
|
-
|
|
200
|
-
- A label value (segment name) - string
|
|
201
|
-
- A numeric value (segment size) - number
|
|
202
|
-
|
|
203
|
-
## Data Aggregation
|
|
204
|
-
|
|
205
|
-
The component automatically aggregates values when multiple data points have the same label:
|
|
206
|
-
|
|
207
|
-
```tsx
|
|
208
|
-
// Input data
|
|
209
|
-
const data = [
|
|
210
|
-
{ category: 'Electronics', value: 100 },
|
|
211
|
-
{ category: 'Electronics', value: 50 }, // Will be aggregated
|
|
212
|
-
{ category: 'Clothing', value: 75 },
|
|
213
|
-
];
|
|
214
|
-
|
|
215
|
-
// Result: Electronics = 150, Clothing = 75
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## Interactive Features
|
|
219
|
-
|
|
220
|
-
### Value Legend
|
|
221
|
-
|
|
222
|
-
- Displays label, actual value, and color for each segment
|
|
223
|
-
- Click legend items to show/hide segments
|
|
224
|
-
- Visual feedback on hover states
|
|
225
|
-
- Maintains proportional relationships when segments are hidden
|
|
226
|
-
|
|
227
|
-
### Segment Interactions
|
|
228
|
-
|
|
229
|
-
- Hover effects on pie segments
|
|
230
|
-
- Rich tooltips showing detailed information
|
|
231
|
-
- Smooth animations and transitions
|
|
232
|
-
|
|
233
|
-
### Responsive Layout
|
|
234
|
-
|
|
235
|
-
- Chart automatically resizes to container dimensions
|
|
236
|
-
- Legend positioning adapts to available space
|
|
237
|
-
- Maintains readability across different screen sizes
|
|
238
|
-
|
|
239
|
-
## Styling and Theme Integration
|
|
240
|
-
|
|
241
|
-
The component uses Fluent UI theme tokens:
|
|
242
|
-
|
|
243
|
-
```tsx
|
|
244
|
-
// Segment colors
|
|
245
|
-
backgroundColor: Derived from theme palette with lightening
|
|
246
|
-
borderWidth: 1
|
|
247
|
-
|
|
248
|
-
// Typography
|
|
249
|
-
fontFamily: theme.fontFamilyBase
|
|
250
|
-
fontSize: theme.fontSizeBase200
|
|
251
|
-
fontWeight: theme.fontWeightSemibold
|
|
252
|
-
color: theme.colorNeutralForeground1
|
|
253
|
-
|
|
254
|
-
// Data labels
|
|
255
|
-
color: theme.colorNeutralForeground1
|
|
256
|
-
font: theme font properties
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
## Performance Optimizations
|
|
260
|
-
|
|
261
|
-
The component includes several React optimizations:
|
|
262
|
-
|
|
263
|
-
````tsx
|
|
264
|
-
// Memoized value aggregation
|
|
265
|
-
const valueMap = useMemo(() => {
|
|
266
|
-
const map = new Map<string, number>();
|
|
267
|
-
data.forEach(series => {
|
|
268
|
-
series.data.forEach(d => {
|
|
269
|
-
const label = getLabel(d);
|
|
270
|
-
const value = getValue(d);
|
|
271
|
-
map.set(label, (map.get(label) || 0) + value);
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
return map;
|
|
275
|
-
}, [data, getLabel, getValue]);
|
|
276
|
-
|
|
277
|
-
// Memoized color and data calculations
|
|
278
|
-
const { allLabels, colors, filteredLabels, values, visibleColors } = useMemo(() => {
|
|
279
|
-
const allLabels = Array.from(valueMap.keys());
|
|
280
|
-
const palette = getFluentPalette(theme);
|
|
281
|
-
const colors = allLabels.map((_, i) =>
|
|
282
|
-
lightenColor(palette[i % palette.length], 0.3)
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
const filteredLabels = allLabels.filter(
|
|
286
|
-
label => !hiddenLabels.includes(label)
|
|
287
|
-
);
|
|
288
|
-
const values = filteredLabels.map(label => valueMap.get(label) || 0);
|
|
289
|
-
const visibleColors = filteredLabels.map(label => {
|
|
290
|
-
const idx = allLabels.indexOf(label);
|
|
291
|
-
return colors[idx];
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
return { allLabels, colors, filteredLabels, values, visibleColors };
|
|
295
|
-
}, [valueMap, getFluentPalette, lightenColor, theme, hiddenLabels]);
|
|
296
|
-
|
|
297
|
-
// Memoized chart data and legend entries
|
|
298
|
-
const { chartData, legendEntries } = useMemo(() => {
|
|
299
|
-
const chartData = {
|
|
300
|
-
labels: filteredLabels,
|
|
301
|
-
datasets: [{
|
|
302
|
-
data: values,
|
|
303
|
-
backgroundColor: visibleColors,
|
|
304
|
-
borderWidth: 1,
|
|
305
|
-
}],
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const legendEntries = allLabels.map((label, i) => ({
|
|
309
|
-
label,
|
|
310
|
-
value: valueMap.get(label) || 0,
|
|
311
|
-
color: colors[i],
|
|
312
|
-
}));
|
|
313
|
-
|
|
314
|
-
return { chartData, legendEntries };
|
|
315
|
-
}, [filteredLabels, values, visibleColors, allLabels, valueMap, colors]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './PieChart';
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CategoryScale,
|
|
3
|
-
Chart as ChartJS,
|
|
4
|
-
ChartOptions,
|
|
5
|
-
Filler,
|
|
6
|
-
Legend,
|
|
7
|
-
LineElement,
|
|
8
|
-
LinearScale,
|
|
9
|
-
PointElement,
|
|
10
|
-
Title,
|
|
11
|
-
Tooltip,
|
|
12
|
-
} from 'chart.js';
|
|
13
|
-
import React, { useMemo, useState } from 'react';
|
|
14
|
-
import { Theme, webLightTheme } from '@fluentui/react-components';
|
|
15
|
-
|
|
16
|
-
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
|
17
|
-
import { Line } from 'react-chartjs-2';
|
|
18
|
-
import RenderLegend from '../../components/RenderLegend/RenderLegend';
|
|
19
|
-
import { useChartUtils } from '../../hooks/useChartUtils';
|
|
20
|
-
import { useGraphGlobalStyles } from '../../graphGlobalStyles/useGraphGlobalStyles';
|
|
21
|
-
|
|
22
|
-
ChartJS.register(
|
|
23
|
-
CategoryScale,
|
|
24
|
-
LinearScale,
|
|
25
|
-
PointElement,
|
|
26
|
-
LineElement,
|
|
27
|
-
Tooltip,
|
|
28
|
-
Filler,
|
|
29
|
-
Legend,
|
|
30
|
-
ChartDataLabels,
|
|
31
|
-
Title
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
export interface AreaChartProps<T> {
|
|
35
|
-
data: { label: string; data: T[] }[];
|
|
36
|
-
getPrimary: (datum: T) => string | number;
|
|
37
|
-
getSecondary: (datum: T) => number;
|
|
38
|
-
stacked?: boolean;
|
|
39
|
-
title?: string;
|
|
40
|
-
showDatalabels?: boolean;
|
|
41
|
-
theme?: Theme;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export default function AreaChart<T extends object>({
|
|
45
|
-
data,
|
|
46
|
-
getPrimary,
|
|
47
|
-
getSecondary,
|
|
48
|
-
title,
|
|
49
|
-
showDatalabels = false,
|
|
50
|
-
theme = webLightTheme,
|
|
51
|
-
stacked = false,
|
|
52
|
-
}: AreaChartProps<T>) {
|
|
53
|
-
const [visibleSeries, setVisibleSeries] = useState(() =>
|
|
54
|
-
data.length > 1 ? data.map(s => s.label) : [data[0]?.label]
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const styles = useGraphGlobalStyles();
|
|
58
|
-
const { lightenColor, getFluentPalette, createFluentTooltip } = useChartUtils(
|
|
59
|
-
theme
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const seriesColors = useMemo(() => {
|
|
63
|
-
return data.reduce((acc, series, idx) => {
|
|
64
|
-
const base = getFluentPalette(theme)[
|
|
65
|
-
idx % getFluentPalette(theme).length
|
|
66
|
-
];
|
|
67
|
-
const color = lightenColor(base, 0.3);
|
|
68
|
-
acc[series.label] = color;
|
|
69
|
-
return acc;
|
|
70
|
-
}, {} as Record<string, string>);
|
|
71
|
-
}, [data, theme]);
|
|
72
|
-
|
|
73
|
-
const toggleSeries = (label: string) => {
|
|
74
|
-
setVisibleSeries(prev => {
|
|
75
|
-
const isVisible = prev.includes(label);
|
|
76
|
-
const next = isVisible ? prev.filter(l => l !== label) : [...prev, label];
|
|
77
|
-
return next.length === 0 && data.length > 0 ? [data[0].label] : next;
|
|
78
|
-
});
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const allCategories = useMemo(() => {
|
|
82
|
-
const set = new Set<string | number>();
|
|
83
|
-
data.forEach(series => {
|
|
84
|
-
series.data.forEach(d => set.add(getPrimary(d)));
|
|
85
|
-
});
|
|
86
|
-
return Array.from(set);
|
|
87
|
-
}, [data, getPrimary]);
|
|
88
|
-
|
|
89
|
-
const chartData = useMemo(() => {
|
|
90
|
-
return {
|
|
91
|
-
labels: allCategories,
|
|
92
|
-
datasets: data
|
|
93
|
-
.filter(series => visibleSeries.includes(series.label))
|
|
94
|
-
.map(series => ({
|
|
95
|
-
label: series.label,
|
|
96
|
-
data: allCategories.map(cat => {
|
|
97
|
-
const match = series.data.find(d => getPrimary(d) === cat);
|
|
98
|
-
return match ? getSecondary(match) : null;
|
|
99
|
-
}),
|
|
100
|
-
borderColor: seriesColors[series.label],
|
|
101
|
-
backgroundColor: seriesColors[series.label],
|
|
102
|
-
fill: true,
|
|
103
|
-
tension: 0.4,
|
|
104
|
-
pointRadius: 2,
|
|
105
|
-
})),
|
|
106
|
-
};
|
|
107
|
-
}, [
|
|
108
|
-
data,
|
|
109
|
-
visibleSeries,
|
|
110
|
-
allCategories,
|
|
111
|
-
getPrimary,
|
|
112
|
-
getSecondary,
|
|
113
|
-
seriesColors,
|
|
114
|
-
]);
|
|
115
|
-
|
|
116
|
-
const { fontFamily, fontSize, labelColor, gridColor } = useMemo(() => ({
|
|
117
|
-
fontFamily: theme.fontFamilyBase,
|
|
118
|
-
fontSize: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
119
|
-
labelColor: theme.colorNeutralForeground1,
|
|
120
|
-
gridColor: theme.colorNeutralStroke2,
|
|
121
|
-
}), [theme]);
|
|
122
|
-
|
|
123
|
-
const options: ChartOptions<'line'> = useMemo(() => ({
|
|
124
|
-
responsive: true,
|
|
125
|
-
maintainAspectRatio: false,
|
|
126
|
-
plugins: {
|
|
127
|
-
title: {
|
|
128
|
-
display: !!title,
|
|
129
|
-
text: title ,
|
|
130
|
-
font: {
|
|
131
|
-
size: 14,
|
|
132
|
-
family: theme.fontFamilyBase,
|
|
133
|
-
weight: theme.fontWeightSemibold,
|
|
134
|
-
},
|
|
135
|
-
color: theme.colorNeutralForeground1,
|
|
136
|
-
padding: {
|
|
137
|
-
top: 20,
|
|
138
|
-
bottom: 20,
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
datalabels: {
|
|
142
|
-
display: showDatalabels,
|
|
143
|
-
color: theme.colorNeutralForeground1,
|
|
144
|
-
font: {
|
|
145
|
-
family: theme.fontFamilyBase,
|
|
146
|
-
size: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
legend: { display: false },
|
|
150
|
-
tooltip: createFluentTooltip<'line'>(theme),
|
|
151
|
-
},
|
|
152
|
-
scales: {
|
|
153
|
-
x: {
|
|
154
|
-
stacked,
|
|
155
|
-
ticks: {
|
|
156
|
-
color: labelColor,
|
|
157
|
-
font: { family: fontFamily, size: fontSize },
|
|
158
|
-
},
|
|
159
|
-
grid: { color: gridColor },
|
|
160
|
-
},
|
|
161
|
-
y: {
|
|
162
|
-
stacked,
|
|
163
|
-
ticks: {
|
|
164
|
-
color: labelColor,
|
|
165
|
-
font: { family: fontFamily, size: fontSize },
|
|
166
|
-
},
|
|
167
|
-
grid: { color: gridColor },
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
}), [
|
|
171
|
-
title,
|
|
172
|
-
showDatalabels,
|
|
173
|
-
theme,
|
|
174
|
-
fontFamily,
|
|
175
|
-
fontSize,
|
|
176
|
-
labelColor,
|
|
177
|
-
gridColor,
|
|
178
|
-
stacked,
|
|
179
|
-
createFluentTooltip,
|
|
180
|
-
]);
|
|
181
|
-
|
|
182
|
-
return (
|
|
183
|
-
<div className={styles.chartWithLegend}>
|
|
184
|
-
<div className={styles.chartArea}>
|
|
185
|
-
<Line data={chartData} options={options} />
|
|
186
|
-
</div>
|
|
187
|
-
<RenderLegend
|
|
188
|
-
data={data}
|
|
189
|
-
visibleSeries={visibleSeries}
|
|
190
|
-
seriesColors={seriesColors}
|
|
191
|
-
toggleSeries={toggleSeries}
|
|
192
|
-
/>
|
|
193
|
-
</div>
|
|
194
|
-
);
|
|
195
|
-
}
|