@spteck/fluentui-react-charts 1.0.6 → 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 +1367 -1066
- 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 +1353 -1066
- package/dist/fluentui-react-charts.esm.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/package.json +4 -4
- 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 -189
- 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 -122
- 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,414 +0,0 @@
|
|
|
1
|
-
# SteamChart Component
|
|
2
|
-
|
|
3
|
-
A specialized stream chart (stacked area chart) component built with Chart.js and Fluent UI React. This component displays data as flowing, stacked areas that create a "stream" effect, making it perfect for visualizing how different categories contribute to a total over time, with smooth transitions and the ability to toggle between raw values and percentages.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Stream Visualization**: Smooth, flowing stacked areas with curved boundaries
|
|
8
|
-
- **Percentage Toggle**: Switch between raw values and percentage view
|
|
9
|
-
- **Interactive Legend**: Toggle series visibility with click interactions
|
|
10
|
-
- **Smooth Animations**: Fluid transitions with configurable easing
|
|
11
|
-
- **Filled Areas**: Semi-transparent filled areas for better visual flow
|
|
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 { SteamChart } from './components/steamChart/SteamChart';
|
|
29
|
-
import { webLightTheme } from '@fluentui/react-components';
|
|
30
|
-
|
|
31
|
-
interface MusicStreamingData {
|
|
32
|
-
month: string;
|
|
33
|
-
pop: number;
|
|
34
|
-
rock: number;
|
|
35
|
-
jazz: number;
|
|
36
|
-
classical: number;
|
|
37
|
-
electronic: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const streamingData: MusicStreamingData[] = [
|
|
41
|
-
{ month: 'Jan', pop: 45000, rock: 32000, jazz: 12000, classical: 8000, electronic: 18000 },
|
|
42
|
-
{ month: 'Feb', pop: 48000, rock: 35000, jazz: 13000, classical: 9000, electronic: 20000 },
|
|
43
|
-
{ month: 'Mar', pop: 52000, rock: 38000, jazz: 15000, classical: 10000, electronic: 22000 },
|
|
44
|
-
{ month: 'Apr', pop: 49000, rock: 36000, jazz: 14000, classical: 11000, electronic: 25000 },
|
|
45
|
-
{ month: 'May', pop: 55000, rock: 40000, jazz: 16000, classical: 12000, electronic: 28000 },
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
function App() {
|
|
49
|
-
return (
|
|
50
|
-
<div style={{ width: '800px', height: '500px' }}>
|
|
51
|
-
<SteamChart
|
|
52
|
-
data={[
|
|
53
|
-
{
|
|
54
|
-
label: 'Pop',
|
|
55
|
-
data: streamingData.map(d => ({ month: d.month, streams: d.pop }))
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
label: 'Rock',
|
|
59
|
-
data: streamingData.map(d => ({ month: d.month, streams: d.rock }))
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
label: 'Jazz',
|
|
63
|
-
data: streamingData.map(d => ({ month: d.month, streams: d.jazz }))
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
label: 'Classical',
|
|
67
|
-
data: streamingData.map(d => ({ month: d.month, streams: d.classical }))
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
label: 'Electronic',
|
|
71
|
-
data: streamingData.map(d => ({ month: d.month, streams: d.electronic }))
|
|
72
|
-
}
|
|
73
|
-
]}
|
|
74
|
-
getPrimary={(datum) => datum.month}
|
|
75
|
-
getSecondary={(datum) => datum.streams}
|
|
76
|
-
title="Music Streaming by Genre"
|
|
77
|
-
theme={webLightTheme}
|
|
78
|
-
/>
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Props
|
|
85
|
-
|
|
86
|
-
### SteamChartProps<T>
|
|
87
|
-
|
|
88
|
-
| Prop | Type | Required | Default | Description |
|
|
89
|
-
|------|------|----------|---------|-------------|
|
|
90
|
-
| `data` | `{ label: string; data: T[] }[]` | Yes | - | Array of data series with labels and data points |
|
|
91
|
-
| `getPrimary` | `(datum: T) => string \| number` | Yes | - | Function to extract the x-axis category from each data point |
|
|
92
|
-
| `getSecondary` | `(datum: T) => number` | Yes | - | Function to extract the y-axis value from each data point |
|
|
93
|
-
| `title` | `string` | No | - | Chart title displayed at the top |
|
|
94
|
-
| `showDataLabels` | `boolean` | No | `false` | Whether to show data values on data points |
|
|
95
|
-
| `theme` | `Theme` | No | `webLightTheme` | Fluent UI theme object for styling |
|
|
96
|
-
|
|
97
|
-
## Advanced Usage
|
|
98
|
-
|
|
99
|
-
### Social Media Engagement
|
|
100
|
-
|
|
101
|
-
```tsx
|
|
102
|
-
interface SocialEngagement {
|
|
103
|
-
week: string;
|
|
104
|
-
facebook: number;
|
|
105
|
-
instagram: number;
|
|
106
|
-
twitter: number;
|
|
107
|
-
linkedin: number;
|
|
108
|
-
tiktok: number;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const engagementData: SocialEngagement[] = [
|
|
112
|
-
{ week: 'Week 1', facebook: 12000, instagram: 18000, twitter: 8000, linkedin: 3000, tiktok: 25000 },
|
|
113
|
-
{ week: 'Week 2', facebook: 13500, instagram: 20000, twitter: 9000, linkedin: 3500, tiktok: 28000 },
|
|
114
|
-
{ week: 'Week 3', facebook: 11000, instagram: 22000, twitter: 7500, linkedin: 4000, tiktok: 32000 },
|
|
115
|
-
{ week: 'Week 4', facebook: 14000, instagram: 24000, twitter: 8500, linkedin: 4500, tiktok: 35000 },
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
<SteamChart
|
|
119
|
-
data={[
|
|
120
|
-
{
|
|
121
|
-
label: 'Facebook',
|
|
122
|
-
data: engagementData.map(d => ({ week: d.week, engagement: d.facebook }))
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
label: 'Instagram',
|
|
126
|
-
data: engagementData.map(d => ({ week: d.week, engagement: d.instagram }))
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
label: 'Twitter',
|
|
130
|
-
data: engagementData.map(d => ({ week: d.week, engagement: d.twitter }))
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
label: 'LinkedIn',
|
|
134
|
-
data: engagementData.map(d => ({ week: d.week, engagement: d.linkedin }))
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
label: 'TikTok',
|
|
138
|
-
data: engagementData.map(d => ({ week: d.week, engagement: d.tiktok }))
|
|
139
|
-
}
|
|
140
|
-
]}
|
|
141
|
-
getPrimary={(datum) => datum.week}
|
|
142
|
-
getSecondary={(datum) => datum.engagement}
|
|
143
|
-
title="Social Media Engagement by Platform"
|
|
144
|
-
showDataLabels={true}
|
|
145
|
-
theme={webLightTheme}
|
|
146
|
-
/>
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### E-commerce Sales Channels
|
|
150
|
-
|
|
151
|
-
```tsx
|
|
152
|
-
interface SalesChannelData {
|
|
153
|
-
quarter: string;
|
|
154
|
-
website: number;
|
|
155
|
-
mobileApp: number;
|
|
156
|
-
retailStores: number;
|
|
157
|
-
marketplace: number;
|
|
158
|
-
wholesale: number;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const salesData: SalesChannelData[] = [
|
|
162
|
-
{ quarter: 'Q1 2024', website: 250000, mobileApp: 180000, retailStores: 320000, marketplace: 150000, wholesale: 200000 },
|
|
163
|
-
{ quarter: 'Q2 2024', website: 280000, mobileApp: 220000, retailStores: 310000, marketplace: 180000, wholesale: 220000 },
|
|
164
|
-
{ quarter: 'Q3 2024', website: 320000, mobileApp: 260000, retailStores: 300000, marketplace: 210000, wholesale: 240000 },
|
|
165
|
-
{ quarter: 'Q4 2024', website: 380000, mobileApp: 300000, retailStores: 290000, marketplace: 250000, wholesale: 260000 },
|
|
166
|
-
];
|
|
167
|
-
|
|
168
|
-
<SteamChart
|
|
169
|
-
data={[
|
|
170
|
-
{
|
|
171
|
-
label: 'Website',
|
|
172
|
-
data: salesData.map(d => ({ quarter: d.quarter, sales: d.website }))
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
label: 'Mobile App',
|
|
176
|
-
data: salesData.map(d => ({ quarter: d.quarter, sales: d.mobileApp }))
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
label: 'Retail Stores',
|
|
180
|
-
data: salesData.map(d => ({ quarter: d.quarter, sales: d.retailStores }))
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
label: 'Marketplace',
|
|
184
|
-
data: salesData.map(d => ({ quarter: d.quarter, sales: d.marketplace }))
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
label: 'Wholesale',
|
|
188
|
-
data: salesData.map(d => ({ quarter: d.quarter, sales: d.wholesale }))
|
|
189
|
-
}
|
|
190
|
-
]}
|
|
191
|
-
getPrimary={(datum) => datum.quarter}
|
|
192
|
-
getSecondary={(datum) => datum.sales}
|
|
193
|
-
title="Sales Performance by Channel"
|
|
194
|
-
theme={webLightTheme}
|
|
195
|
-
/>
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Energy Production Sources
|
|
199
|
-
|
|
200
|
-
```tsx
|
|
201
|
-
interface EnergyProduction {
|
|
202
|
-
month: string;
|
|
203
|
-
solar: number;
|
|
204
|
-
wind: number;
|
|
205
|
-
hydro: number;
|
|
206
|
-
nuclear: number;
|
|
207
|
-
coal: number;
|
|
208
|
-
naturalGas: number;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const energyData: EnergyProduction[] = [
|
|
212
|
-
{ month: 'Jan', solar: 1200, wind: 2800, hydro: 3500, nuclear: 4200, coal: 2100, naturalGas: 3200 },
|
|
213
|
-
{ month: 'Feb', solar: 1400, wind: 3200, hydro: 3200, nuclear: 4200, coal: 1900, naturalGas: 3100 },
|
|
214
|
-
{ month: 'Mar', solar: 1800, wind: 3600, hydro: 3800, nuclear: 4200, coal: 1700, naturalGas: 2900 },
|
|
215
|
-
{ month: 'Apr', solar: 2200, wind: 3400, hydro: 4200, nuclear: 4200, coal: 1500, naturalGas: 2700 },
|
|
216
|
-
];
|
|
217
|
-
|
|
218
|
-
<SteamChart
|
|
219
|
-
data={[
|
|
220
|
-
{
|
|
221
|
-
label: 'Solar',
|
|
222
|
-
data: energyData.map(d => ({ month: d.month, production: d.solar }))
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
label: 'Wind',
|
|
226
|
-
data: energyData.map(d => ({ month: d.month, production: d.wind }))
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
label: 'Hydro',
|
|
230
|
-
data: energyData.map(d => ({ month: d.month, production: d.hydro }))
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
label: 'Nuclear',
|
|
234
|
-
data: energyData.map(d => ({ month: d.month, production: d.nuclear }))
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
label: 'Coal',
|
|
238
|
-
data: energyData.map(d => ({ month: d.month, production: d.coal }))
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
label: 'Natural Gas',
|
|
242
|
-
data: energyData.map(d => ({ month: d.month, production: d.naturalGas }))
|
|
243
|
-
}
|
|
244
|
-
]}
|
|
245
|
-
getPrimary={(datum) => datum.month}
|
|
246
|
-
getSecondary={(datum) => datum.production}
|
|
247
|
-
title="Energy Production by Source (MWh)"
|
|
248
|
-
showDataLabels={true}
|
|
249
|
-
theme={webLightTheme}
|
|
250
|
-
/>
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## Data Structure
|
|
254
|
-
|
|
255
|
-
The component expects data in the following format:
|
|
256
|
-
|
|
257
|
-
```tsx
|
|
258
|
-
interface ChartSeries<T> {
|
|
259
|
-
label: string; // Series name (appears in legend)
|
|
260
|
-
data: T[]; // Array of data points
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Each data point `T` should contain:
|
|
265
|
-
|
|
266
|
-
- A primary value (x-axis category) - string or number
|
|
267
|
-
- A secondary value (y-axis value) - number
|
|
268
|
-
|
|
269
|
-
## Stream Chart Characteristics
|
|
270
|
-
|
|
271
|
-
### Visual Flow
|
|
272
|
-
|
|
273
|
-
- **Smooth Curves**: High tension value (0.5) creates flowing stream effect
|
|
274
|
-
- **Stacked Areas**: Each category flows on top of the previous ones
|
|
275
|
-
- **No Borders**: Transparent borders create seamless flow between areas
|
|
276
|
-
- **Organic Shape**: Natural, river-like appearance
|
|
277
|
-
|
|
278
|
-
### Percentage Toggle
|
|
279
|
-
|
|
280
|
-
- **Raw Values**: Shows actual numerical values
|
|
281
|
-
- **Percentage View**: Shows proportional contribution (0-100%)
|
|
282
|
-
- **Toggle Button**: Central button to switch between views
|
|
283
|
-
- **Dynamic Scaling**: Y-axis adjusts automatically
|
|
284
|
-
|
|
285
|
-
## Use Cases
|
|
286
|
-
|
|
287
|
-
Steam charts are particularly effective for:
|
|
288
|
-
|
|
289
|
-
### Content Consumption
|
|
290
|
-
|
|
291
|
-
- **Media Streaming**: Different genres or content types over time
|
|
292
|
-
- **Website Traffic**: Traffic sources and their evolution
|
|
293
|
-
- **App Usage**: Feature usage patterns across time periods
|
|
294
|
-
|
|
295
|
-
### Market Analysis
|
|
296
|
-
|
|
297
|
-
- **Market Share**: How different players' shares flow over time
|
|
298
|
-
- **Product Mix**: Sales composition with smooth transitions
|
|
299
|
-
- **Demographic Changes**: Population segments over time
|
|
300
|
-
|
|
301
|
-
### Resource Allocation
|
|
302
|
-
|
|
303
|
-
- **Budget Distribution**: How budget allocation changes over time
|
|
304
|
-
- **Team Productivity**: Contribution of different teams
|
|
305
|
-
- **Energy Mix**: Renewable vs non-renewable energy sources
|
|
306
|
-
|
|
307
|
-
### Communication Channels
|
|
308
|
-
|
|
309
|
-
- **Social Media Engagement**: Platform-wise engagement trends
|
|
310
|
-
- **Customer Support**: Channel usage patterns
|
|
311
|
-
- **Marketing Channels**: Campaign performance across channels
|
|
312
|
-
|
|
313
|
-
## Interactive Features
|
|
314
|
-
|
|
315
|
-
### Percentage Toggle Button
|
|
316
|
-
|
|
317
|
-
- Central toggle button to switch between raw values and percentages
|
|
318
|
-
- Smooth transitions between view modes
|
|
319
|
-
- Y-axis automatically adjusts scale and labels
|
|
320
|
-
- Maintains visual flow during transitions
|
|
321
|
-
|
|
322
|
-
### Legend Controls
|
|
323
|
-
|
|
324
|
-
- Click legend items to show/hide data series
|
|
325
|
-
- Visual feedback on hover states
|
|
326
|
-
- At least one series must remain visible
|
|
327
|
-
- Maintains stream flow when series are hidden
|
|
328
|
-
|
|
329
|
-
### Stream Interactions
|
|
330
|
-
|
|
331
|
-
- Hover effects on stream areas
|
|
332
|
-
- Rich tooltips showing breakdown of values
|
|
333
|
-
- Smooth animations with easing effects
|
|
334
|
-
- Index-based interaction mode for better UX
|
|
335
|
-
|
|
336
|
-
## Styling and Theme Integration
|
|
337
|
-
|
|
338
|
-
The component uses Fluent UI theme tokens:
|
|
339
|
-
|
|
340
|
-
```tsx
|
|
341
|
-
// Stream styling
|
|
342
|
-
backgroundColor: seriesColor (semi-transparent)
|
|
343
|
-
borderColor: 'transparent'
|
|
344
|
-
borderWidth: 0
|
|
345
|
-
fill: true
|
|
346
|
-
tension: 0.5 (high curve smoothness)
|
|
347
|
-
|
|
348
|
-
// Animation
|
|
349
|
-
duration: 800ms
|
|
350
|
-
easing: 'easeOutQuart'
|
|
351
|
-
|
|
352
|
-
// Typography
|
|
353
|
-
fontFamily: theme.fontFamilyBase
|
|
354
|
-
fontSize: theme.fontSizeBase200
|
|
355
|
-
fontWeight: theme.fontWeightSemibold
|
|
356
|
-
color: theme.colorNeutralForeground1
|
|
357
|
-
|
|
358
|
-
// Toggle button
|
|
359
|
-
appearance: 'secondary'
|
|
360
|
-
shape: 'circular'
|
|
361
|
-
size: 'small'
|
|
362
|
-
width: '150px'
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
## Performance Optimizations
|
|
366
|
-
|
|
367
|
-
The component includes several React optimizations:
|
|
368
|
-
|
|
369
|
-
````tsx
|
|
370
|
-
// Memoized color calculations
|
|
371
|
-
const seriesColors = useMemo(() => {
|
|
372
|
-
return data.reduce((acc, series, idx) => {
|
|
373
|
-
const base = getFluentPalette(theme)[
|
|
374
|
-
idx % getFluentPalette(theme).length
|
|
375
|
-
];
|
|
376
|
-
acc[series.label] = lightenColor(base, 0.3);
|
|
377
|
-
return acc;
|
|
378
|
-
}, {} as Record<string, string>);
|
|
379
|
-
}, [data, getFluentPalette, lightenColor, theme]);
|
|
380
|
-
|
|
381
|
-
// Memoized total calculations for percentages
|
|
382
|
-
const totalPerPoint = useMemo(() => {
|
|
383
|
-
return allLabels.map(cat =>
|
|
384
|
-
data.reduce((sum, series) => {
|
|
385
|
-
const match = series.data.find(d => getPrimary(d) === cat);
|
|
386
|
-
return sum + (match ? getSecondary(match) : 0);
|
|
387
|
-
}, 0)
|
|
388
|
-
);
|
|
389
|
-
}, [allLabels, data, getPrimary, getSecondary]);
|
|
390
|
-
|
|
391
|
-
// Memoized chart data with percentage support
|
|
392
|
-
const chartData = useMemo(() => {
|
|
393
|
-
return {
|
|
394
|
-
labels: allLabels,
|
|
395
|
-
datasets: data
|
|
396
|
-
.filter(series => visibleSeries.includes(series.label))
|
|
397
|
-
.map((series) => ({
|
|
398
|
-
label: series.label,
|
|
399
|
-
fill: true,
|
|
400
|
-
backgroundColor: seriesColors[series.label],
|
|
401
|
-
borderColor: 'transparent',
|
|
402
|
-
borderWidth: 0,
|
|
403
|
-
data: allLabels.map((cat, index) => {
|
|
404
|
-
const match = series.data.find(d => getPrimary(d) === cat);
|
|
405
|
-
const rawValue = match ? getSecondary(match) : 0;
|
|
406
|
-
const total = totalPerPoint[index] || 1;
|
|
407
|
-
return showPercent
|
|
408
|
-
? Math.round(((rawValue / total) * 100 + Number.EPSILON) * 100) / 100
|
|
409
|
-
: Math.round((rawValue + Number.EPSILON) * 100) / 100;
|
|
410
|
-
}),
|
|
411
|
-
tension: 0.5,
|
|
412
|
-
})),
|
|
413
|
-
};
|
|
414
|
-
}, [data, visibleSeries, allLabels, getPrimary, getSecondary, seriesColors, totalPerPoint, showPercent]);
|
|
@@ -1,236 +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, ToggleButton, webLightTheme } from '@fluentui/react-components';
|
|
15
|
-
import { createFluentTooltip, useChartUtils } from '../../hooks/useChartUtils';
|
|
16
|
-
|
|
17
|
-
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
|
18
|
-
import { Line } from 'react-chartjs-2';
|
|
19
|
-
import RenderLegend from '../../components/RenderLegend/RenderLegend';
|
|
20
|
-
import { Stack } from '../../components/stack/Stack';
|
|
21
|
-
import { useGraphGlobalStyles } from '../../graphGlobalStyles/useGraphGlobalStyles';
|
|
22
|
-
|
|
23
|
-
ChartJS.register(ChartDataLabels);
|
|
24
|
-
ChartJS.register(
|
|
25
|
-
LineElement,
|
|
26
|
-
PointElement,
|
|
27
|
-
Filler,
|
|
28
|
-
CategoryScale,
|
|
29
|
-
LinearScale,
|
|
30
|
-
Tooltip,
|
|
31
|
-
Legend,
|
|
32
|
-
Title
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
export interface SteamChartProps<T> {
|
|
36
|
-
data: { label: string; data: T[] }[];
|
|
37
|
-
getPrimary: (datum: T) => string | number;
|
|
38
|
-
getSecondary: (datum: T) => number;
|
|
39
|
-
title?: string;
|
|
40
|
-
showDataLabels?: boolean;
|
|
41
|
-
theme?: Theme;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export default function SteamChart<T extends object>({
|
|
45
|
-
data,
|
|
46
|
-
getPrimary,
|
|
47
|
-
getSecondary,
|
|
48
|
-
title,
|
|
49
|
-
showDataLabels = false,
|
|
50
|
-
theme = webLightTheme,
|
|
51
|
-
}: SteamChartProps<T>) {
|
|
52
|
-
const [visibleSeries, setVisibleSeries] = useState(() =>
|
|
53
|
-
data.length > 1 ? data.map(s => s.label) : [data[0]?.label]
|
|
54
|
-
);
|
|
55
|
-
const [showPercent, setShowPercent] = useState(false);
|
|
56
|
-
const styles = useGraphGlobalStyles();
|
|
57
|
-
|
|
58
|
-
const { lightenColor, getFluentPalette } = useChartUtils(theme);
|
|
59
|
-
|
|
60
|
-
const seriesColors = useMemo(() => {
|
|
61
|
-
return data.reduce((acc, series, idx) => {
|
|
62
|
-
const base = getFluentPalette(theme)[
|
|
63
|
-
idx % getFluentPalette(theme).length
|
|
64
|
-
];
|
|
65
|
-
acc[series.label] = lightenColor(base, 0.3);
|
|
66
|
-
return acc;
|
|
67
|
-
}, {} as Record<string, string>);
|
|
68
|
-
}, [data, getFluentPalette, lightenColor, theme]);
|
|
69
|
-
|
|
70
|
-
const toggleSeries = React.useCallback(
|
|
71
|
-
(label: string) => {
|
|
72
|
-
setVisibleSeries(prev => {
|
|
73
|
-
const isVisible = prev.includes(label);
|
|
74
|
-
const next = isVisible
|
|
75
|
-
? prev.filter(l => l !== label)
|
|
76
|
-
: [...prev, label];
|
|
77
|
-
return next.length === 0 ? [data[0].label] : next;
|
|
78
|
-
});
|
|
79
|
-
},
|
|
80
|
-
[data]
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const allLabels = useMemo(() => {
|
|
84
|
-
const set = new Set<string | number>();
|
|
85
|
-
data.forEach(series => {
|
|
86
|
-
series.data.forEach(d => set.add(getPrimary(d)));
|
|
87
|
-
});
|
|
88
|
-
return Array.from(set);
|
|
89
|
-
}, [data, getPrimary]);
|
|
90
|
-
|
|
91
|
-
const totalPerPoint = useMemo(() => {
|
|
92
|
-
return allLabels.map(cat =>
|
|
93
|
-
data.reduce((sum, series) => {
|
|
94
|
-
const match = series.data.find(d => getPrimary(d) === cat);
|
|
95
|
-
return sum + (match ? getSecondary(match) : 0);
|
|
96
|
-
}, 0)
|
|
97
|
-
);
|
|
98
|
-
}, [allLabels, data, getPrimary, getSecondary]);
|
|
99
|
-
|
|
100
|
-
const chartData = useMemo(() => {
|
|
101
|
-
return {
|
|
102
|
-
labels: allLabels,
|
|
103
|
-
datasets: data
|
|
104
|
-
.filter(series => visibleSeries.includes(series.label))
|
|
105
|
-
.map((series, ) => ({
|
|
106
|
-
label: series.label,
|
|
107
|
-
fill: true,
|
|
108
|
-
backgroundColor: seriesColors[series.label],
|
|
109
|
-
borderColor: 'transparent',
|
|
110
|
-
borderWidth: 0,
|
|
111
|
-
data: allLabels.map((cat, index) => {
|
|
112
|
-
const match = series.data.find(d => getPrimary(d) === cat);
|
|
113
|
-
const rawValue = match ? getSecondary(match) : 0;
|
|
114
|
-
const total = totalPerPoint[index] || 1;
|
|
115
|
-
return showPercent
|
|
116
|
-
? Math.round(((rawValue / total) * 100 + Number.EPSILON) * 100) / 100
|
|
117
|
-
: Math.round((rawValue + Number.EPSILON) * 100) / 100;
|
|
118
|
-
}),
|
|
119
|
-
tension: 0.5,
|
|
120
|
-
})),
|
|
121
|
-
};
|
|
122
|
-
}, [
|
|
123
|
-
data,
|
|
124
|
-
visibleSeries,
|
|
125
|
-
allLabels,
|
|
126
|
-
getPrimary,
|
|
127
|
-
getSecondary,
|
|
128
|
-
seriesColors,
|
|
129
|
-
totalPerPoint,
|
|
130
|
-
showPercent,
|
|
131
|
-
]);
|
|
132
|
-
|
|
133
|
-
const { fontFamily, fontSize, labelColor, gridColor } = useMemo(() => ({
|
|
134
|
-
fontFamily: theme.fontFamilyBase,
|
|
135
|
-
fontSize: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
136
|
-
labelColor: theme.colorNeutralForeground1,
|
|
137
|
-
gridColor: theme.colorNeutralStroke2,
|
|
138
|
-
}), [theme]);
|
|
139
|
-
|
|
140
|
-
const options = useMemo<ChartOptions<'line'>>(() => ({
|
|
141
|
-
responsive: true,
|
|
142
|
-
maintainAspectRatio: false,
|
|
143
|
-
animation: {
|
|
144
|
-
duration: 800,
|
|
145
|
-
easing: 'easeOutQuart',
|
|
146
|
-
},
|
|
147
|
-
plugins: {
|
|
148
|
-
title: {
|
|
149
|
-
display: !!title,
|
|
150
|
-
text: title,
|
|
151
|
-
font: {
|
|
152
|
-
size: 14,
|
|
153
|
-
family: theme.fontFamilyBase,
|
|
154
|
-
weight: theme.fontWeightSemibold,
|
|
155
|
-
},
|
|
156
|
-
color: theme.colorNeutralForeground1,
|
|
157
|
-
padding: {
|
|
158
|
-
top: 20,
|
|
159
|
-
bottom: 20,
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
datalabels: {
|
|
163
|
-
display: showDataLabels,
|
|
164
|
-
color: theme.colorNeutralForeground1,
|
|
165
|
-
font: {
|
|
166
|
-
family: theme.fontFamilyBase,
|
|
167
|
-
size: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
legend: { display: false },
|
|
171
|
-
tooltip: createFluentTooltip<'line'>(theme),
|
|
172
|
-
},
|
|
173
|
-
interaction: {
|
|
174
|
-
mode: 'index',
|
|
175
|
-
intersect: false,
|
|
176
|
-
},
|
|
177
|
-
scales: {
|
|
178
|
-
x: {
|
|
179
|
-
stacked: true,
|
|
180
|
-
ticks: {
|
|
181
|
-
color: labelColor,
|
|
182
|
-
font: { family: fontFamily, size: fontSize },
|
|
183
|
-
},
|
|
184
|
-
grid: { color: gridColor },
|
|
185
|
-
},
|
|
186
|
-
y: {
|
|
187
|
-
stacked: true,
|
|
188
|
-
ticks: {
|
|
189
|
-
callback: (value: string | number) => (showPercent ? `${value}%` : value),
|
|
190
|
-
color: labelColor,
|
|
191
|
-
font: { family: fontFamily, size: fontSize },
|
|
192
|
-
},
|
|
193
|
-
grid: { color: gridColor },
|
|
194
|
-
min: 0,
|
|
195
|
-
max: showPercent ? 100 : undefined,
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
}), [
|
|
199
|
-
theme,
|
|
200
|
-
title,
|
|
201
|
-
showDataLabels,
|
|
202
|
-
createFluentTooltip,
|
|
203
|
-
labelColor,
|
|
204
|
-
fontFamily,
|
|
205
|
-
fontSize,
|
|
206
|
-
gridColor,
|
|
207
|
-
showPercent,
|
|
208
|
-
]);
|
|
209
|
-
|
|
210
|
-
return (
|
|
211
|
-
<div className={styles.chartWithLegend}>
|
|
212
|
-
<div className={styles.chartArea}>
|
|
213
|
-
<Line data={chartData} options={options} />
|
|
214
|
-
</div>
|
|
215
|
-
<Stack justifyContent="center" alignItems="center" margin="7px">
|
|
216
|
-
<ToggleButton
|
|
217
|
-
onClick={() => setShowPercent(p => !p)}
|
|
218
|
-
shape="circular"
|
|
219
|
-
appearance="secondary"
|
|
220
|
-
size="small"
|
|
221
|
-
style={{ width: '150px' }}
|
|
222
|
-
>
|
|
223
|
-
{showPercent ? 'Show Raw Values' : 'Show %'}
|
|
224
|
-
</ToggleButton>
|
|
225
|
-
</Stack>
|
|
226
|
-
<div className={styles.legendArea}>
|
|
227
|
-
<RenderLegend
|
|
228
|
-
data={data}
|
|
229
|
-
visibleSeries={visibleSeries}
|
|
230
|
-
seriesColors={seriesColors}
|
|
231
|
-
toggleSeries={toggleSeries}
|
|
232
|
-
/>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
);
|
|
236
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './SteamChart';
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Caption1,
|
|
5
|
-
tokens,
|
|
6
|
-
} from '@fluentui/react-components';
|
|
7
|
-
import { Icon } from '@iconify/react';
|
|
8
|
-
|
|
9
|
-
import { useRenderLabelStyles } from './useRenderLabelStylesStyles';
|
|
10
|
-
|
|
11
|
-
export interface IRenderLabelProps {
|
|
12
|
-
label: string; icon?: string | JSX.Element; isRequired?: boolean
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const RenderLabel: React.FunctionComponent<IRenderLabelProps> = (props: React.PropsWithChildren<IRenderLabelProps>) => {
|
|
16
|
-
const { label, icon, isRequired } = props;
|
|
17
|
-
const styles = useRenderLabelStyles();
|
|
18
|
-
return (
|
|
19
|
-
<>
|
|
20
|
-
<div className={styles.labelContainer}>
|
|
21
|
-
{icon && React.isValidElement(icon) ? (
|
|
22
|
-
icon
|
|
23
|
-
) : (
|
|
24
|
-
<Icon
|
|
25
|
-
icon={icon as string}
|
|
26
|
-
className={styles.iconStyles}
|
|
27
|
-
width={"20px"}
|
|
28
|
-
height={"20px"}
|
|
29
|
-
color={tokens.colorBrandForeground1}
|
|
30
|
-
/>
|
|
31
|
-
)}
|
|
32
|
-
<Caption1 style={{ color: tokens.colorBrandForeground1 }}>{label}</Caption1>
|
|
33
|
-
<Caption1 style={{ color: tokens.colorPaletteRedForeground1 }}>{isRequired ? " *" : ""}</Caption1>
|
|
34
|
-
</div>
|
|
35
|
-
</>
|
|
36
|
-
);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export default RenderLabel;
|