@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,409 @@
1
+ import {
2
+ Card,
3
+ CardHeader,
4
+ FluentProvider,
5
+ IdPrefixProvider,
6
+ Text,
7
+ } from '@fluentui/react-components';
8
+ import React, { useEffect, useRef, useState } from 'react';
9
+
10
+ import { ButtonMenu } from './buttonMenu/ButtonMenu';
11
+ import { ResizeObserver } from '@juggle/resize-observer';
12
+ import { css } from '@emotion/css';
13
+ import { tokens } from '@fluentui/react-components';
14
+ import { useGraphUtils } from '../hooks/useGraphUtils';
15
+ import { useThemeContext } from './themeProvider/ThemeProvider';
16
+
17
+ // Fixed card width grid and centering
18
+ const dashboardContainer = css`
19
+ display: grid;
20
+ padding: 20px;
21
+ gap: 16px;
22
+ background-color: ${tokens.colorNeutralBackground2};
23
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
24
+ grid-auto-rows: minmax(350px, 450px);
25
+ justify-content: center;
26
+ grid-auto-flow: dense;
27
+ overflow: auto;
28
+ `;
29
+ const cardBody = css`
30
+ display: flex;
31
+ flex-direction: column;
32
+ height: 100%;
33
+ `;
34
+ const chartContainer = css`
35
+ flex: 1;
36
+ min-height: 0; /* enables flex child to shrink properly */
37
+ overflow: hidden;
38
+ `;
39
+
40
+ const DashboardWithGridDnD: React.FC = () => {
41
+ const { theme } = useThemeContext();
42
+ const { getChartComponent } = useGraphUtils(theme);
43
+
44
+ // sample charts array
45
+ // Helper to generate random integer between min and max (inclusive)
46
+ const randomInt = (min: number, max: number) =>
47
+ Math.floor(Math.random() * (max - min + 1)) + min;
48
+
49
+ // Helper to generate random chart data for a given type
50
+ const generateRandomData = (type: string) => {
51
+ switch (type) {
52
+ case 'bar':
53
+ case 'line':
54
+ case 'area':
55
+ case 'bar-horizontal':
56
+ case 'stacked-line':
57
+ return [
58
+ {
59
+ label: '2023',
60
+ data: [
61
+ { name: 'Jan', value: randomInt(10, 100) },
62
+ { name: 'Feb', value: randomInt(10, 100) },
63
+ { name: 'Mar', value: randomInt(10, 100) },
64
+ ],
65
+ },
66
+ {
67
+ label: '2024',
68
+ data: [
69
+ { name: 'Jan', value: randomInt(10, 100) },
70
+ { name: 'Feb', value: randomInt(10, 100) },
71
+ { name: 'Mar', value: randomInt(10, 100) },
72
+ ],
73
+ },
74
+ {
75
+ label: '2025',
76
+ data: [
77
+ { name: 'Jan', value: randomInt(10, 100) },
78
+ { name: 'Feb', value: randomInt(10, 100) },
79
+ { name: 'Mar', value: randomInt(10, 100) },
80
+ ],
81
+ },
82
+ ];
83
+ case 'bubble':
84
+ return [
85
+ {
86
+ label: '2023',
87
+ data: [
88
+ { name: 'Jan', value: randomInt(10, 100), radius: randomInt(5, 20) },
89
+ { name: 'Feb', value: randomInt(10, 100), radius: randomInt(5, 20) },
90
+ { name: 'Mar', value: randomInt(10, 100), radius: randomInt(5, 20) },
91
+ ],
92
+ },
93
+ {
94
+ label: '2024',
95
+ data: [
96
+ { name: 'Jan', value: randomInt(10, 100), radius: randomInt(5, 20) },
97
+ { name: 'Feb', value: randomInt(10, 100), radius: randomInt(5, 20) },
98
+ { name: 'Mar', value: randomInt(10, 100), radius: randomInt(5, 20) },
99
+ ],
100
+ },
101
+ {
102
+ label: '2025',
103
+ data: [
104
+ { name: 'Jan', value: randomInt(10, 100), radius: randomInt(5, 20) },
105
+ { name: 'Feb', value: randomInt(10, 100), radius: randomInt(5, 20) },
106
+ { name: 'Mar', value: randomInt(10, 100), radius: randomInt(5, 20) },
107
+ ],
108
+ },
109
+ ];
110
+ case 'steam':
111
+ return Array.from({ length: 5 }, (_, i) => ({
112
+ label: `${2021 + i}`,
113
+ data: [
114
+ { name: 'Jan', value: randomInt(10, 200) },
115
+ { name: 'Feb', value: randomInt(10, 200) },
116
+ { name: 'Mar', value: randomInt(10, 200) },
117
+ ],
118
+ }));
119
+ case 'multiple-axes':
120
+ return [
121
+ ...Array.from({ length: 3 }, (_, i) => ({
122
+ label: `${2021 + i}`,
123
+ type: 'bar',
124
+ data: [
125
+ { name: 'Jan', value: randomInt(10, 200) },
126
+ { name: 'Feb', value: randomInt(10, 200) },
127
+ { name: 'Mar', value: randomInt(10, 200) },
128
+ ],
129
+ })),
130
+ ...Array.from({ length: 2 }, (_, i) => ({
131
+ label: `${2024 + i}`,
132
+ type: 'line',
133
+ data: [
134
+ { name: 'Jan', value: randomInt(10, 200) },
135
+ { name: 'Feb', value: randomInt(10, 200) },
136
+ { name: 'Mar', value: randomInt(10, 200) },
137
+ ],
138
+ })),
139
+ ];
140
+ case 'floating-bar':
141
+ return [
142
+ {
143
+ label: 'City Porto',
144
+ data: [
145
+ { name: 'Jan', min: randomInt(-10, 10), max: randomInt(10, 30) },
146
+ { name: 'Feb', min: randomInt(-10, 10), max: randomInt(10, 30) },
147
+ { name: 'Mar', min: randomInt(-10, 10), max: randomInt(10, 30) },
148
+ { name: 'Apr', min: randomInt(-10, 10), max: randomInt(10, 30) },
149
+ { name: 'May', min: randomInt(-10, 10), max: randomInt(10, 30) },
150
+ ],
151
+ },
152
+ {
153
+ label: 'City Lisbon',
154
+ data: [
155
+ { name: 'Jan', min: randomInt(-10, 10), max: randomInt(10, 30) },
156
+ { name: 'Feb', min: randomInt(-10, 10), max: randomInt(10, 30) },
157
+ { name: 'Mar', min: randomInt(-10, 10), max: randomInt(10, 30) },
158
+ { name: 'Apr', min: randomInt(-10, 10), max: randomInt(10, 30) },
159
+ { name: 'May', min: randomInt(-10, 10), max: randomInt(10, 30) },
160
+ ],
161
+ },
162
+ {
163
+ label: 'City Faro',
164
+ data: [
165
+ { name: 'Jan', min: randomInt(-20, 20), max: randomInt(20, 150) },
166
+ { name: 'Feb', min: randomInt(-20, 20), max: randomInt(20, 150) },
167
+ { name: 'Mar', min: randomInt(-20, 20), max: randomInt(20, 150) },
168
+ { name: 'Apr', min: randomInt(-20, 20), max: randomInt(20, 150) },
169
+ { name: 'May', min: randomInt(-20, 20), max: randomInt(20, 150) },
170
+ ],
171
+ },
172
+ ];
173
+ case 'doughnut':
174
+ case 'pie':
175
+ return [
176
+ {
177
+ label: [2023],
178
+ data: [
179
+ { name: 'Jan', value: randomInt(10, 100) },
180
+ { name: 'Feb', value: randomInt(10, 100) },
181
+ { name: 'Mar', value: randomInt(10, 100) },
182
+ { name: 'Apr', value: randomInt(10, 100) },
183
+ ],
184
+ },
185
+ ];
186
+ case 'scatter':
187
+ return [
188
+ {
189
+ label: 'Group A',
190
+ data: [
191
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
192
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
193
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
194
+ ],
195
+ },
196
+ {
197
+ label: 'Group B',
198
+ data: [
199
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
200
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
201
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
202
+ ],
203
+ },
204
+ {
205
+ label: 'Group C',
206
+ data: [
207
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
208
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
209
+ { x: randomInt(1, 10), y: randomInt(1, 20) },
210
+ ],
211
+ },
212
+ ];
213
+ case 'polar':
214
+ case 'radar':
215
+ return [
216
+ {
217
+ label: [2025],
218
+
219
+ data: [
220
+ { name: 'Jan', value: randomInt(10, 50) },
221
+ { name: 'Feb', value: randomInt(10, 50) },
222
+ { name: 'Mar', value: randomInt(10, 50) },
223
+ { name: 'Apr', value: randomInt(10, 50) },
224
+ { name: 'May', value: randomInt(10, 50) },
225
+ { name: 'Jun', value: randomInt(10, 50) },
226
+ { name: 'Jul', value: randomInt(10, 50) },
227
+ { name: 'Aug', value: randomInt(10, 50) },
228
+ { name: 'Sep', value: randomInt(10, 50) },
229
+ { name: 'Oct', value: randomInt(10, 50) },
230
+ { name: 'Nov', value: randomInt(10, 50) },
231
+ { name: 'Dec', value: randomInt(10, 50) },
232
+ ],
233
+ },
234
+ ...(type === 'radar'
235
+ ? [
236
+ {
237
+ label: [2024],
238
+ data: [
239
+ { name: 'Jan', value: randomInt(10, 50) },
240
+ { name: 'Feb', value: randomInt(10, 50) },
241
+ { name: 'Mar', value: randomInt(10, 50) },
242
+ { name: 'Apr', value: randomInt(10, 50) },
243
+ { name: 'May', value: randomInt(10, 50) },
244
+ { name: 'Jun', value: randomInt(10, 50) },
245
+ { name: 'Jul', value: randomInt(10, 50) },
246
+ { name: 'Aug', value: randomInt(10, 50) },
247
+ { name: 'Sep', value: randomInt(10, 50) },
248
+ { name: 'Oct', value: randomInt(10, 50) },
249
+ { name: 'Nov', value: randomInt(10, 50) },
250
+ { name: 'Dec', value: randomInt(10, 50) },
251
+ ],
252
+ },
253
+ ]
254
+ : []),
255
+ ];
256
+ default:
257
+ return [];
258
+ }
259
+ };
260
+
261
+ // Chart definitions with random data
262
+ const [charts, setCharts] = useState<any[]>([
263
+ { id: '7', title: 'Multi-Series Multiple Axes Chart', type: 'bar', data: generateRandomData('bar') },
264
+ { id: '1', title: 'Multi-Series Line Chart', type: 'line', data: generateRandomData('line') },
265
+ { id: '2', title: 'Multi-Series Area Chart', type: 'area', data: generateRandomData('area') },
266
+ { id: '9', title: 'Multi-Series Bar-Horizontal Chart', type: 'bar-horizontal', data: generateRandomData('bar-horizontal') },
267
+ { id: '10', title: 'Multi-Series Bubble Chart', type: 'bubble', data: generateRandomData('bubble') },
268
+ { id: '11', title: 'Multi-Series Steam Chart', type: 'steam', data: generateRandomData('steam') },
269
+ { id: '12', title: 'Multi-Series Multiple Axes Chart', type: 'multiple-axes', data: generateRandomData('multiple-axes') },
270
+ { id: '16', title: 'Floating Bar Chart', type: 'floating-bar', data: generateRandomData('floating-bar') },
271
+ { id: '19', title: 'Stacked Line Chart', type: 'stacked-line', data: generateRandomData('stacked-line') },
272
+ { id: '20', title: 'Doughnut Chart', type: 'doughnut', data: generateRandomData('doughnut') },
273
+ { id: '23', title: 'Pie Chart', type: 'pie', data: generateRandomData('pie') },
274
+ { id: '24', title: 'Scatter Chart', type: 'scatter', data: generateRandomData('scatter') },
275
+ { id: '38', title: 'Polar Chart', type: 'polar', data: generateRandomData('polar') },
276
+ { id: '39', title: 'Radar Chart', type: 'radar', data: generateRandomData('radar') },
277
+ ]);
278
+ const [sizes, setSizes] = useState<Record<string, number>>({});
279
+ const dragItem = useRef<number | null>(null);
280
+ const dragOverItem = useRef<number | null>(null);
281
+
282
+ // measure container width to hide zoom on mobile
283
+ const containerRef = useRef<HTMLDivElement>(null);
284
+ const [containerWidth, setContainerWidth] = useState(0);
285
+
286
+ useEffect(() => {
287
+ if (!containerRef.current) return;
288
+ const obs = new ResizeObserver(entries => {
289
+ setContainerWidth(entries[0].contentRect.width);
290
+ });
291
+ obs.observe(containerRef.current);
292
+ return () => obs.disconnect();
293
+ }, []);
294
+
295
+ useEffect(() => {
296
+ if (containerWidth <= 600) {
297
+ setSizes(() => {
298
+ const reset: Record<string, number> = {};
299
+ charts.forEach(c => {
300
+ reset[c.id] = 1;
301
+ });
302
+ return reset;
303
+ });
304
+ }
305
+ }, [containerWidth, charts]);
306
+
307
+ const showZoom = React.useMemo(() => containerWidth > 600, [containerWidth]);
308
+
309
+ const minCardWidth = 350;
310
+ const gridGap = 16; // same value as CSS gap
311
+ const containerPadding = 20; // same as dashboardContainer padding
312
+ // calculate how many full columns fit
313
+ const innerWidth = containerWidth - containerPadding * 2;
314
+ const columns = Math.floor((innerWidth + gridGap) / (minCardWidth + gridGap));
315
+ const maxZoom = Math.max(columns, 1);
316
+
317
+ // if no charts, show placeholder
318
+ if (charts.length === 0) {
319
+ return (
320
+ <FluentProvider theme={theme}>
321
+ <div
322
+ className={css`
323
+ width: 100%;
324
+ height: 100%;
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ background-color: ${tokens.colorNeutralBackground2};
329
+ `}
330
+ >
331
+ <Text size={500} weight="semibold">
332
+ No charts to display.
333
+ </Text>
334
+ </div>
335
+ </FluentProvider>
336
+ );
337
+ }
338
+
339
+ const handleSort = () => {
340
+ if (
341
+ dragItem.current !== null &&
342
+ dragOverItem.current !== null &&
343
+ dragItem.current !== dragOverItem.current
344
+ ) {
345
+ const copy = [...charts];
346
+ const [moved] = copy.splice(dragItem.current, 1);
347
+ copy.splice(dragOverItem.current, 0, moved);
348
+ setCharts(copy);
349
+ }
350
+ dragItem.current = null;
351
+ dragOverItem.current = null;
352
+ };
353
+ const handleResize = (id: string, span: number) => {
354
+ setSizes(prev => ({ ...prev, [id]: Math.min(Math.max(span, 1), maxZoom) }));
355
+ };
356
+
357
+ return (
358
+ <IdPrefixProvider value="dashboard-grid-dnd">
359
+ <FluentProvider theme={theme} style={{height: '100%', backgroundColor: tokens.colorNeutralBackground2 }} >
360
+ <div ref={containerRef} className={dashboardContainer}>
361
+ {charts.map((chart, idx) => {
362
+ const zoomOptions = Array.from({ length: maxZoom }, (_, i) => ({
363
+ key: String(i + 1),
364
+ text: `Zoom: ${i + 1}x${i + 1}`,
365
+ selected: sizes[chart.id] === i + 1,
366
+ }));
367
+ return (
368
+ <div
369
+ key={chart.id}
370
+ draggable
371
+ onDragStart={() => (dragItem.current = idx)}
372
+ onDragEnter={() => (dragOverItem.current = idx)}
373
+ onDragOver={e => e.preventDefault()}
374
+ onDragEnd={handleSort}
375
+ style={{
376
+ gridColumnEnd: sizes[chart.id]
377
+ ? `span ${sizes[chart.id]}`
378
+ : undefined,
379
+ }}
380
+ >
381
+ <Card className={cardBody}>
382
+ <CardHeader
383
+ header={chart.title}
384
+ action={
385
+ <>
386
+ {showZoom && (
387
+ <ButtonMenu
388
+
389
+ options={zoomOptions}
390
+ onSelected={opt =>
391
+ handleResize(chart.id, Number(opt.key))
392
+ }
393
+ />
394
+ )}
395
+ </>
396
+ }
397
+ />
398
+ <div className={chartContainer}>{getChartComponent(chart, theme)}</div>
399
+ </Card>
400
+ </div>
401
+ );
402
+ })}
403
+ </div>
404
+ </FluentProvider>
405
+ </IdPrefixProvider>
406
+ );
407
+ };
408
+
409
+ export default DashboardWithGridDnD;
@@ -0,0 +1,40 @@
1
+ import { LegendContainer } from '../legendContainer/LegendContainer';
2
+ import React from 'react';
3
+ import { useResponsiveLegend } from '../../hooks/useResponsiveLegend';
4
+
5
+ export interface LegendControlProps {
6
+ data: { label: string }[];
7
+ visibleSeries: string[];
8
+ seriesColors: Record<string, string>;
9
+ toggleSeries: (label: string) => void;
10
+ }
11
+
12
+ const RenderLegend: React.FC<LegendControlProps> = ({
13
+ data,
14
+ visibleSeries,
15
+ seriesColors,
16
+ toggleSeries,
17
+ }) => {
18
+ const {
19
+ containerRef,
20
+
21
+ visibleItems,
22
+ overflowItems,
23
+ } = useResponsiveLegend(data);
24
+
25
+ return (
26
+ <LegendContainer
27
+ containerRef={containerRef}
28
+
29
+ visibleItems={visibleItems}
30
+ overflowItems={overflowItems}
31
+ items={data}
32
+ visibleLabels={visibleSeries}
33
+ toggleLabel={toggleSeries}
34
+ renderLabel={d => d.label}
35
+ getColor={d => seriesColors[d.label]}
36
+ />
37
+ );
38
+ };
39
+
40
+ export default RenderLegend;
@@ -0,0 +1,111 @@
1
+ import {
2
+ Caption1,
3
+ Card,
4
+ FluentProvider,
5
+ IdPrefixProvider,
6
+ Text,
7
+ Theme,
8
+ } from '@fluentui/react-components';
9
+ import { css, keyframes } from '@emotion/css';
10
+
11
+ import React from 'react';
12
+ import Stack from '../stack/Stack';
13
+ import { useThemeContext } from '../themeProvider/ThemeProvider';
14
+
15
+ interface TooltipItem {
16
+ dataset: {
17
+ label: string;
18
+ backgroundColor: string;
19
+ };
20
+ raw: number | null;
21
+ }
22
+
23
+ interface IRenderTooltipProps {
24
+
25
+ title: string[];
26
+ body: TooltipItem[];
27
+ }
28
+
29
+ const fadeIn = keyframes`
30
+ from { opacity: 0; transform: scale(0.95); }
31
+ to { opacity: 1; transform: scale(1); }
32
+ `;
33
+
34
+ const useStyles = (theme: Theme, isDark: boolean) => ({
35
+ tooltip: css({
36
+ backgroundColor: isDark
37
+ ? 'rgba(32, 32, 32, 0.9)'
38
+ : 'rgba(255, 255, 255, 0.9)',
39
+ border: `1px solid ${
40
+ isDark ? theme.colorNeutralStroke1 : theme.colorNeutralStroke2
41
+ }`,
42
+ boxShadow: theme.shadow4,
43
+ padding: '8px 12px',
44
+ maxWidth: 250,
45
+ borderRadius: '4px',
46
+ animation: `${fadeIn} 150ms ease-out`,
47
+ transformOrigin: 'top left',
48
+ position: 'relative',
49
+ }),
50
+ arrow: css({
51
+ position: 'absolute',
52
+ top: '-6px',
53
+ left: '10px',
54
+ width: 0,
55
+ height: 0,
56
+ borderLeft: '6px solid transparent',
57
+ borderRight: '6px solid transparent',
58
+ borderBottom: `6px solid ${
59
+ isDark ? 'rgba(32, 32, 32, 0.9)' : 'rgba(255, 255, 255, 0.9)'
60
+ }`,
61
+ }),
62
+ colorDot: css({
63
+ width: '10px',
64
+ height: '10px',
65
+ borderRadius: '50%',
66
+ marginRight: '6px',
67
+ display: 'inline-block',
68
+ verticalAlign: 'middle',
69
+ }),
70
+ });
71
+
72
+ export function RenderTooltip({ title, body }: IRenderTooltipProps) {
73
+ const { theme,themeName } = useThemeContext();
74
+ const isDark = themeName === 'dark';
75
+ const styles = useStyles(theme, isDark);
76
+
77
+ return (
78
+ <IdPrefixProvider value="chartjs-tooltip">
79
+ <FluentProvider theme={theme} style={{ zIndex: 1000 }}>
80
+ <Card className={styles.tooltip}>
81
+ <div className={styles.arrow} />
82
+ <Stack rowGap="5px">
83
+ {title.length > 0 && (
84
+ <Text block>
85
+ {title[0]}
86
+ </Text>
87
+ )}
88
+ {body.map((item, index) => (
89
+ <Stack
90
+ key={index}
91
+ direction="horizontal"
92
+ columnGap="6px"
93
+ alignItems="center"
94
+ >
95
+ <div
96
+ className={styles.colorDot}
97
+ style={{
98
+ backgroundColor: item.dataset.backgroundColor,
99
+ border: `1px solid ${item.dataset.backgroundColor}`,
100
+ }}
101
+ />
102
+ <Caption1>{item.dataset.label}:</Caption1>
103
+ <Text>{item.raw}</Text>
104
+ </Stack>
105
+ ))}
106
+ </Stack>
107
+ </Card>
108
+ </FluentProvider>
109
+ </IdPrefixProvider>
110
+ );
111
+ }