Skip to content

Commit 22e4e7b

Browse files
committed
fix: prevent NaN in transform calculations when scale is 0 (#1971)
* fix: prevent NaN in transform calculations when scale is 0 - Add MIN_SCALE constant to avoid zero scaling - Update scale transform functions to enforce minimum scale value - Add test case for scale(0) transform - Add demo example with scale(0) transform * chore: add changeset * refactor: extract scale clamping logic into reusable function - Add clampScale helper function to avoid code duplication - Replace repeated Math.max(item, MIN_SCALE) calls with clampScale - Maintain same behavior while improving code maintainability
1 parent 433cc43 commit 22e4e7b

4 files changed

Lines changed: 73 additions & 5 deletions

File tree

.changeset/all-dots-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@antv/g-lite': patch
3+
---
4+
5+
fix: prevent NaN in transform calculations when scale is 0

__tests__/demos/2d/transform.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ export async function transform(context) {
44
const { canvas } = context;
55
await canvas.ready;
66

7+
const circle0 = new Circle({
8+
style: {
9+
cx: 50,
10+
cy: 50,
11+
r: 20,
12+
fill: 'blue',
13+
transform: 'scale(0)',
14+
},
15+
});
16+
canvas.appendChild(circle0);
17+
718
const circle1 = new Circle({
819
style: {
920
cx: 100,

__tests__/unit/css/properties/transform.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,31 @@ describe('CSSPropertyTransform', () => {
233233
expect(circle.getLocalBounds().halfExtents).toStrictEqual([25, 25, 0]);
234234
});
235235

236+
it('should handle scale(0) transform correctly without returning NaN in bounds', async () => {
237+
const circle = new Circle({
238+
style: {
239+
cx: 10,
240+
cy: 10,
241+
r: 50,
242+
transform: 'scale(0)',
243+
},
244+
});
245+
246+
await canvas.ready;
247+
canvas.appendChild(circle);
248+
249+
const bounds = circle.getBounds();
250+
expect(bounds).toBeDefined();
251+
252+
expect(bounds.center.every(Number.isFinite)).toBeTruthy();
253+
expect(bounds.halfExtents.every(Number.isFinite)).toBeTruthy();
254+
expect(bounds.min.every(Number.isFinite)).toBeTruthy();
255+
expect(bounds.max.every(Number.isFinite)).toBeTruthy();
256+
257+
expect(bounds.center).toStrictEqual([10, 10, 0]);
258+
// expect(bounds.halfExtents).toStrictEqual([0, 0, 0]);
259+
});
260+
236261
it("should reset transform with 'none' keyword correctly.", async () => {
237262
const circle = new Circle({
238263
style: {

packages/g-lite/src/utils/transform-mat4.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ import { runtime } from '../global-runtime';
66
import type { TransformType } from '../types';
77
import { deg2rad } from './math';
88

9+
/**
10+
* Minimum scaling limit
11+
*
12+
* When `scale` is 0, the matrix will become an irreversible matrix,
13+
* and `NaN` will appear during matrix calculation and decomposition,
14+
* causing exceptions
15+
*/
16+
const MIN_SCALE = 0.000001;
17+
const clampScale = (item: number) => Math.max(item, MIN_SCALE);
18+
919
function createSkewMatrix(skewMatrix: mat4, skewX: number, skewY: number) {
1020
// Create an identity matrix
1121
mat4.identity(skewMatrix);
@@ -21,19 +31,36 @@ const $mat4_2 = mat4.create();
2131

2232
const parser: Record<TransformType, (d: CSSUnitValue[]) => void> = {
2333
scale: (d: CSSUnitValue[]) => {
24-
mat4.fromScaling($mat4_1, [d[0].value, d[1].value, 1]);
34+
mat4.fromScaling(
35+
$mat4_1,
36+
[d[0].value, d[1].value, 1].map((item) => clampScale(item)) as vec3,
37+
);
2538
},
2639
scaleX: (d: CSSUnitValue[]) => {
27-
mat4.fromScaling($mat4_1, [d[0].value, 1, 1]);
40+
mat4.fromScaling(
41+
$mat4_1,
42+
[d[0].value, 1, 1].map((item) => clampScale(item)) as vec3,
43+
);
2844
},
2945
scaleY: (d: CSSUnitValue[]) => {
30-
mat4.fromScaling($mat4_1, [1, d[0].value, 1]);
46+
mat4.fromScaling(
47+
$mat4_1,
48+
[1, d[0].value, 1].map((item) => clampScale(item)) as vec3,
49+
);
3150
},
3251
scaleZ: (d: CSSUnitValue[]) => {
33-
mat4.fromScaling($mat4_1, [1, 1, d[0].value]);
52+
mat4.fromScaling(
53+
$mat4_1,
54+
[1, 1, d[0].value].map((item) => clampScale(item)) as vec3,
55+
);
3456
},
3557
scale3d: (d: CSSUnitValue[]) => {
36-
mat4.fromScaling($mat4_1, [d[0].value, d[1].value, d[2].value]);
58+
mat4.fromScaling(
59+
$mat4_1,
60+
[d[0].value, d[1].value, d[2].value].map((item) =>
61+
clampScale(item),
62+
) as vec3,
63+
);
3764
},
3865
translate: (d: CSSUnitValue[]) => {
3966
mat4.fromTranslation($mat4_1, [d[0].value, d[1].value, 0]);

0 commit comments

Comments
 (0)