@fluentui/react-charts 9.3.12 → 9.3.14
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/CHANGELOG.md +25 -2
- package/dist/index.d.ts +859 -0
- package/lib/VegaDeclarativeChart.js +1 -0
- package/lib/VegaDeclarativeChart.js.map +1 -0
- package/lib/components/LineChart/LineChart.js +47 -7
- package/lib/components/LineChart/LineChart.js.map +1 -1
- package/lib/components/ScatterChart/ScatterChart.js +12 -12
- package/lib/components/ScatterChart/ScatterChart.js.map +1 -1
- package/lib/components/VegaDeclarativeChart/VegaDeclarativeChart.js +405 -0
- package/lib/components/VegaDeclarativeChart/VegaDeclarativeChart.js.map +1 -0
- package/lib/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.js +20 -0
- package/lib/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.js.map +1 -0
- package/lib/components/VegaDeclarativeChart/VegaLiteColorAdapter.js +415 -0
- package/lib/components/VegaDeclarativeChart/VegaLiteColorAdapter.js.map +1 -0
- package/lib/components/VegaDeclarativeChart/VegaLiteExpressionEvaluator.js +537 -0
- package/lib/components/VegaDeclarativeChart/VegaLiteExpressionEvaluator.js.map +1 -0
- package/lib/components/VegaDeclarativeChart/VegaLiteSchemaAdapter.js +3279 -0
- package/lib/components/VegaDeclarativeChart/VegaLiteSchemaAdapter.js.map +1 -0
- package/lib/components/VegaDeclarativeChart/VegaLiteTypes.js +28 -0
- package/lib/components/VegaDeclarativeChart/VegaLiteTypes.js.map +1 -0
- package/lib/components/VegaDeclarativeChart/index.js +1 -0
- package/lib/components/VegaDeclarativeChart/index.js.map +1 -0
- package/lib/components/VerticalStackedBarChart/VerticalStackedBarChart.js +5 -2
- package/lib/components/VerticalStackedBarChart/VerticalStackedBarChart.js.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/utilities/utilities.js +41 -0
- package/lib/utilities/utilities.js.map +1 -1
- package/lib-commonjs/VegaDeclarativeChart.js +6 -0
- package/lib-commonjs/VegaDeclarativeChart.js.map +1 -0
- package/lib-commonjs/components/LineChart/LineChart.js +46 -6
- package/lib-commonjs/components/LineChart/LineChart.js.map +1 -1
- package/lib-commonjs/components/ScatterChart/ScatterChart.js +11 -11
- package/lib-commonjs/components/ScatterChart/ScatterChart.js.map +1 -1
- package/lib-commonjs/components/VegaDeclarativeChart/VegaDeclarativeChart.js +274 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaDeclarativeChart.js.map +1 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.js +35 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.js.map +1 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteColorAdapter.js +412 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteColorAdapter.js.map +1 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteExpressionEvaluator.js +533 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteExpressionEvaluator.js.map +1 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteSchemaAdapter.js +3214 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteSchemaAdapter.js.map +1 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteTypes.js +31 -0
- package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteTypes.js.map +1 -0
- package/lib-commonjs/components/VegaDeclarativeChart/index.js +6 -0
- package/lib-commonjs/components/VegaDeclarativeChart/index.js.map +1 -0
- package/lib-commonjs/components/VerticalStackedBarChart/VerticalStackedBarChart.js +4 -1
- package/lib-commonjs/components/VerticalStackedBarChart/VerticalStackedBarChart.js.map +1 -1
- package/lib-commonjs/index.js +1 -0
- package/lib-commonjs/index.js.map +1 -1
- package/lib-commonjs/utilities/utilities.js +33 -0
- package/lib-commonjs/utilities/utilities.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { transformVegaLiteToLineChartProps, transformVegaLiteToVerticalBarChartProps, transformVegaLiteToVerticalStackedBarChartProps, transformVegaLiteToGroupedVerticalBarChartProps, transformVegaLiteToHorizontalBarChartProps, transformVegaLiteToAreaChartProps, transformVegaLiteToScatterChartProps, transformVegaLiteToDonutChartProps, transformVegaLiteToHeatMapChartProps, transformVegaLiteToHistogramProps, transformVegaLiteToPolarChartProps, getChartType, getMarkType, getVegaLiteLegendsProps } from './VegaLiteSchemaAdapter';
|
|
4
|
+
import { Legends } from '../Legends/index';
|
|
5
|
+
import { withResponsiveContainer } from '../ResponsiveContainer/withResponsiveContainer';
|
|
6
|
+
import { LineChart } from '../LineChart/index';
|
|
7
|
+
import { VerticalBarChart } from '../VerticalBarChart/index';
|
|
8
|
+
import { VerticalStackedBarChart } from '../VerticalStackedBarChart/index';
|
|
9
|
+
import { GroupedVerticalBarChart } from '../GroupedVerticalBarChart/index';
|
|
10
|
+
import { HorizontalBarChartWithAxis } from '../HorizontalBarChartWithAxis/index';
|
|
11
|
+
import { AreaChart } from '../AreaChart/index';
|
|
12
|
+
import { ScatterChart } from '../ScatterChart/index';
|
|
13
|
+
import { DonutChart } from '../DonutChart/index';
|
|
14
|
+
import { HeatMapChart } from '../HeatMapChart/index';
|
|
15
|
+
import { PolarChart } from '../PolarChart/index';
|
|
16
|
+
import { useIsDarkTheme, useColorMapping } from './VegaDeclarativeChartHooks';
|
|
17
|
+
/**
|
|
18
|
+
* Maximum allowed nesting depth for incoming JSON specs.
|
|
19
|
+
* Matches the Plotly adapter's MAX_DEPTH to prevent stack overflow / memory exhaustion.
|
|
20
|
+
*/ const MAX_DEPTH = 15;
|
|
21
|
+
/**
|
|
22
|
+
* Validates that a JSON value does not exceed the maximum nesting depth.
|
|
23
|
+
* Throws if the depth limit is exceeded (same behavior as Plotly's sanitizeJson).
|
|
24
|
+
*/ function validateJsonDepth(value, depth = 0) {
|
|
25
|
+
if (depth > MAX_DEPTH) {
|
|
26
|
+
throw new Error('VegaDeclarativeChart: Maximum JSON depth exceeded');
|
|
27
|
+
}
|
|
28
|
+
if (value !== null && typeof value === 'object') {
|
|
29
|
+
for (const key of Object.keys(value)){
|
|
30
|
+
validateJsonDepth(value[key], depth + 1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if spec is a horizontal concatenation
|
|
36
|
+
*/ function isHConcatSpec(spec) {
|
|
37
|
+
return !!spec.hconcat && Array.isArray(spec.hconcat) && spec.hconcat.length > 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if spec is a vertical concatenation
|
|
41
|
+
*/ function isVConcatSpec(spec) {
|
|
42
|
+
return !!spec.vconcat && Array.isArray(spec.vconcat) && spec.vconcat.length > 0;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get grid properties for concat specs
|
|
46
|
+
*/ function getVegaConcatGridProperties(spec) {
|
|
47
|
+
if (isHConcatSpec(spec)) {
|
|
48
|
+
return {
|
|
49
|
+
templateRows: 'auto',
|
|
50
|
+
templateColumns: `repeat(${spec.hconcat.length}, 1fr)`,
|
|
51
|
+
isHorizontal: true,
|
|
52
|
+
specs: spec.hconcat
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (isVConcatSpec(spec)) {
|
|
56
|
+
return {
|
|
57
|
+
templateRows: `repeat(${spec.vconcat.length}, auto)`,
|
|
58
|
+
templateColumns: '1fr',
|
|
59
|
+
isHorizontal: false,
|
|
60
|
+
specs: spec.vconcat
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
templateRows: '1fr',
|
|
65
|
+
templateColumns: '1fr',
|
|
66
|
+
isHorizontal: false,
|
|
67
|
+
specs: [
|
|
68
|
+
spec
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const ResponsiveLineChart = withResponsiveContainer(LineChart);
|
|
73
|
+
const ResponsiveVerticalBarChart = withResponsiveContainer(VerticalBarChart);
|
|
74
|
+
const ResponsiveVerticalStackedBarChart = withResponsiveContainer(VerticalStackedBarChart);
|
|
75
|
+
const ResponsiveGroupedVerticalBarChart = withResponsiveContainer(GroupedVerticalBarChart);
|
|
76
|
+
const ResponsiveHorizontalBarChartWithAxis = withResponsiveContainer(HorizontalBarChartWithAxis);
|
|
77
|
+
const ResponsiveAreaChart = withResponsiveContainer(AreaChart);
|
|
78
|
+
const ResponsiveScatterChart = withResponsiveContainer(ScatterChart);
|
|
79
|
+
const ResponsiveDonutChart = withResponsiveContainer(DonutChart);
|
|
80
|
+
const ResponsiveHeatMapChart = withResponsiveContainer(HeatMapChart);
|
|
81
|
+
const ResponsivePolarChart = withResponsiveContainer(PolarChart);
|
|
82
|
+
const vegaChartMap = {
|
|
83
|
+
line: {
|
|
84
|
+
transformer: transformVegaLiteToLineChartProps,
|
|
85
|
+
renderer: ResponsiveLineChart
|
|
86
|
+
},
|
|
87
|
+
bar: {
|
|
88
|
+
transformer: transformVegaLiteToVerticalBarChartProps,
|
|
89
|
+
renderer: ResponsiveVerticalBarChart
|
|
90
|
+
},
|
|
91
|
+
'stacked-bar': {
|
|
92
|
+
transformer: transformVegaLiteToVerticalStackedBarChartProps,
|
|
93
|
+
renderer: ResponsiveVerticalStackedBarChart
|
|
94
|
+
},
|
|
95
|
+
'grouped-bar': {
|
|
96
|
+
transformer: transformVegaLiteToGroupedVerticalBarChartProps,
|
|
97
|
+
renderer: ResponsiveGroupedVerticalBarChart
|
|
98
|
+
},
|
|
99
|
+
'horizontal-bar': {
|
|
100
|
+
transformer: transformVegaLiteToHorizontalBarChartProps,
|
|
101
|
+
renderer: ResponsiveHorizontalBarChartWithAxis
|
|
102
|
+
},
|
|
103
|
+
area: {
|
|
104
|
+
transformer: transformVegaLiteToAreaChartProps,
|
|
105
|
+
renderer: ResponsiveAreaChart
|
|
106
|
+
},
|
|
107
|
+
scatter: {
|
|
108
|
+
transformer: transformVegaLiteToScatterChartProps,
|
|
109
|
+
renderer: ResponsiveScatterChart
|
|
110
|
+
},
|
|
111
|
+
donut: {
|
|
112
|
+
transformer: transformVegaLiteToDonutChartProps,
|
|
113
|
+
renderer: ResponsiveDonutChart
|
|
114
|
+
},
|
|
115
|
+
heatmap: {
|
|
116
|
+
transformer: transformVegaLiteToHeatMapChartProps,
|
|
117
|
+
renderer: ResponsiveHeatMapChart
|
|
118
|
+
},
|
|
119
|
+
histogram: {
|
|
120
|
+
transformer: transformVegaLiteToHistogramProps,
|
|
121
|
+
renderer: ResponsiveVerticalBarChart
|
|
122
|
+
},
|
|
123
|
+
polar: {
|
|
124
|
+
transformer: transformVegaLiteToPolarChartProps,
|
|
125
|
+
renderer: ResponsivePolarChart
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Renders a single Vega-Lite chart spec
|
|
130
|
+
*/ function renderSingleChart(spec, colorMap, isDarkTheme, interactiveCommonProps) {
|
|
131
|
+
const chartType = getChartType(spec);
|
|
132
|
+
const chartConfig = vegaChartMap[chartType.type];
|
|
133
|
+
if (!chartConfig) {
|
|
134
|
+
throw new Error(`VegaDeclarativeChart: Unsupported chart type '${chartType.type}'`);
|
|
135
|
+
}
|
|
136
|
+
const { transformer, renderer: ChartRenderer } = chartConfig;
|
|
137
|
+
const chartProps = transformer(spec, colorMap, isDarkTheme);
|
|
138
|
+
// For hconcat/vconcat sub-charts, hide per-chart legends (shared legend rendered separately)
|
|
139
|
+
if (spec._hideLegend) {
|
|
140
|
+
chartProps.hideLegend = true;
|
|
141
|
+
}
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
143
|
+
return /*#__PURE__*/ React.createElement(ChartRenderer, {
|
|
144
|
+
...chartProps,
|
|
145
|
+
...interactiveCommonProps
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* VegaDeclarativeChart - Render Vega-Lite specifications with Fluent UI styling
|
|
150
|
+
*
|
|
151
|
+
* Supported chart types:
|
|
152
|
+
* - Line charts: mark: 'line' or 'point'
|
|
153
|
+
* - Area charts: mark: 'area'
|
|
154
|
+
* - Scatter charts: mark: 'point', 'circle', or 'square'
|
|
155
|
+
* - Vertical bar charts: mark: 'bar' with nominal/ordinal x-axis
|
|
156
|
+
* - Stacked bar charts: mark: 'bar' with color encoding
|
|
157
|
+
* - Grouped bar charts: mark: 'bar' with color encoding (via configuration)
|
|
158
|
+
* - Horizontal bar charts: mark: 'bar' with nominal/ordinal y-axis
|
|
159
|
+
* - Donut/Pie charts: mark: 'arc' with theta encoding
|
|
160
|
+
* - Heatmaps: mark: 'rect' with x, y, and color (quantitative) encodings
|
|
161
|
+
* - Combo charts: Layered specs with bar + line marks render as VerticalStackedBarChart with line overlays
|
|
162
|
+
*
|
|
163
|
+
* Multi-plot Support:
|
|
164
|
+
* - Horizontal concatenation (hconcat): Multiple charts side-by-side
|
|
165
|
+
* - Vertical concatenation (vconcat): Multiple charts stacked vertically
|
|
166
|
+
* - Shared data and encoding are merged from parent spec to each subplot
|
|
167
|
+
*
|
|
168
|
+
* Limitations:
|
|
169
|
+
* - Most layered specifications (multiple chart types) are not fully supported
|
|
170
|
+
* - Bar + Line combinations ARE supported and will render properly
|
|
171
|
+
* - For other composite charts, only the first layer will be rendered
|
|
172
|
+
* - Faceting and repeat operators are not yet supported
|
|
173
|
+
* - Funnel charts are not a native Vega-Lite mark type. The conversion_funnel.json example
|
|
174
|
+
* uses a horizontal bar chart (y: nominal, x: quantitative) which is the standard way to
|
|
175
|
+
* represent funnel data in Vega-Lite. For specialized funnel visualizations with tapering
|
|
176
|
+
* shapes, consider using Plotly's native funnel chart type instead.
|
|
177
|
+
*
|
|
178
|
+
* Note: Sankey, Gantt, and Gauge charts are not standard Vega-Lite marks.
|
|
179
|
+
* These specialized visualizations would require custom extensions or alternative approaches.
|
|
180
|
+
*
|
|
181
|
+
* @example Line Chart
|
|
182
|
+
* ```tsx
|
|
183
|
+
* import { VegaDeclarativeChart } from '@fluentui/react-charts';
|
|
184
|
+
*
|
|
185
|
+
* const spec = {
|
|
186
|
+
* mark: 'line',
|
|
187
|
+
* data: { values: [{ x: 1, y: 10 }, { x: 2, y: 20 }] },
|
|
188
|
+
* encoding: {
|
|
189
|
+
* x: { field: 'x', type: 'quantitative' },
|
|
190
|
+
* y: { field: 'y', type: 'quantitative' }
|
|
191
|
+
* }
|
|
192
|
+
* };
|
|
193
|
+
*
|
|
194
|
+
* <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: spec }} />
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* @example Area Chart
|
|
198
|
+
* ```tsx
|
|
199
|
+
* const areaSpec = {
|
|
200
|
+
* mark: 'area',
|
|
201
|
+
* data: { values: [{ date: '2023-01', value: 100 }, { date: '2023-02', value: 150 }] },
|
|
202
|
+
* encoding: {
|
|
203
|
+
* x: { field: 'date', type: 'temporal' },
|
|
204
|
+
* y: { field: 'value', type: 'quantitative' }
|
|
205
|
+
* }
|
|
206
|
+
* };
|
|
207
|
+
*
|
|
208
|
+
* <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: areaSpec }} />
|
|
209
|
+
* ```
|
|
210
|
+
*
|
|
211
|
+
* @example Scatter Chart
|
|
212
|
+
* ```tsx
|
|
213
|
+
* const scatterSpec = {
|
|
214
|
+
* mark: 'point',
|
|
215
|
+
* data: { values: [{ x: 10, y: 20, size: 100 }, { x: 15, y: 30, size: 200 }] },
|
|
216
|
+
* encoding: {
|
|
217
|
+
* x: { field: 'x', type: 'quantitative' },
|
|
218
|
+
* y: { field: 'y', type: 'quantitative' },
|
|
219
|
+
* size: { field: 'size', type: 'quantitative' }
|
|
220
|
+
* }
|
|
221
|
+
* };
|
|
222
|
+
*
|
|
223
|
+
* <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: scatterSpec }} />
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @example Vertical Bar Chart
|
|
227
|
+
* ```tsx
|
|
228
|
+
* const barSpec = {
|
|
229
|
+
* mark: 'bar',
|
|
230
|
+
* data: { values: [{ cat: 'A', val: 28 }, { cat: 'B', val: 55 }] },
|
|
231
|
+
* encoding: {
|
|
232
|
+
* x: { field: 'cat', type: 'nominal' },
|
|
233
|
+
* y: { field: 'val', type: 'quantitative' }
|
|
234
|
+
* }
|
|
235
|
+
* };
|
|
236
|
+
*
|
|
237
|
+
* <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: barSpec }} />
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* @example Stacked Bar Chart
|
|
241
|
+
* ```tsx
|
|
242
|
+
* const stackedSpec = {
|
|
243
|
+
* mark: 'bar',
|
|
244
|
+
* data: { values: [
|
|
245
|
+
* { cat: 'A', group: 'G1', val: 28 },
|
|
246
|
+
* { cat: 'A', group: 'G2', val: 15 }
|
|
247
|
+
* ]},
|
|
248
|
+
* encoding: {
|
|
249
|
+
* x: { field: 'cat', type: 'nominal' },
|
|
250
|
+
* y: { field: 'val', type: 'quantitative' },
|
|
251
|
+
* color: { field: 'group', type: 'nominal' }
|
|
252
|
+
* }
|
|
253
|
+
* };
|
|
254
|
+
*
|
|
255
|
+
* <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: stackedSpec }} />
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* @example Donut Chart
|
|
259
|
+
* ```tsx
|
|
260
|
+
* const donutSpec = {
|
|
261
|
+
* mark: 'arc',
|
|
262
|
+
* data: { values: [{ category: 'A', value: 30 }, { category: 'B', value: 70 }] },
|
|
263
|
+
* encoding: {
|
|
264
|
+
* theta: { field: 'value', type: 'quantitative' },
|
|
265
|
+
* color: { field: 'category', type: 'nominal' }
|
|
266
|
+
* }
|
|
267
|
+
* };
|
|
268
|
+
*
|
|
269
|
+
* <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: donutSpec }} />
|
|
270
|
+
* ```
|
|
271
|
+
*
|
|
272
|
+
* @example Heatmap
|
|
273
|
+
* ```tsx
|
|
274
|
+
* const heatmapSpec = {
|
|
275
|
+
* mark: 'rect',
|
|
276
|
+
* data: { values: [
|
|
277
|
+
* { x: 'A', y: 'Mon', value: 28 },
|
|
278
|
+
* { x: 'B', y: 'Mon', value: 55 },
|
|
279
|
+
* { x: 'A', y: 'Tue', value: 43 }
|
|
280
|
+
* ]},
|
|
281
|
+
* encoding: {
|
|
282
|
+
* x: { field: 'x', type: 'nominal' },
|
|
283
|
+
* y: { field: 'y', type: 'nominal' },
|
|
284
|
+
* color: { field: 'value', type: 'quantitative' }
|
|
285
|
+
* }
|
|
286
|
+
* };
|
|
287
|
+
*
|
|
288
|
+
* <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: heatmapSpec }} />
|
|
289
|
+
* ```
|
|
290
|
+
*/ export const VegaDeclarativeChart = /*#__PURE__*/ React.forwardRef((props, forwardedRef)=>{
|
|
291
|
+
const { vegaLiteSpec, selectedLegends = [] } = props.chartSchema;
|
|
292
|
+
if (!vegaLiteSpec) {
|
|
293
|
+
throw new Error('VegaDeclarativeChart: vegaLiteSpec is required in chartSchema');
|
|
294
|
+
}
|
|
295
|
+
// Guard against excessively deep JSON specs that could cause stack overflow / memory exhaustion
|
|
296
|
+
validateJsonDepth(vegaLiteSpec);
|
|
297
|
+
const colorMap = useColorMapping();
|
|
298
|
+
const isDarkTheme = useIsDarkTheme();
|
|
299
|
+
const chartRef = React.useRef(null);
|
|
300
|
+
const [activeLegends, setActiveLegends] = React.useState(selectedLegends);
|
|
301
|
+
const onActiveLegendsChange = (keys)=>{
|
|
302
|
+
setActiveLegends(keys);
|
|
303
|
+
if (props.onSchemaChange) {
|
|
304
|
+
props.onSchemaChange({
|
|
305
|
+
vegaLiteSpec,
|
|
306
|
+
selectedLegends: keys
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
React.useEffect(()=>{
|
|
311
|
+
var _props_chartSchema_selectedLegends;
|
|
312
|
+
setActiveLegends((_props_chartSchema_selectedLegends = props.chartSchema.selectedLegends) !== null && _props_chartSchema_selectedLegends !== void 0 ? _props_chartSchema_selectedLegends : []);
|
|
313
|
+
}, [
|
|
314
|
+
props.chartSchema.selectedLegends
|
|
315
|
+
]);
|
|
316
|
+
const multiSelectLegendProps = {
|
|
317
|
+
canSelectMultipleLegends: true,
|
|
318
|
+
onChange: onActiveLegendsChange,
|
|
319
|
+
selectedLegends: activeLegends
|
|
320
|
+
};
|
|
321
|
+
const interactiveCommonProps = {
|
|
322
|
+
componentRef: chartRef,
|
|
323
|
+
legendProps: multiSelectLegendProps
|
|
324
|
+
};
|
|
325
|
+
try {
|
|
326
|
+
// Check if this is a concat spec (multiple charts side-by-side or stacked)
|
|
327
|
+
if (isHConcatSpec(vegaLiteSpec) || isVConcatSpec(vegaLiteSpec)) {
|
|
328
|
+
const gridProps = getVegaConcatGridProperties(vegaLiteSpec);
|
|
329
|
+
// Build shared legend from the first sub-chart's color encoding
|
|
330
|
+
const firstSubSpec = {
|
|
331
|
+
...gridProps.specs[0],
|
|
332
|
+
data: gridProps.specs[0].data || vegaLiteSpec.data,
|
|
333
|
+
encoding: {
|
|
334
|
+
...vegaLiteSpec.encoding || {},
|
|
335
|
+
...gridProps.specs[0].encoding || {}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
const sharedLegendProps = getVegaLiteLegendsProps(firstSubSpec, colorMap, isDarkTheme);
|
|
339
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
340
|
+
ref: forwardedRef,
|
|
341
|
+
className: props.className,
|
|
342
|
+
style: props.style
|
|
343
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
344
|
+
style: {
|
|
345
|
+
display: 'grid',
|
|
346
|
+
gridTemplateRows: gridProps.templateRows,
|
|
347
|
+
gridTemplateColumns: gridProps.templateColumns,
|
|
348
|
+
gap: '16px'
|
|
349
|
+
}
|
|
350
|
+
}, gridProps.specs.map((subSpec, index)=>{
|
|
351
|
+
// Compute default height for sub-charts
|
|
352
|
+
const defaultSubHeight = typeof vegaLiteSpec.height === 'number' ? vegaLiteSpec.height : typeof subSpec.height === 'number' ? subSpec.height : 300;
|
|
353
|
+
const mergedSpec = {
|
|
354
|
+
...subSpec,
|
|
355
|
+
data: subSpec.data || vegaLiteSpec.data,
|
|
356
|
+
encoding: {
|
|
357
|
+
...vegaLiteSpec.encoding || {},
|
|
358
|
+
...subSpec.encoding || {}
|
|
359
|
+
},
|
|
360
|
+
height: typeof subSpec.height === 'number' ? subSpec.height : defaultSubHeight,
|
|
361
|
+
// Hide legends on ALL sub-charts — shared legend is rendered below
|
|
362
|
+
_hideLegend: true
|
|
363
|
+
};
|
|
364
|
+
const cellRow = gridProps.isHorizontal ? 1 : index + 1;
|
|
365
|
+
const cellColumn = gridProps.isHorizontal ? index + 1 : 1;
|
|
366
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
367
|
+
key: `chart_${index}`,
|
|
368
|
+
style: {
|
|
369
|
+
gridRowStart: cellRow,
|
|
370
|
+
gridRowEnd: cellRow + 1,
|
|
371
|
+
gridColumnStart: cellColumn,
|
|
372
|
+
gridColumnEnd: cellColumn + 1,
|
|
373
|
+
minWidth: 0
|
|
374
|
+
}
|
|
375
|
+
}, renderSingleChart(mergedSpec, colorMap, isDarkTheme, interactiveCommonProps));
|
|
376
|
+
})), sharedLegendProps.legends.length > 0 && /*#__PURE__*/ React.createElement(Legends, {
|
|
377
|
+
...sharedLegendProps,
|
|
378
|
+
...multiSelectLegendProps
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
// Check if this is a layered spec (composite chart)
|
|
382
|
+
if (vegaLiteSpec.layer && vegaLiteSpec.layer.length > 1) {
|
|
383
|
+
// Check if it's a supported bar+line combo
|
|
384
|
+
const marks = vegaLiteSpec.layer.map((layer)=>getMarkType(layer.mark));
|
|
385
|
+
const hasBar = marks.includes('bar');
|
|
386
|
+
const hasLine = marks.includes('line') || marks.includes('point');
|
|
387
|
+
const isBarLineCombo = hasBar && hasLine;
|
|
388
|
+
// Only warn for unsupported layered specs
|
|
389
|
+
if (!isBarLineCombo) {
|
|
390
|
+
// Layered specifications with multiple chart types are not fully supported.
|
|
391
|
+
// Only the first layer will be rendered.
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Render single chart
|
|
395
|
+
const chartComponent = renderSingleChart(vegaLiteSpec, colorMap, isDarkTheme, interactiveCommonProps);
|
|
396
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
397
|
+
ref: forwardedRef,
|
|
398
|
+
className: props.className,
|
|
399
|
+
style: props.style
|
|
400
|
+
}, chartComponent);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
throw new Error(`Failed to transform Vega-Lite spec: ${error}`);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
VegaDeclarativeChart.displayName = 'VegaDeclarativeChart';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/VegaDeclarativeChart/VegaDeclarativeChart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n transformVegaLiteToLineChartProps,\n transformVegaLiteToVerticalBarChartProps,\n transformVegaLiteToVerticalStackedBarChartProps,\n transformVegaLiteToGroupedVerticalBarChartProps,\n transformVegaLiteToHorizontalBarChartProps,\n transformVegaLiteToAreaChartProps,\n transformVegaLiteToScatterChartProps,\n transformVegaLiteToDonutChartProps,\n transformVegaLiteToHeatMapChartProps,\n transformVegaLiteToHistogramProps,\n transformVegaLiteToPolarChartProps,\n getChartType,\n getMarkType,\n getVegaLiteLegendsProps,\n} from './VegaLiteSchemaAdapter';\nimport type { VegaLiteUnitSpec, VegaLiteSpec } from './VegaLiteTypes';\nimport { Legends } from '../Legends/index';\nimport { withResponsiveContainer } from '../ResponsiveContainer/withResponsiveContainer';\nimport { LineChart } from '../LineChart/index';\nimport { VerticalBarChart } from '../VerticalBarChart/index';\nimport { VerticalStackedBarChart } from '../VerticalStackedBarChart/index';\nimport { GroupedVerticalBarChart } from '../GroupedVerticalBarChart/index';\nimport { HorizontalBarChartWithAxis } from '../HorizontalBarChartWithAxis/index';\nimport { AreaChart } from '../AreaChart/index';\nimport { ScatterChart } from '../ScatterChart/index';\nimport { DonutChart } from '../DonutChart/index';\nimport { HeatMapChart } from '../HeatMapChart/index';\nimport { PolarChart } from '../PolarChart/index';\nimport { useIsDarkTheme, useColorMapping } from './VegaDeclarativeChartHooks';\nimport type { Chart } from '../../types/index';\n\n// Re-export the typed VegaLiteSpec for public API\nexport type { VegaLiteSpec } from './VegaLiteTypes';\n\n/**\n * Maximum allowed nesting depth for incoming JSON specs.\n * Matches the Plotly adapter's MAX_DEPTH to prevent stack overflow / memory exhaustion.\n */\nconst MAX_DEPTH = 15;\n\n/**\n * Validates that a JSON value does not exceed the maximum nesting depth.\n * Throws if the depth limit is exceeded (same behavior as Plotly's sanitizeJson).\n */\nfunction validateJsonDepth(value: unknown, depth: number = 0): void {\n if (depth > MAX_DEPTH) {\n throw new Error('VegaDeclarativeChart: Maximum JSON depth exceeded');\n }\n if (value !== null && typeof value === 'object') {\n for (const key of Object.keys(value as Record<string, unknown>)) {\n validateJsonDepth((value as Record<string, unknown>)[key], depth + 1);\n }\n }\n}\n\n/**\n * Schema for VegaDeclarativeChart component\n */\nexport interface VegaSchema {\n /**\n * Vega-Lite specification\n *\n * @see https://vega.github.io/vega-lite/docs/spec.html\n */\n vegaLiteSpec: VegaLiteSpec;\n\n /**\n * Selected legends for filtering\n */\n selectedLegends?: string[];\n}\n\n/**\n * Props for VegaDeclarativeChart component\n */\nexport interface VegaDeclarativeChartProps {\n /**\n * Vega-Lite chart schema\n */\n chartSchema: VegaSchema;\n\n /**\n * Callback when schema changes (e.g., legend selection)\n */\n onSchemaChange?: (newSchema: VegaSchema) => void;\n\n /**\n * Additional CSS class name\n */\n className?: string;\n\n /**\n * Additional inline styles\n */\n style?: React.CSSProperties;\n}\n\n/**\n * Check if spec is a horizontal concatenation\n */\nfunction isHConcatSpec(spec: VegaLiteSpec): boolean {\n return !!spec.hconcat && Array.isArray(spec.hconcat) && spec.hconcat.length > 0;\n}\n\n/**\n * Check if spec is a vertical concatenation\n */\nfunction isVConcatSpec(spec: VegaLiteSpec): boolean {\n return !!spec.vconcat && Array.isArray(spec.vconcat) && spec.vconcat.length > 0;\n}\n\n/**\n * Get grid properties for concat specs\n */\nfunction getVegaConcatGridProperties(spec: VegaLiteSpec): {\n templateRows: string;\n templateColumns: string;\n isHorizontal: boolean;\n specs: VegaLiteSpec[];\n} {\n if (isHConcatSpec(spec)) {\n return {\n templateRows: 'auto',\n templateColumns: `repeat(${spec.hconcat!.length}, 1fr)`,\n isHorizontal: true,\n specs: spec.hconcat!,\n };\n }\n\n if (isVConcatSpec(spec)) {\n return {\n templateRows: `repeat(${spec.vconcat!.length}, auto)`,\n templateColumns: '1fr',\n isHorizontal: false,\n specs: spec.vconcat!,\n };\n }\n\n return {\n templateRows: '1fr',\n templateColumns: '1fr',\n isHorizontal: false,\n specs: [spec],\n };\n}\n\nconst ResponsiveLineChart = withResponsiveContainer(LineChart);\nconst ResponsiveVerticalBarChart = withResponsiveContainer(VerticalBarChart);\nconst ResponsiveVerticalStackedBarChart = withResponsiveContainer(VerticalStackedBarChart);\nconst ResponsiveGroupedVerticalBarChart = withResponsiveContainer(GroupedVerticalBarChart);\nconst ResponsiveHorizontalBarChartWithAxis = withResponsiveContainer(HorizontalBarChartWithAxis);\nconst ResponsiveAreaChart = withResponsiveContainer(AreaChart);\nconst ResponsiveScatterChart = withResponsiveContainer(ScatterChart);\nconst ResponsiveDonutChart = withResponsiveContainer(DonutChart);\nconst ResponsiveHeatMapChart = withResponsiveContainer(HeatMapChart);\nconst ResponsivePolarChart = withResponsiveContainer(PolarChart);\n\n/**\n * Chart type mapping with transformers and renderers\n * Follows the factory functor pattern from PlotlyDeclarativeChart\n */\ntype VegaChartTypeMap = {\n line: { transformer: typeof transformVegaLiteToLineChartProps; renderer: typeof ResponsiveLineChart };\n bar: { transformer: typeof transformVegaLiteToVerticalBarChartProps; renderer: typeof ResponsiveVerticalBarChart };\n 'stacked-bar': {\n transformer: typeof transformVegaLiteToVerticalStackedBarChartProps;\n renderer: typeof ResponsiveVerticalStackedBarChart;\n };\n 'grouped-bar': {\n transformer: typeof transformVegaLiteToGroupedVerticalBarChartProps;\n renderer: typeof ResponsiveGroupedVerticalBarChart;\n };\n 'horizontal-bar': {\n transformer: typeof transformVegaLiteToHorizontalBarChartProps;\n renderer: typeof ResponsiveHorizontalBarChartWithAxis;\n };\n area: { transformer: typeof transformVegaLiteToAreaChartProps; renderer: typeof ResponsiveAreaChart };\n scatter: { transformer: typeof transformVegaLiteToScatterChartProps; renderer: typeof ResponsiveScatterChart };\n donut: { transformer: typeof transformVegaLiteToDonutChartProps; renderer: typeof ResponsiveDonutChart };\n heatmap: { transformer: typeof transformVegaLiteToHeatMapChartProps; renderer: typeof ResponsiveHeatMapChart };\n histogram: { transformer: typeof transformVegaLiteToHistogramProps; renderer: typeof ResponsiveVerticalBarChart };\n polar: { transformer: typeof transformVegaLiteToPolarChartProps; renderer: typeof ResponsivePolarChart };\n};\n\nconst vegaChartMap: VegaChartTypeMap = {\n line: { transformer: transformVegaLiteToLineChartProps, renderer: ResponsiveLineChart },\n bar: { transformer: transformVegaLiteToVerticalBarChartProps, renderer: ResponsiveVerticalBarChart },\n 'stacked-bar': {\n transformer: transformVegaLiteToVerticalStackedBarChartProps,\n renderer: ResponsiveVerticalStackedBarChart,\n },\n 'grouped-bar': {\n transformer: transformVegaLiteToGroupedVerticalBarChartProps,\n renderer: ResponsiveGroupedVerticalBarChart,\n },\n 'horizontal-bar': {\n transformer: transformVegaLiteToHorizontalBarChartProps,\n renderer: ResponsiveHorizontalBarChartWithAxis,\n },\n area: { transformer: transformVegaLiteToAreaChartProps, renderer: ResponsiveAreaChart },\n scatter: { transformer: transformVegaLiteToScatterChartProps, renderer: ResponsiveScatterChart },\n donut: { transformer: transformVegaLiteToDonutChartProps, renderer: ResponsiveDonutChart },\n heatmap: { transformer: transformVegaLiteToHeatMapChartProps, renderer: ResponsiveHeatMapChart },\n histogram: { transformer: transformVegaLiteToHistogramProps, renderer: ResponsiveVerticalBarChart },\n polar: { transformer: transformVegaLiteToPolarChartProps, renderer: ResponsivePolarChart },\n};\n\ninterface MultiSelectLegendProps {\n canSelectMultipleLegends: boolean;\n onChange: (keys: string[]) => void;\n selectedLegends: string[];\n}\n\n/**\n * Renders a single Vega-Lite chart spec\n */\nfunction renderSingleChart(\n spec: VegaLiteSpec,\n colorMap: React.RefObject<Map<string, string>>,\n isDarkTheme: boolean,\n interactiveCommonProps: { componentRef: React.RefObject<Chart | null>; legendProps: MultiSelectLegendProps },\n): React.ReactElement {\n const chartType = getChartType(spec);\n const chartConfig = vegaChartMap[chartType.type];\n\n if (!chartConfig) {\n throw new Error(`VegaDeclarativeChart: Unsupported chart type '${chartType.type}'`);\n }\n\n const { transformer, renderer: ChartRenderer } = chartConfig;\n const chartProps = transformer(spec, colorMap, isDarkTheme) as Record<string, unknown>;\n\n // For hconcat/vconcat sub-charts, hide per-chart legends (shared legend rendered separately)\n if ((spec as Record<string, unknown>)._hideLegend) {\n chartProps.hideLegend = true;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return <ChartRenderer {...(chartProps as any)} {...interactiveCommonProps} />;\n}\n\n/**\n * VegaDeclarativeChart - Render Vega-Lite specifications with Fluent UI styling\n *\n * Supported chart types:\n * - Line charts: mark: 'line' or 'point'\n * - Area charts: mark: 'area'\n * - Scatter charts: mark: 'point', 'circle', or 'square'\n * - Vertical bar charts: mark: 'bar' with nominal/ordinal x-axis\n * - Stacked bar charts: mark: 'bar' with color encoding\n * - Grouped bar charts: mark: 'bar' with color encoding (via configuration)\n * - Horizontal bar charts: mark: 'bar' with nominal/ordinal y-axis\n * - Donut/Pie charts: mark: 'arc' with theta encoding\n * - Heatmaps: mark: 'rect' with x, y, and color (quantitative) encodings\n * - Combo charts: Layered specs with bar + line marks render as VerticalStackedBarChart with line overlays\n *\n * Multi-plot Support:\n * - Horizontal concatenation (hconcat): Multiple charts side-by-side\n * - Vertical concatenation (vconcat): Multiple charts stacked vertically\n * - Shared data and encoding are merged from parent spec to each subplot\n *\n * Limitations:\n * - Most layered specifications (multiple chart types) are not fully supported\n * - Bar + Line combinations ARE supported and will render properly\n * - For other composite charts, only the first layer will be rendered\n * - Faceting and repeat operators are not yet supported\n * - Funnel charts are not a native Vega-Lite mark type. The conversion_funnel.json example\n * uses a horizontal bar chart (y: nominal, x: quantitative) which is the standard way to\n * represent funnel data in Vega-Lite. For specialized funnel visualizations with tapering\n * shapes, consider using Plotly's native funnel chart type instead.\n *\n * Note: Sankey, Gantt, and Gauge charts are not standard Vega-Lite marks.\n * These specialized visualizations would require custom extensions or alternative approaches.\n *\n * @example Line Chart\n * ```tsx\n * import { VegaDeclarativeChart } from '@fluentui/react-charts';\n *\n * const spec = {\n * mark: 'line',\n * data: { values: [{ x: 1, y: 10 }, { x: 2, y: 20 }] },\n * encoding: {\n * x: { field: 'x', type: 'quantitative' },\n * y: { field: 'y', type: 'quantitative' }\n * }\n * };\n *\n * <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: spec }} />\n * ```\n *\n * @example Area Chart\n * ```tsx\n * const areaSpec = {\n * mark: 'area',\n * data: { values: [{ date: '2023-01', value: 100 }, { date: '2023-02', value: 150 }] },\n * encoding: {\n * x: { field: 'date', type: 'temporal' },\n * y: { field: 'value', type: 'quantitative' }\n * }\n * };\n *\n * <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: areaSpec }} />\n * ```\n *\n * @example Scatter Chart\n * ```tsx\n * const scatterSpec = {\n * mark: 'point',\n * data: { values: [{ x: 10, y: 20, size: 100 }, { x: 15, y: 30, size: 200 }] },\n * encoding: {\n * x: { field: 'x', type: 'quantitative' },\n * y: { field: 'y', type: 'quantitative' },\n * size: { field: 'size', type: 'quantitative' }\n * }\n * };\n *\n * <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: scatterSpec }} />\n * ```\n *\n * @example Vertical Bar Chart\n * ```tsx\n * const barSpec = {\n * mark: 'bar',\n * data: { values: [{ cat: 'A', val: 28 }, { cat: 'B', val: 55 }] },\n * encoding: {\n * x: { field: 'cat', type: 'nominal' },\n * y: { field: 'val', type: 'quantitative' }\n * }\n * };\n *\n * <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: barSpec }} />\n * ```\n *\n * @example Stacked Bar Chart\n * ```tsx\n * const stackedSpec = {\n * mark: 'bar',\n * data: { values: [\n * { cat: 'A', group: 'G1', val: 28 },\n * { cat: 'A', group: 'G2', val: 15 }\n * ]},\n * encoding: {\n * x: { field: 'cat', type: 'nominal' },\n * y: { field: 'val', type: 'quantitative' },\n * color: { field: 'group', type: 'nominal' }\n * }\n * };\n *\n * <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: stackedSpec }} />\n * ```\n *\n * @example Donut Chart\n * ```tsx\n * const donutSpec = {\n * mark: 'arc',\n * data: { values: [{ category: 'A', value: 30 }, { category: 'B', value: 70 }] },\n * encoding: {\n * theta: { field: 'value', type: 'quantitative' },\n * color: { field: 'category', type: 'nominal' }\n * }\n * };\n *\n * <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: donutSpec }} />\n * ```\n *\n * @example Heatmap\n * ```tsx\n * const heatmapSpec = {\n * mark: 'rect',\n * data: { values: [\n * { x: 'A', y: 'Mon', value: 28 },\n * { x: 'B', y: 'Mon', value: 55 },\n * { x: 'A', y: 'Tue', value: 43 }\n * ]},\n * encoding: {\n * x: { field: 'x', type: 'nominal' },\n * y: { field: 'y', type: 'nominal' },\n * color: { field: 'value', type: 'quantitative' }\n * }\n * };\n *\n * <VegaDeclarativeChart chartSchema={{ vegaLiteSpec: heatmapSpec }} />\n * ```\n */\nexport const VegaDeclarativeChart = React.forwardRef<HTMLDivElement, VegaDeclarativeChartProps>(\n (props, forwardedRef) => {\n const { vegaLiteSpec, selectedLegends = [] } = props.chartSchema;\n\n if (!vegaLiteSpec) {\n throw new Error('VegaDeclarativeChart: vegaLiteSpec is required in chartSchema');\n }\n\n // Guard against excessively deep JSON specs that could cause stack overflow / memory exhaustion\n validateJsonDepth(vegaLiteSpec);\n\n const colorMap = useColorMapping();\n const isDarkTheme = useIsDarkTheme();\n const chartRef = React.useRef<Chart>(null);\n\n const [activeLegends, setActiveLegends] = React.useState<string[]>(selectedLegends);\n\n const onActiveLegendsChange = (keys: string[]) => {\n setActiveLegends(keys);\n if (props.onSchemaChange) {\n props.onSchemaChange({ vegaLiteSpec, selectedLegends: keys });\n }\n };\n\n React.useEffect(() => {\n setActiveLegends(props.chartSchema.selectedLegends ?? []);\n }, [props.chartSchema.selectedLegends]);\n\n const multiSelectLegendProps = {\n canSelectMultipleLegends: true,\n onChange: onActiveLegendsChange,\n selectedLegends: activeLegends,\n };\n\n const interactiveCommonProps = {\n componentRef: chartRef,\n legendProps: multiSelectLegendProps,\n };\n\n try {\n // Check if this is a concat spec (multiple charts side-by-side or stacked)\n if (isHConcatSpec(vegaLiteSpec) || isVConcatSpec(vegaLiteSpec)) {\n const gridProps = getVegaConcatGridProperties(vegaLiteSpec);\n\n // Build shared legend from the first sub-chart's color encoding\n const firstSubSpec = {\n ...gridProps.specs[0],\n data: gridProps.specs[0].data || vegaLiteSpec.data,\n encoding: {\n ...(vegaLiteSpec.encoding || {}),\n ...(gridProps.specs[0].encoding || {}),\n },\n };\n const sharedLegendProps = getVegaLiteLegendsProps(firstSubSpec, colorMap, isDarkTheme);\n\n return (\n <div ref={forwardedRef} className={props.className} style={props.style}>\n <div\n style={{\n display: 'grid',\n gridTemplateRows: gridProps.templateRows,\n gridTemplateColumns: gridProps.templateColumns,\n gap: '16px',\n }}\n >\n {gridProps.specs.map((subSpec: VegaLiteSpec, index: number) => {\n // Compute default height for sub-charts\n const defaultSubHeight =\n typeof vegaLiteSpec.height === 'number'\n ? vegaLiteSpec.height\n : typeof subSpec.height === 'number'\n ? subSpec.height\n : 300;\n\n const mergedSpec = {\n ...subSpec,\n data: subSpec.data || vegaLiteSpec.data,\n encoding: {\n ...(vegaLiteSpec.encoding || {}),\n ...(subSpec.encoding || {}),\n },\n height: typeof subSpec.height === 'number' ? subSpec.height : defaultSubHeight,\n // Hide legends on ALL sub-charts — shared legend is rendered below\n _hideLegend: true,\n };\n\n const cellRow = gridProps.isHorizontal ? 1 : index + 1;\n const cellColumn = gridProps.isHorizontal ? index + 1 : 1;\n\n return (\n <div\n key={`chart_${index}`}\n style={{\n gridRowStart: cellRow,\n gridRowEnd: cellRow + 1,\n gridColumnStart: cellColumn,\n gridColumnEnd: cellColumn + 1,\n minWidth: 0,\n }}\n >\n {renderSingleChart(mergedSpec, colorMap, isDarkTheme, interactiveCommonProps)}\n </div>\n );\n })}\n </div>\n {sharedLegendProps.legends.length > 0 && <Legends {...sharedLegendProps} {...multiSelectLegendProps} />}\n </div>\n );\n }\n\n // Check if this is a layered spec (composite chart)\n if (vegaLiteSpec.layer && vegaLiteSpec.layer.length > 1) {\n // Check if it's a supported bar+line combo\n const marks = vegaLiteSpec.layer.map((layer: VegaLiteUnitSpec) => getMarkType(layer.mark));\n const hasBar = marks.includes('bar');\n const hasLine = marks.includes('line') || marks.includes('point');\n const isBarLineCombo = hasBar && hasLine;\n\n // Only warn for unsupported layered specs\n if (!isBarLineCombo) {\n // Layered specifications with multiple chart types are not fully supported.\n // Only the first layer will be rendered.\n }\n }\n\n // Render single chart\n const chartComponent = renderSingleChart(vegaLiteSpec, colorMap, isDarkTheme, interactiveCommonProps);\n\n return (\n <div ref={forwardedRef} className={props.className} style={props.style}>\n {chartComponent}\n </div>\n );\n } catch (error) {\n throw new Error(`Failed to transform Vega-Lite spec: ${error}`);\n }\n },\n);\n\nVegaDeclarativeChart.displayName = 'VegaDeclarativeChart';\n"],"names":["React","transformVegaLiteToLineChartProps","transformVegaLiteToVerticalBarChartProps","transformVegaLiteToVerticalStackedBarChartProps","transformVegaLiteToGroupedVerticalBarChartProps","transformVegaLiteToHorizontalBarChartProps","transformVegaLiteToAreaChartProps","transformVegaLiteToScatterChartProps","transformVegaLiteToDonutChartProps","transformVegaLiteToHeatMapChartProps","transformVegaLiteToHistogramProps","transformVegaLiteToPolarChartProps","getChartType","getMarkType","getVegaLiteLegendsProps","Legends","withResponsiveContainer","LineChart","VerticalBarChart","VerticalStackedBarChart","GroupedVerticalBarChart","HorizontalBarChartWithAxis","AreaChart","ScatterChart","DonutChart","HeatMapChart","PolarChart","useIsDarkTheme","useColorMapping","MAX_DEPTH","validateJsonDepth","value","depth","Error","key","Object","keys","isHConcatSpec","spec","hconcat","Array","isArray","length","isVConcatSpec","vconcat","getVegaConcatGridProperties","templateRows","templateColumns","isHorizontal","specs","ResponsiveLineChart","ResponsiveVerticalBarChart","ResponsiveVerticalStackedBarChart","ResponsiveGroupedVerticalBarChart","ResponsiveHorizontalBarChartWithAxis","ResponsiveAreaChart","ResponsiveScatterChart","ResponsiveDonutChart","ResponsiveHeatMapChart","ResponsivePolarChart","vegaChartMap","line","transformer","renderer","bar","area","scatter","donut","heatmap","histogram","polar","renderSingleChart","colorMap","isDarkTheme","interactiveCommonProps","chartType","chartConfig","type","ChartRenderer","chartProps","_hideLegend","hideLegend","VegaDeclarativeChart","forwardRef","props","forwardedRef","vegaLiteSpec","selectedLegends","chartSchema","chartRef","useRef","activeLegends","setActiveLegends","useState","onActiveLegendsChange","onSchemaChange","useEffect","multiSelectLegendProps","canSelectMultipleLegends","onChange","componentRef","legendProps","gridProps","firstSubSpec","data","encoding","sharedLegendProps","div","ref","className","style","display","gridTemplateRows","gridTemplateColumns","gap","map","subSpec","index","defaultSubHeight","height","mergedSpec","cellRow","cellColumn","gridRowStart","gridRowEnd","gridColumnStart","gridColumnEnd","minWidth","legends","layer","marks","mark","hasBar","includes","hasLine","isBarLineCombo","chartComponent","error","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ThemeContext_unstable as V9ThemeContext } from '@fluentui/react-shared-contexts';
|
|
4
|
+
import { webLightTheme } from '@fluentui/tokens';
|
|
5
|
+
import { hsl as d3Hsl } from 'd3-color';
|
|
6
|
+
/**
|
|
7
|
+
* Hook to determine if dark theme is active based on background/foreground luminance
|
|
8
|
+
*/ export function useIsDarkTheme() {
|
|
9
|
+
const parentV9Theme = React.useContext(V9ThemeContext);
|
|
10
|
+
const v9Theme = parentV9Theme ? parentV9Theme : webLightTheme;
|
|
11
|
+
const backgroundColor = d3Hsl(v9Theme.colorNeutralBackground1);
|
|
12
|
+
const foregroundColor = d3Hsl(v9Theme.colorNeutralForeground1);
|
|
13
|
+
const isDarkTheme = backgroundColor.l < foregroundColor.l;
|
|
14
|
+
return isDarkTheme;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Hook for color mapping across charts - maintains persistent color assignments
|
|
18
|
+
*/ export function useColorMapping() {
|
|
19
|
+
return React.useRef(new Map());
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { ThemeContext_unstable as V9ThemeContext } from '@fluentui/react-shared-contexts';\nimport { Theme, webLightTheme } from '@fluentui/tokens';\nimport { hsl as d3Hsl } from 'd3-color';\n\n/**\n * Hook to determine if dark theme is active based on background/foreground luminance\n */\nexport function useIsDarkTheme(): boolean {\n const parentV9Theme = React.useContext(V9ThemeContext) as Theme;\n const v9Theme: Theme = parentV9Theme ? parentV9Theme : webLightTheme;\n\n const backgroundColor = d3Hsl(v9Theme.colorNeutralBackground1);\n const foregroundColor = d3Hsl(v9Theme.colorNeutralForeground1);\n\n const isDarkTheme = backgroundColor.l < foregroundColor.l;\n\n return isDarkTheme;\n}\n\n/**\n * Hook for color mapping across charts - maintains persistent color assignments\n */\nexport function useColorMapping(): React.RefObject<Map<string, string>> {\n return React.useRef<Map<string, string>>(new Map());\n}\n"],"names":["React","ThemeContext_unstable","V9ThemeContext","webLightTheme","hsl","d3Hsl","useIsDarkTheme","parentV9Theme","useContext","v9Theme","backgroundColor","colorNeutralBackground1","foregroundColor","colorNeutralForeground1","isDarkTheme","l","useColorMapping","useRef","Map"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAC/B,SAASC,yBAAyBC,cAAc,QAAQ,kCAAkC;AAC1F,SAAgBC,aAAa,QAAQ,mBAAmB;AACxD,SAASC,OAAOC,KAAK,QAAQ,WAAW;AAExC;;CAEC,GACD,OAAO,SAASC;IACd,MAAMC,gBAAgBP,MAAMQ,UAAU,CAACN;IACvC,MAAMO,UAAiBF,gBAAgBA,gBAAgBJ;IAEvD,MAAMO,kBAAkBL,MAAMI,QAAQE,uBAAuB;IAC7D,MAAMC,kBAAkBP,MAAMI,QAAQI,uBAAuB;IAE7D,MAAMC,cAAcJ,gBAAgBK,CAAC,GAAGH,gBAAgBG,CAAC;IAEzD,OAAOD;AACT;AAEA;;CAEC,GACD,OAAO,SAASE;IACd,OAAOhB,MAAMiB,MAAM,CAAsB,IAAIC;AAC/C"}
|