Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Human readable value in contrained range #129

Open
vallsv opened this issue Oct 15, 2021 · 2 comments
Open

Human readable value in contrained range #129

vallsv opened this issue Oct 15, 2021 · 2 comments

Comments

@vallsv
Copy link

vallsv commented Oct 15, 2021

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.

@vallsv
Copy link
Author

vallsv commented Oct 15, 2021

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');
  });
});

@adrfantini
Copy link

Duplicate of #87 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants