Skip to content

Commit f81fca9

Browse files
authored
fix(react-charting): Use schema colors for all bar charts - VBC, VSBC, GVBC, HBC (#34447)
1 parent c67a7fa commit f81fca9

File tree

8 files changed

+158
-65
lines changed

8 files changed

+158
-65
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix(react-charting): Use schema colors for all bar charts",
4+
"packageName": "@fluentui/react-charting",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/charts/react-charting/etc/react-charting.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ export const DeclarativeChart: React_2.FunctionComponent<DeclarativeChartProps>;
128128
export interface DeclarativeChartProps extends React_2.RefAttributes<HTMLDivElement> {
129129
chartSchema: Schema;
130130
componentRef?: IRefObject<IDeclarativeChart>;
131+
fluentDataVizColorPalette?: 'default' | 'builtin' | 'override';
131132
onSchemaChange?: (eventData: Schema) => void;
132-
useFluentVizColorPalette?: boolean;
133133
}
134134

135135
// @public

packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,13 @@ export interface DeclarativeChartProps extends React.RefAttributes<HTMLDivElemen
8585
componentRef?: IRefObject<IDeclarativeChart>;
8686

8787
/**
88-
* Optional prop to use Fluent UI color palette for the chart or not.
89-
* @default true
88+
* Optional prop to specify the color palette for the chart.
89+
* - 'default': Do not use Fluent UI color palette.
90+
* - 'builtin': Use Fluent UI color palette.
91+
* - 'override': Reserved for future use.
92+
* @default 'builtin'
9093
*/
91-
useFluentVizColorPalette?: boolean;
94+
fluentDataVizColorPalette?: 'default' | 'builtin' | 'override';
9295
}
9396

9497
/**
@@ -165,14 +168,16 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
165168
calloutProps: { layerProps: { eventBubblingEnabled: true } },
166169
};
167170

171+
const useFluentVizColorPalette = props.fluentDataVizColorPalette === 'builtin';
172+
168173
const renderLineArea = (plotlyData: Data[], isAreaChart: boolean): JSX.Element => {
169174
const isScatterMarkers = ['markers', 'text+markers', 'markers+text'].includes((plotlyData[0] as PlotData)?.mode);
170175
const chartProps: ILineChartProps | IAreaChartProps = {
171176
...transformPlotlyJsonToScatterChartProps(
172177
{ data: plotlyData, layout: plotlyInput.layout },
173178
isAreaChart,
174179
colorMap,
175-
props.useFluentVizColorPalette!,
180+
useFluentVizColorPalette!,
176181
isDarkTheme,
177182
),
178183
...commonProps,
@@ -212,7 +217,13 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
212217
}
213218
return (
214219
<ResponsiveVerticalStackedBarChart
215-
{...transformPlotlyJsonToVSBCProps(plotlyInputWithValidData, colorMap, isDarkTheme, fallbackVSBC)}
220+
{...transformPlotlyJsonToVSBCProps(
221+
plotlyInputWithValidData,
222+
colorMap,
223+
useFluentVizColorPalette,
224+
isDarkTheme,
225+
fallbackVSBC,
226+
)}
216227
{...commonProps}
217228
/>
218229
);
@@ -253,7 +264,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
253264
{...transformPlotlyJsonToDonutProps(
254265
plotlyInputWithValidData,
255266
colorMap,
256-
props.useFluentVizColorPalette!,
267+
useFluentVizColorPalette,
257268
isDarkTheme,
258269
)}
259270
{...commonProps}
@@ -262,21 +273,26 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
262273
case 'horizontalbar':
263274
return (
264275
<ResponsiveHorizontalBarChartWithAxis
265-
{...transformPlotlyJsonToHorizontalBarWithAxisProps(plotlyInputWithValidData, colorMap, isDarkTheme)}
276+
{...transformPlotlyJsonToHorizontalBarWithAxisProps(
277+
plotlyInputWithValidData,
278+
colorMap,
279+
useFluentVizColorPalette,
280+
isDarkTheme,
281+
)}
266282
{...commonProps}
267283
/>
268284
);
269285
case 'groupedverticalbar':
270286
return (
271287
<ResponsiveGroupedVerticalBarChart
272-
{...transformPlotlyJsonToGVBCProps(plotlyInputWithValidData, colorMap, isDarkTheme)}
288+
{...transformPlotlyJsonToGVBCProps(plotlyInputWithValidData, colorMap, useFluentVizColorPalette, isDarkTheme)}
273289
{...commonProps}
274290
/>
275291
);
276292
case 'verticalstackedbar':
277293
return (
278294
<ResponsiveVerticalStackedBarChart
279-
{...transformPlotlyJsonToVSBCProps(plotlyInputWithValidData, colorMap, isDarkTheme)}
295+
{...transformPlotlyJsonToVSBCProps(plotlyInputWithValidData, colorMap, useFluentVizColorPalette, isDarkTheme)}
280296
{...commonProps}
281297
/>
282298
);
@@ -305,7 +321,7 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
305321
case 'verticalbar':
306322
return (
307323
<ResponsiveVerticalBarChart
308-
{...transformPlotlyJsonToVBCProps(plotlyInputWithValidData, colorMap, isDarkTheme)}
324+
{...transformPlotlyJsonToVBCProps(plotlyInputWithValidData, colorMap, useFluentVizColorPalette, isDarkTheme)}
309325
{...commonProps}
310326
/>
311327
);
@@ -328,5 +344,5 @@ export const DeclarativeChart: React.FunctionComponent<DeclarativeChartProps> =
328344
});
329345
DeclarativeChart.displayName = 'DeclarativeChart';
330346
DeclarativeChart.defaultProps = {
331-
useFluentVizColorPalette: true,
347+
fluentDataVizColorPalette: 'builtin',
332348
};

packages/charts/react-charting/src/components/DeclarativeChart/PlotlySchemaAdapter.ts

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ const getSecondaryYAxisValues = (
222222
};
223223

224224
export const getSchemaColors = (
225-
colors: PieColors | Color | null | undefined,
225+
colors: PieColors | Color | Color[] | string | null | undefined,
226226
colorMap: React.MutableRefObject<Map<string, string>>,
227227
isDarkTheme?: boolean,
228228
): string[] | string | undefined => {
@@ -248,28 +248,52 @@ export const getSchemaColors = (
248248
return hexColors;
249249
};
250250

251+
export const _extractColor = (
252+
useFluentVizColorPalette: boolean,
253+
colors: PieColors | Color | Color[] | string | null | undefined,
254+
colorMap: React.MutableRefObject<Map<string, string>>,
255+
isDarkTheme?: boolean,
256+
): string | string[] | undefined => {
257+
return !useFluentVizColorPalette && colors ? getSchemaColors(colors, colorMap, isDarkTheme) : undefined;
258+
};
259+
260+
export const _resolveColor = (
261+
extractedColors: string[] | string | null | undefined,
262+
index: number,
263+
legend: string,
264+
colorMap: React.MutableRefObject<Map<string, string>>,
265+
isDarkTheme?: boolean,
266+
): string => {
267+
let color = '';
268+
if (extractedColors && isArrayOrTypedArray(extractedColors) && extractedColors[index]) {
269+
color = extractedColors[index % extractedColors.length];
270+
} else if (typeof extractedColors === 'string') {
271+
color = extractedColors;
272+
} else {
273+
color = getColor(legend, colorMap, isDarkTheme);
274+
}
275+
return color;
276+
};
277+
251278
export const transformPlotlyJsonToDonutProps = (
252279
input: PlotlySchema,
253280
colorMap: React.MutableRefObject<Map<string, string>>,
254281
useFluentVizColorPalette: boolean,
255282
isDarkTheme?: boolean,
256283
): IDonutChartProps => {
257284
const firstData = input.data[0] as PieData;
258-
let colors: string[] | string | null | undefined = undefined;
259-
if (!useFluentVizColorPalette) {
260-
colors = firstData.marker?.colors ? getSchemaColors(firstData?.marker?.colors, colorMap, isDarkTheme) : undefined;
261-
}
285+
// extract colors for each series only once
286+
const colors: string[] | string | null | undefined = _extractColor(
287+
useFluentVizColorPalette,
288+
firstData?.marker?.colors,
289+
colorMap,
290+
isDarkTheme,
291+
);
262292

263293
const mapLegendToDataPoint: Record<string, IChartDataPoint> = {};
264294
firstData.labels?.forEach((label: string, index: number) => {
265-
let color: string = '';
266-
if (colors && isStringArray(colors)) {
267-
color = colors[index % colors.length];
268-
} else if (typeof colors === 'string') {
269-
color = colors;
270-
} else {
271-
color = getColor(label, colorMap, isDarkTheme);
272-
}
295+
// resolve color for each legend from the extracted colors
296+
const color: string = _resolveColor(colors, index, label, colorMap, isDarkTheme);
273297
//ToDo how to handle string data?
274298
const value = typeof firstData.values?.[index] === 'number' ? (firstData.values[index] as number) : 1;
275299

@@ -313,6 +337,7 @@ export const transformPlotlyJsonToDonutProps = (
313337
export const transformPlotlyJsonToVSBCProps = (
314338
input: PlotlySchema,
315339
colorMap: React.MutableRefObject<Map<string, string>>,
340+
useFluentVizColorPalette: boolean,
316341
isDarkTheme?: boolean,
317342
fallbackVSBC?: boolean,
318343
): IVerticalStackedBarChartProps => {
@@ -322,22 +347,33 @@ export const transformPlotlyJsonToVSBCProps = (
322347
const { legends, hideLegend } = getLegendProps(input.data, input.layout);
323348
input.data.forEach((series: PlotData, index1: number) => {
324349
const isXYearCategory = isYearArray(series.x); // Consider year as categorical not numeric continuous axis
350+
// extract bar colors for each series only once
351+
const extractedBarColors = _extractColor(useFluentVizColorPalette, series.marker?.color, colorMap, isDarkTheme) as
352+
| string[]
353+
| string
354+
| undefined;
355+
// extract line colors for each series only once
356+
const extractedLineColors = _extractColor(useFluentVizColorPalette, series.line?.color, colorMap, isDarkTheme) as
357+
| string[]
358+
| string
359+
| undefined;
325360
(series.x as Datum[])?.forEach((x: string | number, index2: number) => {
326361
if (!mapXToDataPoints[x]) {
327362
mapXToDataPoints[x] = { xAxisPoint: isXYearCategory ? x.toString() : x, chartData: [], lineData: [] };
328363
}
329364
const legend: string = legends[index1];
365+
// resolve color for each legend's bars from the extracted colors
366+
const color = _resolveColor(extractedBarColors, index1, legend, colorMap, isDarkTheme);
330367
const yVal: number = (series.y?.[index2] as number) ?? 0;
331368
if (series.type === 'bar') {
332-
const color = getColor(legend, colorMap, isDarkTheme);
333369
mapXToDataPoints[x].chartData.push({
334370
legend,
335371
data: yVal,
336372
color,
337373
});
338374
yMaxValue = Math.max(yMaxValue, yVal);
339375
} else if (series.type === 'scatter' || !!fallbackVSBC) {
340-
const color = getColor(legend, colorMap, isDarkTheme);
376+
const lineColor = _resolveColor(extractedLineColors, index1, legend, colorMap, isDarkTheme);
341377
const lineOptions = getLineOptions(series.line);
342378
const dashType = series.line?.dash || 'solid';
343379
const legendShape =
@@ -346,7 +382,7 @@ export const transformPlotlyJsonToVSBCProps = (
346382
legend,
347383
legendShape,
348384
y: yVal,
349-
color,
385+
color: lineColor,
350386
...(lineOptions ? { lineOptions } : {}),
351387
useSecondaryYScale: usesSecondaryYScale(series),
352388
});
@@ -378,22 +414,28 @@ export const transformPlotlyJsonToVSBCProps = (
378414
export const transformPlotlyJsonToGVBCProps = (
379415
input: PlotlySchema,
380416
colorMap: React.MutableRefObject<Map<string, string>>,
417+
useFluentVizColorPalette: boolean,
381418
isDarkTheme?: boolean,
382419
): IGroupedVerticalBarChartProps => {
383420
const mapXToDataPoints: Record<string, IGroupedVerticalBarChartData> = {};
384421
const secondaryYAxisValues = getSecondaryYAxisValues(input.data, input.layout, 0, 0);
385422
const { legends, hideLegend } = getLegendProps(input.data, input.layout);
386423

387424
input.data.forEach((series: PlotData, index1: number) => {
425+
// extract colors for each series only once
426+
const extractedColors = _extractColor(useFluentVizColorPalette, series.marker?.color, colorMap, isDarkTheme) as
427+
| string[]
428+
| string
429+
| undefined;
388430
(series.x as Datum[])?.forEach((x: string | number, xIndex: number) => {
389431
if (!mapXToDataPoints[x]) {
390432
mapXToDataPoints[x] = { name: x.toString(), series: [] };
391433
}
392434

393435
if (series.type === 'bar') {
394436
const legend: string = legends[index1];
395-
const color = getColor(legend, colorMap, isDarkTheme);
396-
437+
// resolve color for each legend's bars from the extracted colors
438+
const color = _resolveColor(extractedColors, index1, legend, colorMap, isDarkTheme);
397439
mapXToDataPoints[x].series.push({
398440
key: legend,
399441
data: (series.y?.[xIndex] as number) ?? 0,
@@ -426,6 +468,7 @@ export const transformPlotlyJsonToGVBCProps = (
426468
export const transformPlotlyJsonToVBCProps = (
427469
input: PlotlySchema,
428470
colorMap: React.MutableRefObject<Map<string, string>>,
471+
useFluentVizColorPalette: boolean,
429472
isDarkTheme?: boolean,
430473
): IVerticalBarChartProps => {
431474
const vbcData: IVerticalBarChartDataPoint[] = [];
@@ -435,7 +478,11 @@ export const transformPlotlyJsonToVBCProps = (
435478
if (!series.x) {
436479
return;
437480
}
438-
481+
// extract colors for each series only once
482+
const extractedColors = _extractColor(useFluentVizColorPalette, series.marker?.color, colorMap, isDarkTheme) as
483+
| string[]
484+
| string
485+
| undefined;
439486
const isXString = isStringArray(series.x);
440487
// TODO: In case of a single bin, add an empty bin of the same size to prevent the
441488
// default bar width from being used and ensure the bar spans the full intended range.
@@ -458,7 +505,8 @@ export const transformPlotlyJsonToVBCProps = (
458505

459506
xBins.forEach((bin, index) => {
460507
const legend: string = legends[seriesIdx];
461-
const color: string = getColor(legend, colorMap, isDarkTheme);
508+
// resolve color for each legend's bars from the extracted colors
509+
const color = _resolveColor(extractedColors, seriesIdx, legend, colorMap, isDarkTheme);
462510
const yVal = calculateHistNorm(
463511
series.histnorm,
464512
y[index],
@@ -511,15 +559,18 @@ export const transformPlotlyJsonToScatterChartProps = (
511559
let mode: string = 'tonexty';
512560
const { legends, hideLegend } = getLegendProps(input.data, input.layout);
513561
const chartData: ILineChartPoints[] = input.data.map((series: PlotData, index: number) => {
562+
// extract colors for each series only once
563+
const extractedColors = _extractColor(useFluentVizColorPalette, series.line?.color, colorMap, isDarkTheme) as
564+
| string[]
565+
| string
566+
| undefined;
514567
const xValues = series.x as Datum[];
515568
const isString = typeof xValues[0] === 'string';
516569
const isXDate = isDateArray(xValues);
517570
const isXNumber = isNumberArray(xValues);
518571
const legend: string = legends[index];
519-
const lineColor =
520-
!useFluentVizColorPalette && series.line?.color
521-
? getSchemaColors(series.line?.color, colorMap, isDarkTheme)
522-
: getColor(legend, colorMap, isDarkTheme);
572+
// resolve color for each legend's lines from the extracted colors
573+
const lineColor = _resolveColor(extractedColors, index, legend, colorMap, isDarkTheme);
523574
mode = series.fill === 'tozeroy' ? 'tozeroy' : 'tonexty';
524575
const lineOptions = getLineOptions(series.line);
525576
const dashType = series.line?.dash || 'solid';
@@ -588,13 +639,20 @@ export const transformPlotlyJsonToScatterChartProps = (
588639
export const transformPlotlyJsonToHorizontalBarWithAxisProps = (
589640
input: PlotlySchema,
590641
colorMap: React.MutableRefObject<Map<string, string>>,
642+
useFluentVizColorPalette: boolean,
591643
isDarkTheme?: boolean,
592644
): IHorizontalBarChartWithAxisProps => {
593645
const { legends, hideLegend } = getLegendProps(input.data, input.layout);
594646
const chartData: IHorizontalBarChartWithAxisDataPoint[] = input.data
595647
.map((series: PlotData, index: number) => {
648+
// extract colors for each series only once
649+
const extractedColors = _extractColor(useFluentVizColorPalette, series.marker?.color, colorMap, isDarkTheme) as
650+
| string[]
651+
| string
652+
| undefined;
596653
const legend = legends[index];
597-
const color = getColor(legend, colorMap, isDarkTheme);
654+
// resolve color for each legend's bars from the extracted colors
655+
const color = _resolveColor(extractedColors, index, legend, colorMap, isDarkTheme);
598656
return (series.y as Datum[]).map((yValue: string, i: number) => {
599657
return {
600658
x: series.x[i],
@@ -768,7 +826,6 @@ export const transformPlotlyJsonToSankeyProps = (
768826
source: link?.source![index],
769827
target: link?.target![index],
770828
}))
771-
// eslint-disable-next-line @typescript-eslint/no-shadow
772829
// Filter out negative nodes, unequal nodes and self-references (circular links)
773830
.filter(x => x.source >= 0 && x.target >= 0 && x.source !== x.target);
774831

0 commit comments

Comments
 (0)