@spteck/fluentui-react-charts 1.0.7 → 1.0.9
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 +5 -5
- 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,396 +0,0 @@
|
|
|
1
|
-
# StackedLineChart Component
|
|
2
|
-
|
|
3
|
-
A specialized stacked line chart component built with Chart.js and Fluent UI React. This component displays multiple data series as stacked areas with smooth line boundaries, making it perfect for visualizing cumulative data, part-to-whole relationships over time, and how different components contribute to a total value.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Stacked Area Visualization**: Display multiple data series as cumulative stacked areas
|
|
8
|
-
- **Smooth Line Boundaries**: Configurable line tension for smooth area transitions
|
|
9
|
-
- **Interactive Legend**: Toggle series visibility with click interactions
|
|
10
|
-
- **Cumulative Values**: Show how individual series contribute to total values
|
|
11
|
-
- **Filled Areas**: Semi-transparent filled areas for better visual distinction
|
|
12
|
-
- **Fluent UI Integration**: Seamless integration with Fluent UI themes and design system
|
|
13
|
-
- **Data Labels**: Optional display of values directly on data points
|
|
14
|
-
- **Responsive Design**: Automatically adapts to container dimensions
|
|
15
|
-
- **TypeScript Support**: Full TypeScript support with generic types
|
|
16
|
-
- **Custom Tooltips**: Rich tooltips showing detailed breakdown information
|
|
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 { StackedLineChart } from './components/stackedLineChart/StackedLineChart';
|
|
29
|
-
import { webLightTheme } from '@fluentui/react-components';
|
|
30
|
-
|
|
31
|
-
interface RevenueData {
|
|
32
|
-
month: string;
|
|
33
|
-
subscriptions: number;
|
|
34
|
-
oneTime: number;
|
|
35
|
-
consulting: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const revenueData: RevenueData[] = [
|
|
39
|
-
{ month: 'Jan', subscriptions: 45000, oneTime: 12000, consulting: 8000 },
|
|
40
|
-
{ month: 'Feb', subscriptions: 48000, oneTime: 15000, consulting: 10000 },
|
|
41
|
-
{ month: 'Mar', subscriptions: 52000, oneTime: 18000, consulting: 12000 },
|
|
42
|
-
{ month: 'Apr', subscriptions: 55000, oneTime: 14000, consulting: 15000 },
|
|
43
|
-
{ month: 'May', subscriptions: 58000, oneTime: 16000, consulting: 18000 },
|
|
44
|
-
{ month: 'Jun', subscriptions: 62000, oneTime: 20000, consulting: 20000 },
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
function App() {
|
|
48
|
-
return (
|
|
49
|
-
<div style={{ width: '800px', height: '500px' }}>
|
|
50
|
-
<StackedLineChart
|
|
51
|
-
data={[
|
|
52
|
-
{
|
|
53
|
-
label: 'Subscriptions',
|
|
54
|
-
data: revenueData.map(d => ({ month: d.month, value: d.subscriptions }))
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
label: 'One-time Sales',
|
|
58
|
-
data: revenueData.map(d => ({ month: d.month, value: d.oneTime }))
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
label: 'Consulting',
|
|
62
|
-
data: revenueData.map(d => ({ month: d.month, value: d.consulting }))
|
|
63
|
-
}
|
|
64
|
-
]}
|
|
65
|
-
getPrimary={(datum) => datum.month}
|
|
66
|
-
getSecondary={(datum) => datum.value}
|
|
67
|
-
title="Revenue Breakdown Over Time"
|
|
68
|
-
theme={webLightTheme}
|
|
69
|
-
/>
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Props
|
|
76
|
-
|
|
77
|
-
### StackedLineChartProps<T>
|
|
78
|
-
|
|
79
|
-
| Prop | Type | Required | Default | Description |
|
|
80
|
-
|------|------|----------|---------|-------------|
|
|
81
|
-
| `data` | `{ label: string; data: T[] }[]` | Yes | - | Array of data series with labels and data points |
|
|
82
|
-
| `getPrimary` | `(datum: T) => string \| number` | Yes | - | Function to extract the x-axis category from each data point |
|
|
83
|
-
| `getSecondary` | `(datum: T) => number` | Yes | - | Function to extract the y-axis value from each data point |
|
|
84
|
-
| `title` | `string` | No | - | Chart title displayed at the top |
|
|
85
|
-
| `showDataLabels` | `boolean` | No | `false` | Whether to show data values on data points |
|
|
86
|
-
| `theme` | `Theme` | No | `webLightTheme` | Fluent UI theme object for styling |
|
|
87
|
-
|
|
88
|
-
## Advanced Usage
|
|
89
|
-
|
|
90
|
-
### Website Traffic Sources
|
|
91
|
-
|
|
92
|
-
```tsx
|
|
93
|
-
interface TrafficData {
|
|
94
|
-
date: string;
|
|
95
|
-
organic: number;
|
|
96
|
-
paid: number;
|
|
97
|
-
social: number;
|
|
98
|
-
direct: number;
|
|
99
|
-
email: number;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const trafficData: TrafficData[] = [
|
|
103
|
-
{ date: '2024-01', organic: 12000, paid: 5000, social: 3000, direct: 8000, email: 2000 },
|
|
104
|
-
{ date: '2024-02', organic: 13500, paid: 5500, social: 3500, direct: 8500, email: 2200 },
|
|
105
|
-
{ date: '2024-03', organic: 15000, paid: 6000, social: 4000, direct: 9000, email: 2500 },
|
|
106
|
-
{ date: '2024-04', organic: 16500, paid: 6500, social: 4500, direct: 9500, email: 2800 },
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
<StackedLineChart
|
|
110
|
-
data={[
|
|
111
|
-
{
|
|
112
|
-
label: 'Organic Search',
|
|
113
|
-
data: trafficData.map(d => ({ date: d.date, visitors: d.organic }))
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
label: 'Paid Advertising',
|
|
117
|
-
data: trafficData.map(d => ({ date: d.date, visitors: d.paid }))
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
label: 'Social Media',
|
|
121
|
-
data: trafficData.map(d => ({ date: d.date, visitors: d.social }))
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
label: 'Direct Traffic',
|
|
125
|
-
data: trafficData.map(d => ({ date: d.date, visitors: d.direct }))
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
label: 'Email Marketing',
|
|
129
|
-
data: trafficData.map(d => ({ date: d.date, visitors: d.email }))
|
|
130
|
-
}
|
|
131
|
-
]}
|
|
132
|
-
getPrimary={(datum) => datum.date}
|
|
133
|
-
getSecondary={(datum) => datum.visitors}
|
|
134
|
-
title="Website Traffic Sources Over Time"
|
|
135
|
-
showDataLabels={true}
|
|
136
|
-
theme={webLightTheme}
|
|
137
|
-
/>
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Expense Categories
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
interface ExpenseData {
|
|
144
|
-
quarter: string;
|
|
145
|
-
personnel: number;
|
|
146
|
-
infrastructure: number;
|
|
147
|
-
marketing: number;
|
|
148
|
-
operations: number;
|
|
149
|
-
research: number;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const expenseData: ExpenseData[] = [
|
|
153
|
-
{ quarter: 'Q1 2024', personnel: 150000, infrastructure: 45000, marketing: 35000, operations: 25000, research: 40000 },
|
|
154
|
-
{ quarter: 'Q2 2024', personnel: 165000, infrastructure: 50000, marketing: 40000, operations: 28000, research: 45000 },
|
|
155
|
-
{ quarter: 'Q3 2024', personnel: 180000, infrastructure: 55000, marketing: 45000, operations: 30000, research: 50000 },
|
|
156
|
-
{ quarter: 'Q4 2024', personnel: 195000, infrastructure: 60000, marketing: 50000, operations: 35000, research: 55000 },
|
|
157
|
-
];
|
|
158
|
-
|
|
159
|
-
<StackedLineChart
|
|
160
|
-
data={[
|
|
161
|
-
{
|
|
162
|
-
label: 'Personnel',
|
|
163
|
-
data: expenseData.map(d => ({ quarter: d.quarter, amount: d.personnel }))
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
label: 'Infrastructure',
|
|
167
|
-
data: expenseData.map(d => ({ quarter: d.quarter, amount: d.infrastructure }))
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
label: 'Marketing',
|
|
171
|
-
data: expenseData.map(d => ({ quarter: d.quarter, amount: d.marketing }))
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
label: 'Operations',
|
|
175
|
-
data: expenseData.map(d => ({ quarter: d.quarter, amount: d.operations }))
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
label: 'Research & Development',
|
|
179
|
-
data: expenseData.map(d => ({ quarter: d.quarter, amount: d.research }))
|
|
180
|
-
}
|
|
181
|
-
]}
|
|
182
|
-
getPrimary={(datum) => datum.quarter}
|
|
183
|
-
getSecondary={(datum) => datum.amount}
|
|
184
|
-
title="Quarterly Expense Breakdown"
|
|
185
|
-
theme={webLightTheme}
|
|
186
|
-
/>
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Population Demographics
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
interface DemographicData {
|
|
193
|
-
year: number;
|
|
194
|
-
age18_25: number;
|
|
195
|
-
age26_35: number;
|
|
196
|
-
age36_45: number;
|
|
197
|
-
age46_55: number;
|
|
198
|
-
age56_65: number;
|
|
199
|
-
age65Plus: number;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const demographicData: DemographicData[] = [
|
|
203
|
-
{ year: 2020, age18_25: 15000, age26_35: 22000, age36_45: 18000, age46_55: 16000, age56_65: 12000, age65Plus: 8000 },
|
|
204
|
-
{ year: 2021, age18_25: 15500, age26_35: 23000, age36_45: 18500, age46_55: 16200, age56_65: 12500, age65Plus: 8500 },
|
|
205
|
-
{ year: 2022, age18_25: 16000, age26_35: 24000, age36_45: 19000, age46_55: 16500, age56_65: 13000, age65Plus: 9000 },
|
|
206
|
-
{ year: 2023, age18_25: 16500, age26_35: 25000, age36_45: 19500, age46_55: 16800, age56_65: 13500, age65Plus: 9500 },
|
|
207
|
-
];
|
|
208
|
-
|
|
209
|
-
<StackedLineChart
|
|
210
|
-
data={[
|
|
211
|
-
{
|
|
212
|
-
label: '18-25',
|
|
213
|
-
data: demographicData.map(d => ({ year: d.year.toString(), count: d.age18_25 }))
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
label: '26-35',
|
|
217
|
-
data: demographicData.map(d => ({ year: d.year.toString(), count: d.age26_35 }))
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
label: '36-45',
|
|
221
|
-
data: demographicData.map(d => ({ year: d.year.toString(), count: d.age36_45 }))
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
label: '46-55',
|
|
225
|
-
data: demographicData.map(d => ({ year: d.year.toString(), count: d.age46_55 }))
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
label: '56-65',
|
|
229
|
-
data: demographicData.map(d => ({ year: d.year.toString(), count: d.age56_65 }))
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
label: '65+',
|
|
233
|
-
data: demographicData.map(d => ({ year: d.year.toString(), count: d.age65Plus }))
|
|
234
|
-
}
|
|
235
|
-
]}
|
|
236
|
-
getPrimary={(datum) => datum.year}
|
|
237
|
-
getSecondary={(datum) => datum.count}
|
|
238
|
-
title="Population by Age Group"
|
|
239
|
-
showDataLabels={true}
|
|
240
|
-
theme={webLightTheme}
|
|
241
|
-
/>
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## Data Structure
|
|
245
|
-
|
|
246
|
-
The component expects data in the following format:
|
|
247
|
-
|
|
248
|
-
```tsx
|
|
249
|
-
interface ChartSeries<T> {
|
|
250
|
-
label: string; // Series name (appears in legend)
|
|
251
|
-
data: T[]; // Array of data points
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
Each data point `T` should contain:
|
|
256
|
-
|
|
257
|
-
- A primary value (x-axis category) - string or number
|
|
258
|
-
- A secondary value (y-axis value) - number
|
|
259
|
-
|
|
260
|
-
## Stacking Behavior
|
|
261
|
-
|
|
262
|
-
### Cumulative Values
|
|
263
|
-
|
|
264
|
-
- Each series is stacked on top of the previous ones
|
|
265
|
-
- The total height at any point represents the sum of all series values
|
|
266
|
-
- Series order matters - bottom series appears first in the data array
|
|
267
|
-
|
|
268
|
-
### Visual Interpretation
|
|
269
|
-
|
|
270
|
-
- **Area Size**: Represents the contribution of each series
|
|
271
|
-
- **Total Height**: Shows the cumulative total across all series
|
|
272
|
-
- **Trends**: Individual series trends are visible through area thickness changes
|
|
273
|
-
|
|
274
|
-
## Use Cases
|
|
275
|
-
|
|
276
|
-
Stacked line charts are particularly effective for:
|
|
277
|
-
|
|
278
|
-
### Financial Analysis
|
|
279
|
-
|
|
280
|
-
- **Revenue Breakdown**: Different revenue streams over time
|
|
281
|
-
- **Expense Categories**: Cost distribution across departments
|
|
282
|
-
- **Investment Portfolio**: Asset allocation changes over time
|
|
283
|
-
|
|
284
|
-
### Business Metrics
|
|
285
|
-
|
|
286
|
-
- **Traffic Sources**: Website visitor sources and their evolution
|
|
287
|
-
- **Sales Channels**: Performance of different sales channels
|
|
288
|
-
- **Customer Segments**: Customer base composition over time
|
|
289
|
-
|
|
290
|
-
### Operational Data
|
|
291
|
-
|
|
292
|
-
- **Resource Usage**: Computing resources, storage, bandwidth
|
|
293
|
-
- **Production Output**: Different product lines or categories
|
|
294
|
-
- **Team Performance**: Contribution of different teams to goals
|
|
295
|
-
|
|
296
|
-
### Market Analysis
|
|
297
|
-
|
|
298
|
-
- **Market Share**: How different competitors' shares change
|
|
299
|
-
- **Product Mix**: Sales composition by product category
|
|
300
|
-
- **Geographic Performance**: Regional contribution to totals
|
|
301
|
-
|
|
302
|
-
## Interactive Features
|
|
303
|
-
|
|
304
|
-
### Legend Controls
|
|
305
|
-
|
|
306
|
-
- Click legend items to show/hide data series
|
|
307
|
-
- Visual feedback on hover states
|
|
308
|
-
- At least one series must remain visible
|
|
309
|
-
- Maintains stacking relationship when series are hidden
|
|
310
|
-
|
|
311
|
-
### Area Interactions
|
|
312
|
-
|
|
313
|
-
- Hover effects on stacked areas
|
|
314
|
-
- Rich tooltips showing breakdown of values
|
|
315
|
-
- Smooth transitions and animations
|
|
316
|
-
|
|
317
|
-
### Responsive Layout
|
|
318
|
-
|
|
319
|
-
- Chart automatically resizes to container dimensions
|
|
320
|
-
- Legend positioning adapts to available space
|
|
321
|
-
- Maintains readability across different screen sizes
|
|
322
|
-
|
|
323
|
-
## Styling and Theme Integration
|
|
324
|
-
|
|
325
|
-
The component uses Fluent UI theme tokens:
|
|
326
|
-
|
|
327
|
-
```tsx
|
|
328
|
-
// Area styling
|
|
329
|
-
backgroundColor: seriesColor (semi-transparent fill)
|
|
330
|
-
borderColor: seriesColor
|
|
331
|
-
borderWidth: 2
|
|
332
|
-
fill: true
|
|
333
|
-
tension: 0.4 (smooth curves)
|
|
334
|
-
|
|
335
|
-
// Points
|
|
336
|
-
pointRadius: 3
|
|
337
|
-
pointBackgroundColor: seriesColor
|
|
338
|
-
pointBorderColor: seriesColor
|
|
339
|
-
|
|
340
|
-
// Typography
|
|
341
|
-
fontFamily: theme.fontFamilyBase
|
|
342
|
-
fontSize: theme.fontSizeBase200
|
|
343
|
-
fontWeight: theme.fontWeightSemibold
|
|
344
|
-
color: theme.colorNeutralForeground1
|
|
345
|
-
|
|
346
|
-
// Grid and axes
|
|
347
|
-
labelColor: theme.colorNeutralForeground1
|
|
348
|
-
gridColor: theme.colorNeutralStroke2
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
## Performance Optimizations
|
|
352
|
-
|
|
353
|
-
The component includes several React optimizations:
|
|
354
|
-
|
|
355
|
-
````tsx
|
|
356
|
-
// Memoized color calculations
|
|
357
|
-
const seriesColors = useMemo(() => {
|
|
358
|
-
return data.reduce((acc, series, idx) => {
|
|
359
|
-
const base = getFluentPalette(theme)[
|
|
360
|
-
idx % getFluentPalette(theme).length
|
|
361
|
-
];
|
|
362
|
-
const color = lightenColor(base, 0.3);
|
|
363
|
-
acc[series.label] = color;
|
|
364
|
-
return acc;
|
|
365
|
-
}, {} as Record<string, string>);
|
|
366
|
-
}, [data, theme]);
|
|
367
|
-
|
|
368
|
-
// Memoized category aggregation
|
|
369
|
-
const allCategories = useMemo(() => {
|
|
370
|
-
const set = new Set<string | number>();
|
|
371
|
-
data.forEach(series => {
|
|
372
|
-
series.data.forEach(d => set.add(getPrimary(d)));
|
|
373
|
-
});
|
|
374
|
-
return Array.from(set);
|
|
375
|
-
}, [data, getPrimary]);
|
|
376
|
-
|
|
377
|
-
// Memoized chart data transformation
|
|
378
|
-
const chartData = useMemo(() => {
|
|
379
|
-
return {
|
|
380
|
-
labels: allCategories,
|
|
381
|
-
datasets: data
|
|
382
|
-
.filter(series => visibleSeries.includes(series.label))
|
|
383
|
-
.map(series => ({
|
|
384
|
-
label: series.label,
|
|
385
|
-
data: allCategories.map(cat => {
|
|
386
|
-
const match = series.data.find(d => getPrimary(d) === cat);
|
|
387
|
-
return match ? getSecondary(match) : 0;
|
|
388
|
-
}),
|
|
389
|
-
borderColor: seriesColors[series.label],
|
|
390
|
-
backgroundColor: seriesColors[series.label],
|
|
391
|
-
fill: true,
|
|
392
|
-
tension: 0.4,
|
|
393
|
-
pointRadius: 3,
|
|
394
|
-
})),
|
|
395
|
-
};
|
|
396
|
-
}, [data, visibleSeries, allCategories, getPrimary, getSecondary, seriesColors]);
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CategoryScale,
|
|
3
|
-
Chart as ChartJS,
|
|
4
|
-
ChartOptions,
|
|
5
|
-
Legend,
|
|
6
|
-
LineElement,
|
|
7
|
-
LinearScale,
|
|
8
|
-
PointElement,
|
|
9
|
-
Title,
|
|
10
|
-
Tooltip,
|
|
11
|
-
} from 'chart.js';
|
|
12
|
-
import React, { useMemo, useState } from 'react';
|
|
13
|
-
import { Theme, webLightTheme } from '@fluentui/react-components';
|
|
14
|
-
|
|
15
|
-
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
|
16
|
-
import { Line } from 'react-chartjs-2';
|
|
17
|
-
import RenderLegend from '../../components/RenderLegend/RenderLegend';
|
|
18
|
-
import { useChartUtils } from '../../hooks/useChartUtils';
|
|
19
|
-
import { useGraphGlobalStyles } from '../../graphGlobalStyles/useGraphGlobalStyles';
|
|
20
|
-
|
|
21
|
-
ChartJS.register(ChartDataLabels);
|
|
22
|
-
ChartJS.register(
|
|
23
|
-
CategoryScale,
|
|
24
|
-
LinearScale,
|
|
25
|
-
LineElement,
|
|
26
|
-
PointElement,
|
|
27
|
-
Tooltip,
|
|
28
|
-
Legend,
|
|
29
|
-
Title
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
export interface StackedLineChartProps<T> {
|
|
33
|
-
data: { label: string; data: T[] }[];
|
|
34
|
-
getPrimary: (datum: T) => string | number;
|
|
35
|
-
getSecondary: (datum: T) => number;
|
|
36
|
-
title?: string;
|
|
37
|
-
showDataLabels?: boolean;
|
|
38
|
-
theme?: Theme;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default function StackedLineChart<T extends object>({
|
|
42
|
-
data,
|
|
43
|
-
getPrimary,
|
|
44
|
-
getSecondary,
|
|
45
|
-
title,
|
|
46
|
-
showDataLabels = false,
|
|
47
|
-
theme = webLightTheme,
|
|
48
|
-
}: StackedLineChartProps<T>) {
|
|
49
|
-
const [visibleSeries, setVisibleSeries] = useState(() =>
|
|
50
|
-
data.length > 1 ? data.map(s => s.label) : [data[0]?.label]
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const styles = useGraphGlobalStyles();
|
|
54
|
-
const { lightenColor, getFluentPalette, createFluentTooltip } = useChartUtils(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
|
-
const color = lightenColor(base, 0.3);
|
|
62
|
-
acc[series.label] = color;
|
|
63
|
-
return acc;
|
|
64
|
-
}, {} as Record<string, string>);
|
|
65
|
-
}, [data, theme]);
|
|
66
|
-
|
|
67
|
-
const toggleSeries = (label: string) => {
|
|
68
|
-
setVisibleSeries(prev => {
|
|
69
|
-
const isVisible = prev.includes(label);
|
|
70
|
-
const next = isVisible ? prev.filter(l => l !== label) : [...prev, label];
|
|
71
|
-
return next.length === 0 && data.length > 0 ? [data[0].label] : next;
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const allCategories = useMemo(() => {
|
|
76
|
-
const set = new Set<string | number>();
|
|
77
|
-
data.forEach(series => {
|
|
78
|
-
series.data.forEach(d => set.add(getPrimary(d)));
|
|
79
|
-
});
|
|
80
|
-
return Array.from(set);
|
|
81
|
-
}, [data, getPrimary]);
|
|
82
|
-
|
|
83
|
-
const chartData = useMemo(() => {
|
|
84
|
-
return {
|
|
85
|
-
labels: allCategories,
|
|
86
|
-
datasets: data
|
|
87
|
-
.filter(series => visibleSeries.includes(series.label))
|
|
88
|
-
.map(series => ({
|
|
89
|
-
label: series.label,
|
|
90
|
-
data: allCategories.map(cat => {
|
|
91
|
-
const match = series.data.find(d => getPrimary(d) === cat);
|
|
92
|
-
return match ? getSecondary(match) : 0;
|
|
93
|
-
}),
|
|
94
|
-
borderColor: seriesColors[series.label],
|
|
95
|
-
backgroundColor: seriesColors[series.label],
|
|
96
|
-
fill: true,
|
|
97
|
-
tension: 0.4,
|
|
98
|
-
pointRadius: 3,
|
|
99
|
-
})),
|
|
100
|
-
};
|
|
101
|
-
}, [
|
|
102
|
-
data,
|
|
103
|
-
visibleSeries,
|
|
104
|
-
allCategories,
|
|
105
|
-
getPrimary,
|
|
106
|
-
getSecondary,
|
|
107
|
-
seriesColors,
|
|
108
|
-
]);
|
|
109
|
-
|
|
110
|
-
const { fontFamily, fontSize, labelColor, gridColor } = useMemo(() => ({
|
|
111
|
-
fontFamily: theme.fontFamilyBase,
|
|
112
|
-
fontSize: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
113
|
-
labelColor: theme.colorNeutralForeground1,
|
|
114
|
-
gridColor: theme.colorNeutralStroke2,
|
|
115
|
-
}), [theme]);
|
|
116
|
-
|
|
117
|
-
const options = useMemo<ChartOptions<'line'>>(() => ({
|
|
118
|
-
responsive: true,
|
|
119
|
-
maintainAspectRatio: false,
|
|
120
|
-
plugins: {
|
|
121
|
-
title: {
|
|
122
|
-
display: !!title,
|
|
123
|
-
text: title,
|
|
124
|
-
font: {
|
|
125
|
-
size: 14,
|
|
126
|
-
family: theme.fontFamilyBase,
|
|
127
|
-
weight: theme.fontWeightSemibold,
|
|
128
|
-
},
|
|
129
|
-
color: theme.colorNeutralForeground1,
|
|
130
|
-
padding: {
|
|
131
|
-
top: 20,
|
|
132
|
-
bottom: 20,
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
datalabels: {
|
|
136
|
-
display: showDataLabels,
|
|
137
|
-
color: theme.colorNeutralForeground1,
|
|
138
|
-
font: {
|
|
139
|
-
family: theme.fontFamilyBase,
|
|
140
|
-
size: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
legend: { display: false },
|
|
144
|
-
tooltip: createFluentTooltip<'line'>(theme),
|
|
145
|
-
},
|
|
146
|
-
scales: {
|
|
147
|
-
x: {
|
|
148
|
-
stacked: true,
|
|
149
|
-
ticks: {
|
|
150
|
-
color: labelColor,
|
|
151
|
-
font: { family: fontFamily, size: fontSize },
|
|
152
|
-
},
|
|
153
|
-
grid: { color: gridColor },
|
|
154
|
-
},
|
|
155
|
-
y: {
|
|
156
|
-
stacked: true,
|
|
157
|
-
ticks: {
|
|
158
|
-
color: labelColor,
|
|
159
|
-
font: { family: fontFamily, size: fontSize },
|
|
160
|
-
},
|
|
161
|
-
grid: { color: gridColor },
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
}), [
|
|
165
|
-
theme,
|
|
166
|
-
title,
|
|
167
|
-
showDataLabels,
|
|
168
|
-
createFluentTooltip,
|
|
169
|
-
labelColor,
|
|
170
|
-
fontFamily,
|
|
171
|
-
fontSize,
|
|
172
|
-
gridColor,
|
|
173
|
-
]);
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<div className={styles.chartWithLegend}>
|
|
177
|
-
<div className={styles.chartArea}>
|
|
178
|
-
<Line data={chartData} options={options} />
|
|
179
|
-
</div>
|
|
180
|
-
<RenderLegend
|
|
181
|
-
data={data}
|
|
182
|
-
visibleSeries={visibleSeries}
|
|
183
|
-
seriesColors={seriesColors}
|
|
184
|
-
toggleSeries={toggleSeries}
|
|
185
|
-
/>
|
|
186
|
-
</div>
|
|
187
|
-
);
|
|
188
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './StackedLineChart';
|