How do you smoothly display numbers in frontend? In this article, we summarized some practical principles, introduced a tool library - Number Display, and analyzed it's source code.
Number Display has both JavaScript version for Web, and Dart version for Flutter.
Displaying numbers is a very common requirement in frontend development. Unlike those in the statistic or scientific reports, which pay most attention to precision and standardization, numbers in frontend care more about friendly user experience. It's important to let the user perceive the number at first glance, and to keep the page clean and neat.
Weather in a mobile app, or in a backstage management system, or on a popular data visualization screen, displaying numbers has some common demands:
That will be really nice if there would be a tool function which, with only simple configurations, could convert input numbers to required strings. Especially when the geometric space is limited, it could automatically trim the number string by transforming the unit or reduce the decimal:
-254623933.876 => -254.62M
-1.2345e+5 => -123,450
12.000 => 12
NaN => --
Number Display was built for such demands:
rstStr = display(-254623933.876) // result: -254.62M
In principle of separating configuration and usage, Number Display exports a high order function named createDisplay . By passing parameters as configuration to createDisplay , we can custom the real display function used in widgets.
Usage in Web:
import createDisplay from 'number-display';
const display = createDisplay({
length: 8,
decimal: 0,
});
<div>{display(data)}</div>
Usage in Flutter:
import 'package:number_display/number_display.dart';
final display = createDisplay(
length: 8,
decimal: 0,
);
print(display(data));
This makes it quite concise to use display , and possible to batch the configuration. Note that the configuration is passed as a object in JavaScript but named parameters in Dart.
Now Number Display includes 5 configurations:
While displayed on the page, number strings are limited by the geometric space.
Theoretically, While designing the size of the widgets, it's value length should be considered. But actually, it can't be so comprehensive, and the response always falls to the frontend developers.
The form of the number can be flexible, but the string is absolutely unacceptable to overflow outside the widget. That is the highest priority. And that is the main function of Number Display. When the length is limit, Number Display will trim the number string by the following oder:
Behind the hood, Number Display mainly uses regular expression to handle the number strings. Steps are as follows:
1. Legal judgment
For JavaScript, which a quite dynamic language, the input "number" can either be a Number or a String in practice:
if (
(type !== 'number' && type !== 'string')
|| (type === 'number' && !Number.isFinite(value))
) {
return placeholder;
}
While for static typed Dart, we announced the input type as num:
if (value == null || !value.isFinite) {
return placeholder;
}
2. Slicing the sign, integer and decimal parts
The sign, integer and decimal parts are identified by the separator "-" and ".". Refered to the code of Ant Design Statistic component, with String.match function in JavaScript, we can match multiple patterns and get all the three parts as an array in just one line:
const cells = value.match(/^(-?)(\d*)(\.(\d+))?$/);
But in Dart we have no such trick:
final negative = RegExp(r'^-?').stringMatch(valueStr) ?? '';
final integer = RegExp(r'\d+').stringMatch(valueStr) ?? '';
final deciRaw = RegExp(r'(?<=\.)\d+$').stringMatch(valueStr) ?? '';
In fact the regular expression in dart is optimizing, we noticed that there are changes of regular expression in all recent versions (2.1-2.4) .
3. Trimming the decimal part
After omitting the comma separators, if the space is still limited, the decimal length will be reduced. Note since that the decimal point only appears when there were decimal part, there would be two boundaries, 0 and 2, for the length. Meanwhile, we will subtract the trailing zeros by the way:
(For the left part, JavaScript and Dart code are quite similar, so we only take JavaScript code for example)
let space = length - negative.length - int.length;
if (space >= 2) {
deciShow = deci.slice(0, space - 1).replace(/0+$/, '');
return `${negative}${int}${deciShow && '.'}${deciShow}`;
}
if (space >= 0) {
return `${negative}${int}`;
}
4. Trimming the integer part
If the string still overflows, we have to convert the integer part with units like k or M. As we have added the group separator before:
const localeInt = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
We can use that to calculate which unit we need:
const sections = localeInt.split(',');
const units = ['k', 'M', 'G', 'T', 'P'];
const unit = units[sections.length - 2];
At last, just the same as trimming the decimal part, reduce the trailing part to the room left:
space = length - negative.length - mainSection.length - 1;
if (space >= 2) {
const tailShow = tailSection.slice(0, space - 1).replace(/0+$/, '');
return `${negative}${mainSection}${tailShow && '.'}${tailShow}${unit}`;
}
if (space >= 0) {
return `${negative}${mainSection}${unit}`;
}
Displaying numbers is often ignored in the development, and there are no deep technologies in it indeed. But if we can't think thoroughly, there would be a lot of trouble.
The principles above seek more in common as possible. But there are always unique demands, so we tried to make it configurable. Number Display has experienced two major updates, and simplified the configurations. For sake of better performance, we changed some loops and judgments with regular expression operations. Hopefully that you could give some advice: