js-quantities
js-quantities copied to clipboard
Human readable value in contrained range
Hi,
Thanks a lot for that library. Looks very useful for now.
I would like to know the best way to pick a "best" unit for human readable value.
I have to deal with metrics of about mm/um/nm` so in my case i can check easially what the best unit i can pick.
But for the general use case, what would be the best approach to render a value between 0.1 < < 999
, by picking the appropriate prefix p/n/u/m//k/M/G
to match the constraint? Looking at the documentation i really have no idea.
Thanks a lot.
I did some work around.
Here is an implementation outside of the lib.
It is probably very specific use case, but if you would like to provide on your side a shiftUnit
and something like getUnitPlace
, it would be very useful to reduce the size of such code.
If you like that idea, i can create a pull request.
const PREFIX = {
'<tera>': 12,
'<giga>': 9,
'<mega>': 6,
'<kilo>': 3,
'<1>': 0,
'<milli>': -3,
'<micro>': -6,
'<nano>': -9
};
const PLACES_TO_PREFIX = {};
for (const [key, value] of Object.entries(PREFIX)) {
PLACES_TO_PREFIX[value] = key;
}
/**
* Returns a quantity with a unit inside a readable range of values
*
* The prefix of the unit (n/u/k/M) is picked to make the value
* in the range 0.1 .. 999
*/
export function toHumanReadableUnit(quantity) {
if (quantity.scalar === 0) {
return quantity;
}
function getNextDeltaPlace(q) {
const v = q.scalar;
if (v > 200) {
return 3;
}
if (v < 0.3) {
return -3;
}
return 0;
}
function getUnitWithNewPrefix(q, prefix) {
function cleanup(elemString) {
if (elemString === undefined) return '';
if (elemString === '<1>') return '';
return elemString.substring(1, elemString.length - 1);
}
let n = '';
for (let ni = 0; ni < q.numerator.length; ni += 1) {
const elem = q.numerator[ni];
if (ni === 0) {
const notPrefix = PREFIX[elem] === undefined;
n += cleanup(prefix) + (notPrefix ? cleanup(elem) : '');
} else {
n += cleanup(elem);
}
}
let d = '';
for (let di = 0; di < q.denominator.length; di += 1) {
const elem = q.denominator[di];
d += cleanup(elem);
}
if (d === '') {
return n;
}
if (n === '') {
return `1/${d}`;
}
return `${n}/${d}`;
}
function getCurrentPlace(q) {
return PREFIX[q.numerator[0]] || 0;
}
function shiftUnit(q, deltaPlace) {
const place = getCurrentPlace(q) + deltaPlace;
const prefixNumerator = PLACES_TO_PREFIX[place];
if (prefixNumerator === undefined) {
return q;
}
const unit = getUnitWithNewPrefix(quantity, prefixNumerator);
return q.to(unit);
}
let current = quantity;
for (let i = 0; i < 10; i += 1) {
const deltaPlace = getNextDeltaPlace(current);
if (deltaPlace === 0) {
break;
}
current = shiftUnit(current, deltaPlace);
}
// Check if we loose a bit of digits
const fixed = current.scalar.toFixed(2);
if (
fixed.length === 4 &&
fixed.substring(0, 2) === '0.' &&
fixed.substring(3, 4) !== '0'
) {
return shiftUnit(current, -3);
}
return current;
}
import Qty from 'js-quantities';
import { toHumanReadableUnit } from 'helpers/QtyHelper';
describe('Qty helper', () => {
test('check already normalized', async () => {
const q = Qty('10 mm');
const result = toHumanReadableUnit(q);
expect(result.toPrec(0.1).toString()).toEqual('10 mm');
});
test('check with smaller unit than expected', async () => {
const q = Qty('1000 mm');
const result = toHumanReadableUnit(q);
expect(result.toPrec(0.1).toString()).toEqual('1 m');
});
test('check with bigger unit than expected', async () => {
const q = Qty('0.02 mm');
const result = toHumanReadableUnit(q);
expect(result.toPrec(0.1).toString()).toEqual('20 um');
});
test('check with smaller unit than expected', async () => {
const q = Qty('0.0222 mm');
const result = toHumanReadableUnit(q);
expect(result.toPrec(0.1).toString()).toEqual('22.2 um');
});
test('check with more than 2 precision digit', async () => {
const q = Qty('0.222 mm');
const result = toHumanReadableUnit(q);
expect(result.toPrec(0.1).toString()).toEqual('222 um');
});
test('check with a value bigger than 200', async () => {
const q = Qty('0.500 mm');
const result = toHumanReadableUnit(q);
expect(result.toPrec(0.1).toString()).toEqual('0.5 mm');
});
test('check above rounding error', async () => {
const q = Qty('0.995 mm');
const result = toHumanReadableUnit(q);
expect(result.toPrec(0.1).toString()).toEqual('995 um');
});
test('check under rounding error', async () => {
const q = Qty('0.996 mm');
const result = toHumanReadableUnit(q);
expect(result.toPrec(0.1).toString()).toEqual('1 mm');
});
});
Duplicate of #87 ?