Skip to content

Commit 1e99bed

Browse files
committed
[IMP] format: add scientific format
Added scientific format in menu and side panel Task: 4962708
1 parent 04b8575 commit 1e99bed

File tree

9 files changed

+119
-72
lines changed

9 files changed

+119
-72
lines changed

package-lock.json

Lines changed: 22 additions & 66 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/o-spreadsheet-engine/src/helpers/format/format.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,14 @@ function applyInternalNumberFormat(value: number, format: NumberInternalFormat,
234234
return "∞" + (format.percentSymbols ? "%" : "");
235235
}
236236

237+
let power = Math.floor(Math.log10(Math.abs(value)));
238+
if (power === -Infinity) {
239+
power = 0;
240+
}
241+
if (format.scientific) {
242+
value = value / 10 ** power;
243+
}
244+
237245
const multiplier = format.percentSymbols * 2 - format.magnitude * 3;
238246
value = value * 10 ** multiplier;
239247

@@ -253,6 +261,12 @@ function applyInternalNumberFormat(value: number, format: NumberInternalFormat,
253261
formattedValue += locale.decimalSeparator + applyDecimalFormat(decimalDigits || "", format);
254262
}
255263

264+
if (format.scientific) {
265+
const powerAsString = Math.abs(power).toString().padStart(2, "0");
266+
const sign = power >= 0 ? "+" : "-";
267+
formattedValue += "e" + sign + powerAsString;
268+
}
269+
256270
return formattedValue;
257271
}
258272

packages/o-spreadsheet-engine/src/helpers/format/format_parser.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
FormatToken,
99
PercentToken,
1010
RepeatCharToken,
11+
ScientificToken,
1112
StringToken,
1213
TextPlaceholderToken,
1314
ThousandsSeparatorToken,
@@ -40,11 +41,13 @@ export interface NumberInternalFormat {
4041
| StringToken
4142
| CharToken
4243
| PercentToken
44+
| ScientificToken
4345
| ThousandsSeparatorToken
4446
| RepeatCharToken
4547
)[];
4648
readonly percentSymbols: number;
4749
readonly thousandsSeparator: boolean;
50+
readonly scientific: boolean;
4851
/** A thousand separator after the last digit in the format means that we divide the number by a thousand */
4952
readonly magnitude: number;
5053
/**
@@ -57,6 +60,7 @@ export interface NumberInternalFormat {
5760
| StringToken
5861
| CharToken
5962
| PercentToken
63+
| ScientificToken
6064
| ThousandsSeparatorToken
6165
| RepeatCharToken
6266
)[];
@@ -151,6 +155,7 @@ function areValidNumberFormatTokens(
151155
| DecimalPointToken
152156
| ThousandsSeparatorToken
153157
| PercentToken
158+
| ScientificToken
154159
| StringToken
155160
| CharToken
156161
| RepeatCharToken
@@ -161,6 +166,7 @@ function areValidNumberFormatTokens(
161166
token.type === "DECIMAL_POINT" ||
162167
token.type === "THOUSANDS_SEPARATOR" ||
163168
token.type === "PERCENT" ||
169+
token.type === "SCIENTIFIC" ||
164170
token.type === "STRING" ||
165171
token.type === "CHAR" ||
166172
token.type === "REPEATED_CHAR"
@@ -188,6 +194,7 @@ function parseNumberFormatTokens(
188194

189195
let parsedPart = integerPart;
190196
let percentSymbols = 0;
197+
let scientific = false;
191198
let magnitude = 0;
192199
let lastIndexOfDigit = tokens.findLastIndex((token) => token.type === "DIGIT");
193200
let hasThousandSeparator = false;
@@ -212,6 +219,9 @@ function parseNumberFormatTokens(
212219
throw new Error("Multiple decimal points in a number format");
213220
}
214221
break;
222+
case "SCIENTIFIC":
223+
scientific = true;
224+
break;
215225
case "REPEATED_CHAR":
216226
case "CHAR":
217227
case "STRING":
@@ -247,6 +257,7 @@ function parseNumberFormatTokens(
247257
integerPart,
248258
decimalPart,
249259
percentSymbols,
260+
scientific,
250261
thousandsSeparator: hasThousandSeparator,
251262
magnitude,
252263
};
@@ -349,6 +360,9 @@ function numberInternalFormatToTokenList(internalFormat: NumberInternalFormat):
349360
tokens.push({ type: "DECIMAL_POINT", value: "." });
350361
tokens.push(...internalFormat.decimalPart);
351362
}
363+
if (internalFormat.scientific) {
364+
tokens.push({ type: "SCIENTIFIC", value: "e" });
365+
}
352366

353367
return tokens;
354368
}

packages/o-spreadsheet-engine/src/helpers/format/format_tokenizer.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export interface PercentToken {
2525
value: "%";
2626
}
2727

28+
export interface ScientificToken {
29+
type: "SCIENTIFIC";
30+
value: "e";
31+
}
32+
2833
export interface ThousandsSeparatorToken {
2934
type: "THOUSANDS_SEPARATOR";
3035
value: ",";
@@ -51,6 +56,7 @@ export type FormatToken =
5156
| StringToken
5257
| CharToken
5358
| PercentToken
59+
| ScientificToken
5460
| ThousandsSeparatorToken
5561
| TextPlaceholderToken
5662
| DatePartToken
@@ -77,6 +83,7 @@ export function tokenizeFormat(str: string): FormatToken[][] {
7783
tokenizeThousandsSeparator(chars) ||
7884
tokenizeDecimalPoint(chars) ||
7985
tokenizePercent(chars) ||
86+
tokenizeScientific(chars) ||
8087
tokenizeDatePart(chars) ||
8188
tokenizeTextPlaceholder(chars) ||
8289
tokenizeRepeatedChar(chars);
@@ -173,6 +180,14 @@ function tokenizePercent(chars: TokenizingChars): FormatToken | null {
173180
return null;
174181
}
175182

183+
function tokenizeScientific(chars: TokenizingChars): FormatToken | null {
184+
if (chars.current === "e") {
185+
chars.shift();
186+
return { type: "SCIENTIFIC", value: "e" };
187+
}
188+
return null;
189+
}
190+
176191
function tokenizeDigit(chars: TokenizingChars): FormatToken | null {
177192
if (chars.current === "0" || chars.current === "#") {
178193
const value = chars.current;

src/actions/format_actions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ export const formatNumberPercent = createFormatActionSpec({
7676
format: "0.00%",
7777
});
7878

79+
export const formatNumberScientific = createFormatActionSpec({
80+
name: _t("Scientific"),
81+
descriptionValue: 0.1012,
82+
format: "0.00e",
83+
});
84+
7985
export const formatNumberCurrency = createFormatActionSpec({
8086
name: _t("Currency"),
8187
descriptionValue: 1000.12,

src/components/side_panel/more_formats/more_formats_store.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,19 @@ export class MoreFormatsStore extends SpreadsheetStore {
125125
}
126126

127127
get numberFormatProposals() {
128-
const numberFormats = ["0.00", "0", "#,##0", "#,##0.00", "0%", "0.00%", "0.00;(0.00);-"].map(
129-
(format) => ({
130-
label: formatValue(-1234.56, { format, locale: this.getters.getLocale() }),
131-
format,
132-
})
133-
);
128+
const numberFormats = [
129+
"0.00",
130+
"0",
131+
"#,##0",
132+
"#,##0.00",
133+
"0%",
134+
"0.00%",
135+
"0.00e",
136+
"0.00;(0.00);-",
137+
].map((format) => ({
138+
label: formatValue(-1234.56, { format, locale: this.getters.getLocale() }),
139+
format,
140+
}));
134141

135142
return [
136143
{ label: _t("Automatic"), format: undefined },

src/registries/menus/number_format_menu_registry.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ numberFormatMenuRegistry
2929
...ACTION_FORMAT.formatNumberPercent,
3030
id: "format_number_percent",
3131
sequence: 30,
32+
separator: false,
33+
})
34+
.add("format_number_scientific", {
35+
...ACTION_FORMAT.formatNumberScientific,
36+
id: "format_number_scientific",
37+
sequence: 33,
3238
separator: true,
3339
})
3440
.add("format_number_currency", {

tests/formats/format_helpers.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,20 @@ describe("formatValue on number", () => {
316316
expect(formatValue(-Infinity, { format: "0.0%", locale })).toBe("-∞%");
317317
});
318318

319+
test("apply various scientific format", () => {
320+
expect(formatValue(0.1234, { format: "0e", locale })).toBe("1e-01");
321+
expect(formatValue(0.1234, { format: "0.0e", locale })).toBe("1.2e-01");
322+
expect(formatValue(0.1234, { format: "0.00e", locale })).toBe("1.23e-01");
323+
expect(formatValue(0.1234, { format: "0.000e", locale })).toBe("1.234e-01");
324+
});
325+
326+
test("apply scientific format to various number", () => {
327+
expect(formatValue(-0.1234, { format: "0.00e", locale })).toBe("-1.23e-01");
328+
expect(formatValue(1234, { format: "0.00e", locale })).toBe("1.23e+03");
329+
expect(formatValue(0, { format: "0.00e", locale })).toBe("0.00e+00");
330+
expect(formatValue(-1234, { format: "0.00e", locale })).toBe("-1.23e+03");
331+
});
332+
319333
test("can apply format with custom currencies", () => {
320334
expect(formatValue(1234, { format: "#,##0[$TEST]", locale })).toBe("1,234TEST");
321335
expect(formatValue(1234, { format: '#,##0 "TEST"', locale })).toBe("1,234 TEST");

tests/formats/formatting_plugin.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,21 @@ describe("formatting values (with formatters)", () => {
251251
expect(getCell(model, "A1")?.format).toBe(";;;@");
252252
});
253253

254+
test("SET_DECIMAL on scientific format", () => {
255+
const model = new Model();
256+
setCellContent(model, "A1", "1234");
257+
258+
setFormat(model, "A1", "0.00e");
259+
setDecimal(model, "A1", 1);
260+
expect(getCell(model, "A1")?.format).toBe("0.000e");
261+
expect(getEvaluatedCell(model, "A1").formattedValue).toBe("1.234e+03");
262+
263+
setDecimal(model, "A1", -1);
264+
setDecimal(model, "A1", -1);
265+
expect(getCell(model, "A1")?.format).toBe("0.0e");
266+
expect(getEvaluatedCell(model, "A1").formattedValue).toBe("1.2e+03");
267+
});
268+
254269
test("UPDATE_CELL on long number that are truncated due to default format don't loose truncated digits", () => {
255270
const model = new Model();
256271
setCellContent(model, "A1", "10.123456789123");

0 commit comments

Comments
 (0)