Skip to content

Commit 1095d7d

Browse files
Fix percentage formatting
1 parent 654db94 commit 1095d7d

3 files changed

Lines changed: 65 additions & 2 deletions

File tree

frontend/src/components/Simulation/Results.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Panel, SimulationResults } from "@/dto/SimulationResults";
55
import ResultConcTable from "@/components/Simulation/ConcResultTable";
66
import Reactions from "../../pages/Reactions";
77
import { MassBalanceError } from "@/components/Simulation/MassBalanceError";
8-
import { extractPlotData } from "@/functions/Formatting";
8+
import { extractPlotData, formatPhaseFraction } from "@/functions/Formatting";
99
import BarChart from "@/components/BarChart";
1010
import GenericTable from "@/components/GenericTable";
1111

@@ -73,7 +73,7 @@ const Results: React.FC<ResultsProps> = ({ simulationResults }) => {
7373
const hasConcentrations = Object.keys(phase.concentrations).length > 0;
7474
if (!hasConcentrations) continue;
7575

76-
panelTabs.push(`${modelPrefix}${phase.kind} (${(phase.fraction * 100).toFixed(1)}%)`);
76+
panelTabs.push(`${modelPrefix}${phase.kind} (${formatPhaseFraction(phase.fraction)})`);
7777

7878
const initialConcentrations = simulationResults.input.concentrations;
7979

frontend/src/functions/Formatting.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@ import { SimulationResults, getCo2RichConcentrations } from "@/dto/SimulationRes
22
import { ChartDataSet, TabulatedResultRow } from "@/dto/ChartData";
33
import { ExperimentResult } from "@/dto/ExperimentResult";
44

5+
// Adaptive precision: finds enough decimals to show a small value
6+
// e.g. gap=0.0001 → 5 decimals, gap=0.05 → 2 decimals
7+
const adaptiveDecimals = (gap: number, max = 6): number =>
8+
Math.min(Math.max(1, Math.ceil(-Math.log10(gap)) + 1), max);
9+
10+
export const formatPhaseFraction = (fraction: number): string => {
11+
const percent = fraction * 100;
12+
13+
if (percent === 0 || percent === 100) return `${percent.toFixed(1)}%`;
14+
15+
if (percent < 1e-5) {
16+
return `${percent.toExponential(2)}%`;
17+
}
18+
19+
if (percent < 0.1) {
20+
return `${percent.toFixed(adaptiveDecimals(percent))}%`;
21+
}
22+
23+
if (percent > 99.9) {
24+
const gap = 100 - percent;
25+
if (gap < 1e-5) return "≈100%";
26+
return `${percent.toFixed(adaptiveDecimals(gap))}%`;
27+
}
28+
29+
return `${percent.toFixed(1)}%`;
30+
};
31+
532
export const formatConcentration = (value: number | undefined | null): string => {
633
if (value === undefined || value === null) return "-";
734

frontend/tests/functions/Formatting.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
convertSimulationToChartData,
55
convertSimulationQueriesResultToTabulatedData,
66
convertExperimentResultsToTabulatedData,
7+
formatPhaseFraction,
78
} from "@/functions/Formatting";
89
import { SimulationResults } from "@/dto/SimulationResults";
910
import { ExperimentResult } from "@/dto/ExperimentResult";
@@ -33,6 +34,41 @@ describe("convertToSubscripts", () => {
3334
});
3435
});
3536

37+
describe("formatPhaseFraction", () => {
38+
it("should show normal percentage for mid-range values", () => {
39+
expect(formatPhaseFraction(0.5)).toBe("50.0%");
40+
expect(formatPhaseFraction(0.123)).toBe("12.3%");
41+
});
42+
43+
it("should show exact 0 and 100", () => {
44+
expect(formatPhaseFraction(0)).toBe("0.0%");
45+
expect(formatPhaseFraction(1)).toBe("100.0%");
46+
});
47+
48+
it("should show extra precision for values very close to 100%", () => {
49+
expect(formatPhaseFraction(0.999999)).toBe("99.99990%");
50+
expect(formatPhaseFraction(0.9999)).toBe("99.990%");
51+
expect(formatPhaseFraction(0.999)).toBe("99.9%");
52+
expect(formatPhaseFraction(0.999999999)).toBe("≈100%");
53+
});
54+
55+
it("should show decimal percentage for small values", () => {
56+
expect(formatPhaseFraction(0.000001)).toBe("0.00010%");
57+
expect(formatPhaseFraction(0.00001)).toBe("0.0010%");
58+
expect(formatPhaseFraction(0.0001)).toBe("0.010%");
59+
});
60+
61+
it("should show scientific notation for extremely small values", () => {
62+
expect(formatPhaseFraction(0.0000001)).toBe("1.00e-5%");
63+
expect(formatPhaseFraction(0.00000001)).toBe("1.00e-6%");
64+
});
65+
66+
it("should show normal percentage for small but not tiny values", () => {
67+
expect(formatPhaseFraction(0.05)).toBe("5.0%");
68+
expect(formatPhaseFraction(0.001)).toBe("0.1%");
69+
});
70+
});
71+
3672
describe("convertingSimulationToChartData", () => {
3773
const simulation: SimulationResults = {
3874
input: { concentrations: { CO: 0.5, H20: 0.7 }, models: [{ parameters: {}, modelId: "Narnia" }] },

0 commit comments

Comments
 (0)