WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 13 additions & 18 deletions pages/common/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,31 @@ export function dateFormatter(value: null | number) {
.join("\n");
}

export function numberFormatter(value: null | number) {
export function numberFormatter(value: number | null, locale: string = "en-US"): string {
if (value === null) {
return "";
}

const format = (num: number) => parseFloat(num.toFixed(2)).toString(); // trims unnecessary decimals

const absValue = Math.abs(value);

if (absValue === 0) {
return "0";
}

if (absValue < 0.01) {
return value.toExponential(0);
}

if (absValue >= 1e9) {
return format(value / 1e9) + "G";
}

if (absValue >= 1e6) {
return format(value / 1e6) + "M";
}

if (absValue >= 1e3) {
return format(value / 1e3) + "K";
// Use scientific notation for very small numbers
if (absValue < 1e-9) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason why the threshold of this condition changes from 0.01 (i.e, 1e-2) to 1e-9?

@pan-kot Would this be a problem?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PR, I also updated how numbers are truncated, to reflect how CloudWatch charts are currently behaving. The number 0.00086661402 is truncated in CDS, but not on the console.

I am open to the discussion about that. It may be valuable to expose that setting to the chart props?

Copy link
Member

@pan-kot pan-kot Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do that in the test pages - but let's not change the current precision in the default chart formatters. What is good for one team might not work for another.

All formatters can be already overridden in both public and core APIs.

return new Intl.NumberFormat(locale, {
notation: "scientific",
maximumFractionDigits: 9,
}).format(value);
}

return format(value);
// Use compact notation for normal range
return new Intl.NumberFormat(locale, {
notation: "compact",
compactDisplay: "short",
maximumFractionDigits: 9,
}).format(value);
}

export function moneyFormatter(value: null | number) {
Expand Down
6 changes: 4 additions & 2 deletions src/core/__tests__/chart-core-axes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,12 @@ describe("CoreChart: axes", () => {
test("uses default numeric axes formatters for integer values", () => {
renderChart({ highcharts, options: { series, xAxis: { title: { text: "X" } }, yAxis: { title: { text: "Y" } } } });
getAxisOptionsFormatters().forEach((formatter) => {
// See https://www.unicode.org/cldr/cldr-aux/charts/29/verify/numbers/en.html
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's better add this reference to the formatter function, not tests

expect(formatter.call(mockAxisContext({ value: 0 }))).toBe("0");
expect(formatter.call(mockAxisContext({ value: 1 }))).toBe("1");
expect(formatter.call(mockAxisContext({ value: 1_000 }))).toBe("1K");
expect(formatter.call(mockAxisContext({ value: 1_000_000 }))).toBe("1M");
expect(formatter.call(mockAxisContext({ value: 1_000_000_000 }))).toBe("1G");
expect(formatter.call(mockAxisContext({ value: 1_000_000_000 }))).toBe("1B");
});
});

Expand All @@ -140,7 +141,8 @@ describe("CoreChart: axes", () => {
expect(formatter.call(mockAxisContext({ value: 2.0 }))).toBe("2");
expect(formatter.call(mockAxisContext({ value: 2.03 }))).toBe("2.03");
expect(formatter.call(mockAxisContext({ value: 0.03 }))).toBe("0.03");
expect(formatter.call(mockAxisContext({ value: 0.003 }))).toBe("3e-3");
expect(formatter.call(mockAxisContext({ value: 0.003 }))).toBe("0.003");
expect(formatter.call(mockAxisContext({ value: 0.0000000003 }))).toBe("3E-10");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the "E" uppercase?

Copy link
Contributor Author

@Pixselve Pixselve Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is based on the Unicode Common Locale Data Repository. See https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns

Type Example pattern Meaning
E # E 0 This marks a scientific format. The E symbol may change position. Must be retained.

});
});

Expand Down
31 changes: 13 additions & 18 deletions src/core/formatters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,30 +102,25 @@ function secondFormatter(value: number) {
});
}

function numberFormatter(value: number): string {
const format = (num: number) => parseFloat(num.toFixed(2)).toString(); // trims unnecessary decimals

function numberFormatter(value: number, locale: string = "en-US"): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we take locale from the document instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a PR to share the locale getting logic: cloudscape-design/component-toolkit#176

const absValue = Math.abs(value);

if (absValue === 0) {
return "0";
}

if (absValue < 0.01) {
return value.toExponential(0);
}

if (absValue >= 1e9) {
return format(value / 1e9) + "G";
}

if (absValue >= 1e6) {
return format(value / 1e6) + "M";
}

if (absValue >= 1e3) {
return format(value / 1e3) + "K";
// Use scientific notation for very small numbers
if (absValue < 1e-9) {
return new Intl.NumberFormat(locale, {
notation: "scientific",
maximumFractionDigits: 9,
}).format(value);
}

return format(value);
// Use compact notation for normal range
return new Intl.NumberFormat(locale, {
notation: "compact",
compactDisplay: "short",
maximumFractionDigits: 9,
}).format(value);
}
4 changes: 2 additions & 2 deletions test/functional/cartesian-chart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ test(
"pins chart tooltip after hovering chart point and then chart point group",
setupTest("#/01-cartesian-chart/column-chart-test", async (page) => {
const chart = w.findCartesianHighcharts('[data-testid="grouped-column-chart"]');
const point = chart.find('[aria-label="Jul 2019 6.32K, Prev costs"]');
const expectedTooltipContent = ["Jul 2019\nCosts\n8.77K\nPrev costs\n6.32K"];
const point = chart.find('[aria-label="Jul 2019 6.322K, Prev costs"]');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we increase the default precision?

const expectedTooltipContent = ["Jul 2019\nCosts\n8.768K\nPrev costs\n6.322K"];

const pointBox = await page.getBoundingBox(point.toSelector());
const pointCenter = [pointBox.left + pointBox.width / 2, pointBox.top + pointBox.height / 2];
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"target": "ES2019",
"jsx": "react-jsx",
"types": [],
"lib": ["es2019", "dom", "dom.iterable"],
"lib": ["es2020", "dom", "dom.iterable"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change needed?

"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
Expand Down
Loading