@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,209 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BarElement,
|
|
3
|
-
CategoryScale,
|
|
4
|
-
Chart as ChartJS,
|
|
5
|
-
ChartOptions,
|
|
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 { Chart } from 'react-chartjs-2';
|
|
17
|
-
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
|
18
|
-
import RenderLegend from '../../components/RenderLegend/RenderLegend';
|
|
19
|
-
import { useChartUtils } from '../../hooks/useChartUtils';
|
|
20
|
-
import { useGraphGlobalStyles } from '../../graphGlobalStyles/useGraphGlobalStyles';
|
|
21
|
-
|
|
22
|
-
ChartJS.register(ChartDataLabels);
|
|
23
|
-
ChartJS.register(
|
|
24
|
-
CategoryScale,
|
|
25
|
-
LinearScale,
|
|
26
|
-
BarElement,
|
|
27
|
-
LineElement,
|
|
28
|
-
PointElement,
|
|
29
|
-
Tooltip,
|
|
30
|
-
Legend,
|
|
31
|
-
Title
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
export interface ComboChartProps<T> {
|
|
35
|
-
data: {
|
|
36
|
-
label: string;
|
|
37
|
-
type: 'bar' | 'line';
|
|
38
|
-
data: T[];
|
|
39
|
-
yAxisID?: string;
|
|
40
|
-
}[];
|
|
41
|
-
getPrimary: (datum: T) => string | number;
|
|
42
|
-
getSecondary: (datum: T) => number;
|
|
43
|
-
title?: string;
|
|
44
|
-
showDataLabels?: boolean;
|
|
45
|
-
theme?: Theme;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export default function ComboChart<T extends object>({
|
|
49
|
-
data,
|
|
50
|
-
getPrimary,
|
|
51
|
-
getSecondary,
|
|
52
|
-
title,
|
|
53
|
-
showDataLabels = false,
|
|
54
|
-
theme = webLightTheme,
|
|
55
|
-
}: ComboChartProps<T>) {
|
|
56
|
-
const [visibleSeries, setVisibleSeries] = useState(() =>
|
|
57
|
-
data.map(s => s.label)
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const styles = useGraphGlobalStyles();
|
|
61
|
-
const { lightenColor, getFluentPalette, createFluentTooltip } = useChartUtils(theme);
|
|
62
|
-
|
|
63
|
-
const seriesColors = useMemo(() => {
|
|
64
|
-
return data.reduce((acc, series, idx) => {
|
|
65
|
-
const base = getFluentPalette(theme)[
|
|
66
|
-
idx % getFluentPalette(theme).length
|
|
67
|
-
];
|
|
68
|
-
acc[series.label] = lightenColor(base, 0.3);
|
|
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[0].label] : next;
|
|
78
|
-
});
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const allCategories = useMemo(() => {
|
|
82
|
-
const set = new Set<string | number>();
|
|
83
|
-
data.forEach(series => series.data.forEach(d => set.add(getPrimary(d))));
|
|
84
|
-
return Array.from(set);
|
|
85
|
-
}, [data, getPrimary]);
|
|
86
|
-
|
|
87
|
-
const chartData = useMemo(() => {
|
|
88
|
-
const sortedSeries = data
|
|
89
|
-
.filter(series => visibleSeries.includes(series.label))
|
|
90
|
-
.sort((a, b) => {
|
|
91
|
-
// Ensure bars come before lines
|
|
92
|
-
if (a.type === 'bar' && b.type === 'line') return -1;
|
|
93
|
-
if (a.type === 'line' && b.type === 'bar') return 1;
|
|
94
|
-
return 0;
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
labels: allCategories,
|
|
99
|
-
datasets: sortedSeries.map(series => ({
|
|
100
|
-
type: series.type,
|
|
101
|
-
label: series.label,
|
|
102
|
-
yAxisID: series.yAxisID ?? 'y',
|
|
103
|
-
data: allCategories.map(cat => {
|
|
104
|
-
const found = series.data.find(d => getPrimary(d) === cat);
|
|
105
|
-
return found ? getSecondary(found) : 0;
|
|
106
|
-
}),
|
|
107
|
-
backgroundColor: seriesColors[series.label],
|
|
108
|
-
borderColor: seriesColors[series.label],
|
|
109
|
-
fill: series.type === 'bar',
|
|
110
|
-
tension: series.type === 'line' ? 0.4 : 0,
|
|
111
|
-
pointRadius: series.type === 'line' ? 3 : 0,
|
|
112
|
-
borderWidth: series.type === 'line' ? 2 : 1,
|
|
113
|
-
order: series.type === 'bar' ? 1 : 0, // Ensure bars are drawn first
|
|
114
|
-
})),
|
|
115
|
-
};
|
|
116
|
-
}, [
|
|
117
|
-
data,
|
|
118
|
-
visibleSeries,
|
|
119
|
-
allCategories,
|
|
120
|
-
getPrimary,
|
|
121
|
-
getSecondary,
|
|
122
|
-
seriesColors,
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
const { fontFamily, fontSize, labelColor, gridColor } = useMemo(() => ({
|
|
126
|
-
fontFamily: theme.fontFamilyBase,
|
|
127
|
-
fontSize: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
128
|
-
labelColor: theme.colorNeutralForeground1,
|
|
129
|
-
gridColor: theme.colorNeutralStroke2,
|
|
130
|
-
}), [theme]);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const options: ChartOptions<'bar' | 'line'> = useMemo(() => ( {
|
|
135
|
-
responsive: true,
|
|
136
|
-
maintainAspectRatio: false,
|
|
137
|
-
plugins: {
|
|
138
|
-
title: {
|
|
139
|
-
display: !!title,
|
|
140
|
-
text: title,
|
|
141
|
-
font: {
|
|
142
|
-
size: 14,
|
|
143
|
-
family: theme.fontFamilyBase,
|
|
144
|
-
weight: theme.fontWeightSemibold,
|
|
145
|
-
},
|
|
146
|
-
color: theme.colorNeutralForeground1,
|
|
147
|
-
padding: {
|
|
148
|
-
top: 20,
|
|
149
|
-
bottom: 20,
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
datalabels: {
|
|
153
|
-
display: showDataLabels,
|
|
154
|
-
color: theme.colorNeutralForeground1,
|
|
155
|
-
font: {
|
|
156
|
-
family: theme.fontFamilyBase,
|
|
157
|
-
size: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
legend: { display: false },
|
|
161
|
-
tooltip: createFluentTooltip<'bar' | 'line'>(theme),
|
|
162
|
-
},
|
|
163
|
-
scales: {
|
|
164
|
-
x: {
|
|
165
|
-
ticks: {
|
|
166
|
-
color: labelColor,
|
|
167
|
-
font: { family: fontFamily, size: fontSize },
|
|
168
|
-
},
|
|
169
|
-
grid: { color: gridColor },
|
|
170
|
-
},
|
|
171
|
-
y: {
|
|
172
|
-
position: 'left',
|
|
173
|
-
ticks: {
|
|
174
|
-
color: labelColor,
|
|
175
|
-
font: { family: fontFamily, size: fontSize },
|
|
176
|
-
},
|
|
177
|
-
grid: { color: gridColor },
|
|
178
|
-
stacked: false,
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
}), [
|
|
182
|
-
title,
|
|
183
|
-
showDataLabels,
|
|
184
|
-
theme,
|
|
185
|
-
fontFamily,
|
|
186
|
-
fontSize,
|
|
187
|
-
labelColor,
|
|
188
|
-
gridColor,
|
|
189
|
-
createFluentTooltip,
|
|
190
|
-
]);
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
<>
|
|
194
|
-
<div className={styles.chartWithLegend}>
|
|
195
|
-
<div className={styles.chartArea}>
|
|
196
|
-
<Chart type="bar" data={chartData} options={options} />
|
|
197
|
-
</div>
|
|
198
|
-
<div className={styles.legendArea}>
|
|
199
|
-
<RenderLegend
|
|
200
|
-
data={data}
|
|
201
|
-
visibleSeries={visibleSeries}
|
|
202
|
-
seriesColors={seriesColors}
|
|
203
|
-
toggleSeries={toggleSeries}
|
|
204
|
-
/>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
</>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
# ComboChart Component
|
|
2
|
-
|
|
3
|
-
A versatile combination chart component built with Chart.js and Fluent UI React. This component allows you to display multiple chart types (bars and lines) in a single visualization, making it perfect for comparing different types of metrics or showing relationships between various data series.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Mixed Chart Types**: Combine bar and line charts in a single visualization
|
|
8
|
-
- **Dual Y-Axes Support**: Optional secondary y-axis for different data scales
|
|
9
|
-
- **Multiple Series Support**: Display multiple data series with different chart types
|
|
10
|
-
- **Interactive Legend**: Toggle series visibility with click interactions
|
|
11
|
-
- **Fluent UI Integration**: Seamless integration with Fluent UI themes and design system
|
|
12
|
-
- **Data Labels**: Optional display of values directly on chart elements
|
|
13
|
-
- **Responsive Design**: Automatically adapts to container dimensions
|
|
14
|
-
- **TypeScript Support**: Full TypeScript support with generic types
|
|
15
|
-
- **Custom Tooltips**: Rich tooltips showing detailed information
|
|
16
|
-
- **Smart Rendering Order**: Bars render behind lines for optimal visibility
|
|
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 { ComboChart } from './components/ComboChart/ComboChart';
|
|
29
|
-
import { webLightTheme } from '@fluentui/react-components';
|
|
30
|
-
|
|
31
|
-
interface SalesData {
|
|
32
|
-
month: string;
|
|
33
|
-
revenue: number;
|
|
34
|
-
units: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const salesData: SalesData[] = [
|
|
38
|
-
{ month: 'Jan', revenue: 150000, units: 1200 },
|
|
39
|
-
{ month: 'Feb', revenue: 180000, units: 1450 },
|
|
40
|
-
{ month: 'Mar', revenue: 165000, units: 1320 },
|
|
41
|
-
{ month: 'Apr', revenue: 200000, units: 1600 },
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
function App() {
|
|
45
|
-
return (
|
|
46
|
-
<div style={{ width: '800px', height: '400px' }}>
|
|
47
|
-
<ComboChart
|
|
48
|
-
data={[
|
|
49
|
-
{
|
|
50
|
-
label: 'Revenue',
|
|
51
|
-
type: 'bar',
|
|
52
|
-
data: salesData
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
label: 'Units Sold',
|
|
56
|
-
type: 'line',
|
|
57
|
-
data: salesData
|
|
58
|
-
}
|
|
59
|
-
]}
|
|
60
|
-
getPrimary={(datum) => datum.month}
|
|
61
|
-
getSecondary={(datum) => datum.revenue} // Will use different values per series
|
|
62
|
-
title="Revenue and Units Sold Over Time"
|
|
63
|
-
theme={webLightTheme}
|
|
64
|
-
/>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Props
|
|
71
|
-
|
|
72
|
-
### ComboChartProps<T>
|
|
73
|
-
|
|
74
|
-
| Prop | Type | Required | Default | Description |
|
|
75
|
-
|------|------|----------|---------|-------------|
|
|
76
|
-
| `data` | `{ label: string; type: 'bar' \| 'line'; data: T[]; yAxisID?: string }[]` | Yes | - | Array of data series with labels, chart types, and optional y-axis assignment |
|
|
77
|
-
| `getPrimary` | `(datum: T) => string \| number` | Yes | - | Function to extract the x-axis category from each data point |
|
|
78
|
-
| `getSecondary` | `(datum: T) => number` | Yes | - | Function to extract the y-axis value from each data point |
|
|
79
|
-
| `title` | `string` | No | - | Chart title displayed at the top |
|
|
80
|
-
| `showDataLabels` | `boolean` | No | `false` | Whether to show data values on chart elements |
|
|
81
|
-
| `theme` | `Theme` | No | `webLightTheme` | Fluent UI theme object for styling |
|
|
82
|
-
|
|
83
|
-
## Advanced Usage
|
|
84
|
-
|
|
85
|
-
### Revenue vs Performance Metrics
|
|
86
|
-
|
|
87
|
-
```tsx
|
|
88
|
-
interface BusinessMetrics {
|
|
89
|
-
quarter: string;
|
|
90
|
-
revenue: number;
|
|
91
|
-
customerSatisfaction: number;
|
|
92
|
-
marketShare: number;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const businessData: BusinessMetrics[] = [
|
|
96
|
-
{ quarter: 'Q1 2024', revenue: 2500000, customerSatisfaction: 85, marketShare: 15.2 },
|
|
97
|
-
{ quarter: 'Q2 2024', revenue: 2800000, customerSatisfaction: 87, marketShare: 16.1 },
|
|
98
|
-
{ quarter: 'Q3 2024', revenue: 3100000, customerSatisfaction: 89, marketShare: 17.3 },
|
|
99
|
-
{ quarter: 'Q4 2024', revenue: 3400000, customerSatisfaction: 91, marketShare: 18.7 },
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
<ComboChart
|
|
103
|
-
data={[
|
|
104
|
-
{
|
|
105
|
-
label: 'Revenue ($)',
|
|
106
|
-
type: 'bar',
|
|
107
|
-
data: businessData.map(d => ({ quarter: d.quarter, value: d.revenue })),
|
|
108
|
-
yAxisID: 'y'
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
label: 'Customer Satisfaction (%)',
|
|
112
|
-
type: 'line',
|
|
113
|
-
data: businessData.map(d => ({ quarter: d.quarter, value: d.customerSatisfaction })),
|
|
114
|
-
yAxisID: 'y1'
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
label: 'Market Share (%)',
|
|
118
|
-
type: 'line',
|
|
119
|
-
data: businessData.map(d => ({ quarter: d.quarter, value: d.marketShare })),
|
|
120
|
-
yAxisID: 'y1'
|
|
121
|
-
}
|
|
122
|
-
]}
|
|
123
|
-
getPrimary={(datum) => datum.quarter}
|
|
124
|
-
getSecondary={(datum) => datum.value}
|
|
125
|
-
title="Business Performance Dashboard"
|
|
126
|
-
showDataLabels={true}
|
|
127
|
-
theme={webLightTheme}
|
|
128
|
-
/>
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Sales Performance Analysis
|
|
132
|
-
|
|
133
|
-
```tsx
|
|
134
|
-
interface SalesPerformance {
|
|
135
|
-
region: string;
|
|
136
|
-
actualSales: number;
|
|
137
|
-
targetSales: number;
|
|
138
|
-
growthRate: number;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const performanceData: SalesPerformance[] = [
|
|
142
|
-
{ region: 'North America', actualSales: 450000, targetSales: 420000, growthRate: 12.5 },
|
|
143
|
-
{ region: 'Europe', actualSales: 380000, targetSales: 400000, growthRate: 8.2 },
|
|
144
|
-
{ region: 'Asia Pacific', actualSales: 520000, targetSales: 480000, growthRate: 15.7 },
|
|
145
|
-
{ region: 'Latin America', actualSales: 280000, targetSales: 300000, growthRate: 6.3 },
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
<ComboChart
|
|
149
|
-
data={[
|
|
150
|
-
{
|
|
151
|
-
label: 'Actual Sales',
|
|
152
|
-
type: 'bar',
|
|
153
|
-
data: performanceData.map(d => ({ region: d.region, value: d.actualSales }))
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
label: 'Target Sales',
|
|
157
|
-
type: 'bar',
|
|
158
|
-
data: performanceData.map(d => ({ region: d.region, value: d.targetSales }))
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
label: 'Growth Rate (%)',
|
|
162
|
-
type: 'line',
|
|
163
|
-
data: performanceData.map(d => ({ region: d.region, value: d.growthRate })),
|
|
164
|
-
yAxisID: 'y1'
|
|
165
|
-
}
|
|
166
|
-
]}
|
|
167
|
-
getPrimary={(datum) => datum.region}
|
|
168
|
-
getSecondary={(datum) => datum.value}
|
|
169
|
-
title="Regional Sales Performance vs Growth"
|
|
170
|
-
theme={webLightTheme}
|
|
171
|
-
/>
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Financial KPIs Dashboard
|
|
175
|
-
|
|
176
|
-
```tsx
|
|
177
|
-
interface FinancialKPIs {
|
|
178
|
-
month: string;
|
|
179
|
-
revenue: number;
|
|
180
|
-
expenses: number;
|
|
181
|
-
profitMargin: number;
|
|
182
|
-
cashFlow: number;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
<ComboChart
|
|
186
|
-
data={[
|
|
187
|
-
{
|
|
188
|
-
label: 'Revenue',
|
|
189
|
-
type: 'bar',
|
|
190
|
-
data: financialData.map(d => ({ month: d.month, value: d.revenue }))
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
label: 'Expenses',
|
|
194
|
-
type: 'bar',
|
|
195
|
-
data: financialData.map(d => ({ month: d.month, value: d.expenses }))
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
label: 'Profit Margin (%)',
|
|
199
|
-
type: 'line',
|
|
200
|
-
data: financialData.map(d => ({ month: d.month, value: d.profitMargin })),
|
|
201
|
-
yAxisID: 'y1'
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
label: 'Cash Flow',
|
|
205
|
-
type: 'line',
|
|
206
|
-
data: financialData.map(d => ({ month: d.month, value: d.cashFlow }))
|
|
207
|
-
}
|
|
208
|
-
]}
|
|
209
|
-
getPrimary={(datum) => datum.month}
|
|
210
|
-
getSecondary={(datum) => datum.value}
|
|
211
|
-
title="Financial Performance Overview"
|
|
212
|
-
showDataLabels={true}
|
|
213
|
-
/>
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## Data Structure
|
|
217
|
-
|
|
218
|
-
The component expects data in the following format:
|
|
219
|
-
|
|
220
|
-
```tsx
|
|
221
|
-
interface ChartSeries<T> {
|
|
222
|
-
label: string; // Series name (appears in legend)
|
|
223
|
-
type: 'bar' | 'line'; // Chart type for this series
|
|
224
|
-
data: T[]; // Array of data points
|
|
225
|
-
yAxisID?: string; // Optional y-axis assignment ('y' or 'y1')
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Each data point `T` should contain:
|
|
230
|
-
|
|
231
|
-
- A primary value (x-axis category) - string or number
|
|
232
|
-
- A secondary value (y-axis value) - number
|
|
233
|
-
|
|
234
|
-
## Chart Type Characteristics
|
|
235
|
-
|
|
236
|
-
### Bar Series
|
|
237
|
-
|
|
238
|
-
- Rendered as vertical bars
|
|
239
|
-
- Ideal for discrete categories and comparisons
|
|
240
|
-
- Supports stacking (when all series are bars)
|
|
241
|
-
- Better for showing absolute values
|
|
242
|
-
|
|
243
|
-
### Line Series
|
|
244
|
-
|
|
245
|
-
- Rendered as connected points with lines
|
|
246
|
-
- Perfect for trends and continuous data
|
|
247
|
-
- Smooth curves with configurable tension
|
|
248
|
-
- Better for showing changes over time
|
|
249
|
-
|
|
250
|
-
## Y-Axis Configuration
|
|
251
|
-
|
|
252
|
-
### Single Y-Axis (Default)
|
|
253
|
-
|
|
254
|
-
All series share the same y-axis scale:
|
|
255
|
-
|
|
256
|
-
```tsx
|
|
257
|
-
data={[
|
|
258
|
-
{ label: 'Series 1', type: 'bar', data: data1 },
|
|
259
|
-
{ label: 'Series 2', type: 'line', data: data2 }
|
|
260
|
-
]}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### Dual Y-Axes
|
|
264
|
-
|
|
265
|
-
Different series can use different y-axis scales:
|
|
266
|
-
|
|
267
|
-
```tsx
|
|
268
|
-
data={[
|
|
269
|
-
{ label: 'Revenue ($)', type: 'bar', data: revenueData, yAxisID: 'y' },
|
|
270
|
-
{ label: 'Growth (%)', type: 'line', data: growthData, yAxisID: 'y1' }
|
|
271
|
-
]}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
## Rendering Order
|
|
275
|
-
|
|
276
|
-
The component automatically optimizes rendering order:
|
|
277
|
-
|
|
278
|
-
1. **Bar series** are rendered first (background layer)
|
|
279
|
-
2. **Line series** are rendered on top (foreground layer)
|
|
280
|
-
3. This ensures lines are always visible over bars
|
|
281
|
-
|
|
282
|
-
## Interactive Features
|
|
283
|
-
|
|
284
|
-
### Legend Controls
|
|
285
|
-
|
|
286
|
-
- Click legend items to show/hide series
|
|
287
|
-
- Visual feedback on hover states
|
|
288
|
-
- At least one series must remain visible
|
|
289
|
-
- Colors automatically assigned from theme palette
|
|
290
|
-
|
|
291
|
-
### Smart Data Handling
|
|
292
|
-
|
|
293
|
-
- Automatic category aggregation across all series
|
|
294
|
-
- Missing data points default to 0
|
|
295
|
-
- Efficient data transformation and caching
|
|
296
|
-
|
|
297
|
-
### Responsive Behavior
|
|
298
|
-
|
|
299
|
-
- Chart automatically resizes to container dimensions
|
|
300
|
-
- Legend adapts to available space
|
|
301
|
-
- Maintains readability across different screen sizes
|
|
302
|
-
|
|
303
|
-
## Styling and Theme Integration
|
|
304
|
-
|
|
305
|
-
The component uses Fluent UI theme tokens:
|
|
306
|
-
|
|
307
|
-
```tsx
|
|
308
|
-
// Bar styling
|
|
309
|
-
backgroundColor: Theme color palette
|
|
310
|
-
borderWidth: 1
|
|
311
|
-
|
|
312
|
-
// Line styling
|
|
313
|
-
borderColor: Theme color palette
|
|
314
|
-
borderWidth: 2
|
|
315
|
-
tension: 0.4 (smooth curves)
|
|
316
|
-
pointRadius: 3
|
|
317
|
-
|
|
318
|
-
// Typography
|
|
319
|
-
fontFamily: theme.fontFamilyBase
|
|
320
|
-
fontSize: theme.fontSizeBase200
|
|
321
|
-
color: theme.colorNeutralForeground1
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
## Performance Optimizations
|
|
325
|
-
|
|
326
|
-
````tsx
|
|
327
|
-
// Memoized color calculations
|
|
328
|
-
const seriesColors = useMemo(() => {
|
|
329
|
-
return data.reduce((acc, series, idx) => {
|
|
330
|
-
const base = getFluentPalette(theme)[idx % getFluentPalette(theme).length];
|
|
331
|
-
acc[series.label] = lightenColor(base, 0.3);
|
|
332
|
-
return acc;
|
|
333
|
-
}, {} as Record<string, string>);
|
|
334
|
-
}, [data, theme]);
|
|
335
|
-
|
|
336
|
-
// Memoized chart data with optimized sorting
|
|
337
|
-
const chartData = useMemo(() => {
|
|
338
|
-
const sortedSeries = data
|
|
339
|
-
.filter(series => visibleSeries.includes(series.label))
|
|
340
|
-
.sort((a, b) => {
|
|
341
|
-
// Ensure bars come before lines
|
|
342
|
-
if (a.type === 'bar' && b.type === 'line') return -1;
|
|
343
|
-
if (a.type === 'line' && b.type === 'bar') return 1;
|
|
344
|
-
return 0;
|
|
345
|
-
});
|
|
346
|
-
// ... rest of transformation
|
|
347
|
-
}, [data, visibleSeries, allCategories, getPrimary, getSecondary, seriesColors]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './ComboChart';
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArcElement,
|
|
3
|
-
Chart as ChartJS,
|
|
4
|
-
ChartOptions,
|
|
5
|
-
Legend,
|
|
6
|
-
Title,
|
|
7
|
-
Tooltip,
|
|
8
|
-
} from 'chart.js';
|
|
9
|
-
import React, { useMemo, useState } from 'react';
|
|
10
|
-
import { Theme, webLightTheme } from '@fluentui/react-components';
|
|
11
|
-
|
|
12
|
-
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
|
13
|
-
import { Doughnut } from 'react-chartjs-2';
|
|
14
|
-
import RenderValueLegend from '../../components/renderValueLegend/RenderValueLegend';
|
|
15
|
-
import { useChartUtils } from '../../hooks/useChartUtils';
|
|
16
|
-
import { useGraphGlobalStyles } from '../../graphGlobalStyles/useGraphGlobalStyles';
|
|
17
|
-
|
|
18
|
-
ChartJS.register(ChartDataLabels);
|
|
19
|
-
ChartJS.register(ArcElement, Tooltip, Legend, Title);
|
|
20
|
-
|
|
21
|
-
export interface DoughnutChartProps<T> {
|
|
22
|
-
data: {
|
|
23
|
-
label: string;
|
|
24
|
-
data: T[];
|
|
25
|
-
}[];
|
|
26
|
-
getLabel: (datum: T) => string;
|
|
27
|
-
getValue: (datum: T) => number;
|
|
28
|
-
title?: string;
|
|
29
|
-
showDataLabels?: boolean;
|
|
30
|
-
theme?: Theme;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default function DoughnutChart<T extends object>({
|
|
34
|
-
data,
|
|
35
|
-
getLabel,
|
|
36
|
-
getValue,
|
|
37
|
-
title,
|
|
38
|
-
showDataLabels = true,
|
|
39
|
-
theme = webLightTheme,
|
|
40
|
-
}: DoughnutChartProps<T>) {
|
|
41
|
-
const styles = useGraphGlobalStyles();
|
|
42
|
-
const { lightenColor, getFluentPalette, createFluentTooltip } = useChartUtils(
|
|
43
|
-
theme
|
|
44
|
-
);
|
|
45
|
-
const [hiddenLabels, setHiddenLabels] = useState<string[]>([]);
|
|
46
|
-
|
|
47
|
-
const toggleLabel = (label: string): void => {
|
|
48
|
-
setHiddenLabels(prev =>
|
|
49
|
-
prev.includes(label) ? prev.filter(l => l !== label) : [...prev, label]
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const valueMap = useMemo(() => {
|
|
54
|
-
const map = new Map<string, number>();
|
|
55
|
-
data.forEach(series => {
|
|
56
|
-
series.data.forEach(d => {
|
|
57
|
-
const label = getLabel(d);
|
|
58
|
-
const value = getValue(d);
|
|
59
|
-
map.set(label, (map.get(label) || 0) + value);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
return map;
|
|
63
|
-
}, [data, getLabel, getValue]);
|
|
64
|
-
|
|
65
|
-
const { allLabels, colors } = useMemo(() => {
|
|
66
|
-
const allLabels = Array.from(valueMap.keys());
|
|
67
|
-
const palette = getFluentPalette(theme);
|
|
68
|
-
const colors = allLabels.map((_, i) =>
|
|
69
|
-
lightenColor(palette[i % palette.length], 0.3)
|
|
70
|
-
);
|
|
71
|
-
return { allLabels, colors };
|
|
72
|
-
}, [valueMap, getFluentPalette, theme, lightenColor]);
|
|
73
|
-
|
|
74
|
-
const { filteredLabels, values, visibleColors } = useMemo(() => {
|
|
75
|
-
const filteredLabels = allLabels.filter(label => !hiddenLabels.includes(label));
|
|
76
|
-
const values = filteredLabels.map(label => valueMap.get(label) || 0);
|
|
77
|
-
const visibleColors = filteredLabels.map(label => {
|
|
78
|
-
const idx = allLabels.indexOf(label);
|
|
79
|
-
return colors[idx];
|
|
80
|
-
});
|
|
81
|
-
return { filteredLabels, values, visibleColors };
|
|
82
|
-
}, [allLabels, hiddenLabels, valueMap, colors]);
|
|
83
|
-
|
|
84
|
-
const chartData = useMemo(() => ({
|
|
85
|
-
labels: filteredLabels,
|
|
86
|
-
datasets: [
|
|
87
|
-
{
|
|
88
|
-
data: values,
|
|
89
|
-
backgroundColor: visibleColors,
|
|
90
|
-
borderWidth: 1,
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
}), [filteredLabels, values, visibleColors]);
|
|
94
|
-
|
|
95
|
-
const legendEntries = useMemo(() => {
|
|
96
|
-
return allLabels.map((label, idx) => ({
|
|
97
|
-
label,
|
|
98
|
-
value: valueMap.get(label) || 0,
|
|
99
|
-
color: colors[idx],
|
|
100
|
-
}));
|
|
101
|
-
}, [allLabels, valueMap, colors]);
|
|
102
|
-
|
|
103
|
-
const options: ChartOptions<'doughnut'> = useMemo(
|
|
104
|
-
() => ({
|
|
105
|
-
responsive: true,
|
|
106
|
-
maintainAspectRatio: false,
|
|
107
|
-
plugins: {
|
|
108
|
-
legend: { display: false },
|
|
109
|
-
tooltip: createFluentTooltip<'doughnut'>(theme),
|
|
110
|
-
title: {
|
|
111
|
-
display: !!title,
|
|
112
|
-
text: title,
|
|
113
|
-
font: {
|
|
114
|
-
size: 14,
|
|
115
|
-
family: theme.fontFamilyBase,
|
|
116
|
-
weight: theme.fontWeightSemibold,
|
|
117
|
-
},
|
|
118
|
-
color: theme.colorNeutralForeground1,
|
|
119
|
-
padding: {
|
|
120
|
-
top: 20,
|
|
121
|
-
bottom: 20,
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
datalabels: {
|
|
125
|
-
display: showDataLabels,
|
|
126
|
-
color: theme.colorNeutralForeground1,
|
|
127
|
-
font: {
|
|
128
|
-
family: theme.fontFamilyBase,
|
|
129
|
-
size: parseInt(theme.fontSizeBase200.replace('px', '')) || 14,
|
|
130
|
-
},
|
|
131
|
-
formatter: (value: number) => value,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
}),
|
|
135
|
-
[title, theme, showDataLabels, createFluentTooltip]
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<div className={styles.chartWithLegend}>
|
|
140
|
-
<div className={styles.chartArea}>
|
|
141
|
-
<Doughnut data={chartData} options={options} />
|
|
142
|
-
</div>
|
|
143
|
-
<div className={styles.legendArea}>
|
|
144
|
-
<RenderValueLegend
|
|
145
|
-
entries={legendEntries}
|
|
146
|
-
visibleLabels={filteredLabels}
|
|
147
|
-
toggleLabel={toggleLabel}
|
|
148
|
-
/>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
}
|