Skip to content

Commit d6d90ba

Browse files
Anush2303Anush
andauthored
feat(react-charts): support string y axis in scatter chart (microsoft#35120)
Co-authored-by: Anush <[email protected]>
1 parent ec2c01b commit d6d90ba

File tree

7 files changed

+86
-2
lines changed

7 files changed

+86
-2
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": "support string y axis in scatter chart",
4+
"packageName": "@fluentui/react-charts",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,7 @@ export interface ScatterChartProps extends CartesianChartProps {
14191419
data: ChartProps;
14201420
getCalloutDescriptionMessage?: (calloutDataProps: CustomizedCalloutData) => string | undefined;
14211421
onRenderCalloutPerDataPoint?: RenderFunction<CustomizedCalloutData>;
1422+
showYAxisLablesTooltip?: boolean;
14221423
styles?: ScatterChartStyles;
14231424
}
14241425

packages/charts/react-charts/library/src/components/CommonComponents/CartesianChart.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const CartesianChart: React.FunctionComponent<ModifiedCartesianChartProps
7272
ChartTypes.HeatMapChart,
7373
ChartTypes.VerticalStackedBarChart,
7474
ChartTypes.GanttChart,
75+
ChartTypes.ScatterChart,
7576
];
7677
/**
7778
* In RTL mode, Only graph will be rendered left/right. We need to provide left and right margins manually.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,12 @@ const transformPlotlyJsonToScatterTraceProps = (
10811081
roundedTicks: true,
10821082
...commonProps,
10831083
...yMinMax,
1084+
...(isScatterChart
1085+
? {
1086+
showYAxisLablesTooltip: true,
1087+
...getAxisCategoryOrderProps(input.data, input.layout),
1088+
}
1089+
: {}),
10841090
} as LineChartProps;
10851091
}
10861092
};

packages/charts/react-charts/library/src/components/DeclarativeChart/__snapshots__/PlotlySchemaAdapterUT.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5202,6 +5202,7 @@ Object {
52025202
"hideTickOverlap": true,
52035203
"optimizeLargeData": false,
52045204
"roundedTicks": true,
5205+
"showYAxisLablesTooltip": true,
52055206
"supportNegativeData": true,
52065207
"useUTC": false,
52075208
"width": undefined,

packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.tsx

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
getRangeForScatterMarkerSize,
2525
domainRangeOfDateForAreaLineScatterVerticalBarCharts,
2626
domainRangeOfNumericForAreaLineScatterCharts,
27+
sortAxisCategories,
2728
} from '../../utilities/index';
2829
import {
2930
AccessibilityProps,
@@ -135,6 +136,17 @@ export const ScatterChart: React.FunctionComponent<ScatterChartProps> = React.fo
135136
? (getTypeOfAxis(props.data.scatterChartData![0].data[0].x, true) as XAxisTypes)
136137
: XAxisTypes.StringAxis;
137138

139+
// Detect y axis type (numeric or string)
140+
const _yAxisType: YAxisType =
141+
props.data.scatterChartData &&
142+
props.data.scatterChartData.length > 0 &&
143+
props.data.scatterChartData[0].data &&
144+
props.data.scatterChartData[0].data.length > 0
145+
? typeof props.data.scatterChartData[0].data[0].y === 'string'
146+
? YAxisType.StringAxis
147+
: YAxisType.NumericAxis
148+
: YAxisType.NumericAxis;
149+
138150
const pointsRef = React.useRef<ScatterChartDataWithIndex[] | []>([]);
139151
// eslint-disable-next-line @typescript-eslint/no-explicit-any
140152
const calloutPointsRef = React.useRef<any[]>([]);
@@ -300,6 +312,47 @@ export const ScatterChart: React.FunctionComponent<ScatterChartProps> = React.fo
300312
);
301313
}
302314

315+
function _getOrderedYAxisLabels() {
316+
const shouldOrderYAxisLabelsByCategoryOrder =
317+
_yAxisType === YAxisType.StringAxis && props.yAxisCategoryOrder !== 'default';
318+
if (!shouldOrderYAxisLabelsByCategoryOrder) {
319+
// Collect all unique string y values from all data points in all series, in reverse order
320+
const yLabelsSet = new Set<string>();
321+
for (let i = _points.length - 1; i >= 0; i--) {
322+
const point = _points[i];
323+
if (point.data && Array.isArray(point.data)) {
324+
for (const d of point.data) {
325+
if (typeof d.y === 'string') {
326+
yLabelsSet.add(d.y);
327+
}
328+
}
329+
}
330+
}
331+
return Array.from(yLabelsSet);
332+
}
333+
334+
return sortAxisCategories(_mapCategoryToValues(), props.yAxisCategoryOrder);
335+
}
336+
337+
function _mapCategoryToValues() {
338+
const categoryToValues: Record<string, number[]> = {};
339+
_points.forEach(point => {
340+
if (point.data && Array.isArray(point.data)) {
341+
point.data.forEach(d => {
342+
if (typeof d.y === 'string') {
343+
if (!categoryToValues[d.y]) {
344+
categoryToValues[d.y] = [];
345+
}
346+
if (typeof d.x === 'number') {
347+
categoryToValues[d.y].push(d.x);
348+
}
349+
}
350+
});
351+
}
352+
});
353+
return categoryToValues;
354+
}
355+
303356
function _onLegendSelectionChange(
304357
legendsSelected: string[],
305358
event: React.MouseEvent<HTMLButtonElement>,
@@ -343,7 +396,7 @@ export const ScatterChart: React.FunctionComponent<ScatterChartProps> = React.fo
343396
})!;
344397

345398
const extraMaxPixels =
346-
_xAxisType !== XAxisTypes.StringAxis
399+
_xAxisType !== XAxisTypes.StringAxis && _yAxisType !== YAxisType.StringAxis
347400
? getRangeForScatterMarkerSize({
348401
data: _points,
349402
xScale: _xAxisScale,
@@ -363,7 +416,11 @@ export const ScatterChart: React.FunctionComponent<ScatterChartProps> = React.fo
363416
for (let j = 0; j < _points[i].data.length; j++) {
364417
const { x, y, xAxisCalloutData, xAxisCalloutAccessibilityData } = _points?.[i]?.data[j];
365418
const xPoint = _xAxisScale?.(x);
366-
const yPoint = _yAxisScale?.(y);
419+
// Use string y axis scale if needed
420+
const yPoint =
421+
_yAxisType === YAxisType.StringAxis
422+
? _yAxisScale?.(y) + (_yAxisScale?.bandwidth ? _yAxisScale.bandwidth() / 2 : 0)
423+
: _yAxisScale?.(y);
367424
if (!isPlottable(xPoint, yPoint)) {
368425
continue;
369426
}
@@ -673,6 +730,9 @@ export const ScatterChart: React.FunctionComponent<ScatterChartProps> = React.fo
673730

674731
_xAxisLabels = [...new Set(xAxisLabels)];
675732

733+
// Compute unique y axis labels for string y axis
734+
const _yAxisLabels: string[] = _getOrderedYAxisLabels();
735+
676736
return !_isChartEmpty() ? (
677737
<CartesianChart
678738
{...props}
@@ -685,6 +745,9 @@ export const ScatterChart: React.FunctionComponent<ScatterChartProps> = React.fo
685745
getmargins={_getMargins}
686746
getGraphData={_initializeScatterChartData}
687747
xAxisType={_xAxisType}
748+
yAxisType={_yAxisType}
749+
// Pass stringDatasetForYAxisDomain only if y axis is string
750+
{...(_yAxisType === YAxisType.StringAxis ? { stringDatasetForYAxisDomain: _yAxisLabels } : {})}
688751
getMinMaxOfYAxis={_getNumericMinMaxOfY}
689752
getDomainNRangeValues={_getDomainNRangeValues}
690753
createYAxis={createNumericYAxis}

packages/charts/react-charts/library/src/components/ScatterChart/ScatterChart.types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ export interface ScatterChartProps extends CartesianChartProps {
4646
* The prop used to define the culture to localized the numbers
4747
*/
4848
culture?: string;
49+
50+
/**
51+
* The prop used to define the y axis labels tooltip visibility
52+
*/
53+
showYAxisLablesTooltip?: boolean;
4954
}
5055

5156
/**

0 commit comments

Comments
 (0)