Fargho
Fargho

Reputation: 1277

Angular pipe that transforms number to readable fraction

I would like to create an Angular 5 pipe that translates a more readable fraction from a number.

For example:

0,66 -> ⅔
0,25 -> ¼
1.25 -> 1 ¼

Here is what I already have, but I would like to make it more dynamic:

export class FracturePipe implements PipeTransform {
  transform(value: any, args?: any): any {
    let roundedValue = Math.round(Number(value) * 100) / 100

    if (roundedValue === 0.66) {
      return '⅔'
    }
    //..and so on, but maybe there is a better way
  }
}

Any ideas how to do that in a more dymamic way?

Upvotes: 3

Views: 1812

Answers (5)

Nate May
Nate May

Reputation: 4062

I just made this pipe to support recipe portions. It is only for positive values and not comprehensive for all fractions:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'quantity',
  standalone: true,
})
export class QuantityPipe implements PipeTransform {
  transform(quantity: number): string | number {
    const decimal = quantity % 1;
    if (!decimal) return quantity;

    const whole = Math.floor(quantity);
    if (approxEq(decimal, 1 / 2)) return whole ? `${whole} ½` : '½';
    if (approxEq(decimal, 1 / 3)) return whole ? `${whole} ⅓` : '⅓';
    if (approxEq(decimal, 2 / 3)) return whole ? `${whole} ⅔` : '⅔';
    if (approxEq(decimal, 1 / 4)) return whole ? `${whole} ¼` : '¼';
    if (approxEq(decimal, 3 / 4)) return whole ? `${whole} ¾` : '¾';
    if (approxEq(decimal, 1 / 5)) return whole ? `${whole} ⅕` : '⅕';
    if (approxEq(decimal, 2 / 5)) return whole ? `${whole} ⅖` : '⅖';
    if (approxEq(decimal, 3 / 5)) return whole ? `${whole} ⅗` : '⅗';
    if (approxEq(decimal, 4 / 5)) return whole ? `${whole} ⅘` : '⅘';
    if (approxEq(decimal, 1 / 6)) return whole ? `${whole} ⅙` : '⅙';
    if (approxEq(decimal, 5 / 6)) return whole ? `${whole} ⅚` : '⅚';
    if (approxEq(decimal, 1 / 8)) return whole ? `${whole} ⅛` : '⅛';
    if (approxEq(decimal, 3 / 8)) return whole ? `${whole} ⅜` : '⅜';
    if (approxEq(decimal, 5 / 8)) return whole ? `${whole} ⅝` : '⅝';
    if (approxEq(decimal, 7 / 8)) return whole ? `${whole} ⅞` : '⅞';

    return Number(quantity.toFixed(2));
  }
}

const approxEq = (v1: number, v2: number, epsilon: number = 0.005): boolean =>
  Math.abs(v1 - v2) < epsilon;

Upvotes: 0

baumsystems
baumsystems

Reputation: 22

Use one way flow syntax property binding:

<!DOCTYPE html>
<html>
<style>
</style>
<body>
<p>I will display &frac34;</p>
</body>
</html>

export class AngularFractions {

  fraction: any;
  input: number;
  public dynamicFractions(input) {
    this.fraction = '&frac' + input + ';';
  }
}
<div class="row">
  <input class="form-control" (change)="dynamicFractions(input)" [(ngModel)]="input"><br>
  <p [innerHTML]="fraction"></p>
</div>

Upvotes: 0

Tiago Silva
Tiago Silva

Reputation: 2349

Although you can do this using external libraries this is more than possible with typescript, using the Euclidean Algorithm you can calculate the Greatest common divisor between two numbers and divide by your decimal value, where this decimal value is calculated at

const wholeNumber = Math.floor(input);
const decimal = input - wholeNumber;

The return of this pipe is a string first followed by the integer value of the number then the fraction that is calculated ( decimal divided by gcd and bottom of fraction divided by gcd )

Example : 1.3 will return 1 3/10 You then can edit the output of the pipe as your liking

 export class FracturePipe implements PipeTransform {
 transform(value: any, args?: any): any {

  if (value === parseInt(value)) {
        return value.toString();
      } else {
        let top = value.toString().includes('.') ? value.toString().replace(/\d+[.]/, '') : 0;
        const wholeNumber = Math.floor(value);
        const decimal = value - wholeNumber;
        const bottom = Math.pow(10, top.toString().replace('-', '').length);
        if (decimal >= 1) {
          top = +top + (Math.floor(decimal) * bottom);
        } else if (decimal <= -1) {
          top = +top + (Math.ceil(decimal) * bottom);
        }

        const x = Math.abs(this.gcd(top, bottom));
        if (wholeNumber === 0) {
          return (top / x) + '/' + (bottom / x);
        }
        return wholeNumber + ' ' + (top / x) + '/' + (bottom / x);
      }
}

gcd(a: number, b: number) {
    return (b) ? this.gcd(b, a % b) : a;
  }
}

Upvotes: 1

magnattic
magnattic

Reputation: 12988

This library might help, if you pass two values instead of the finished float: https://github.com/ben-ng/vulgarities

var characterFor = require('vulgarities/charFor')
  , vulgarities = require('vulgarities');

console.log(characterFor(1,4)); // Returns "\u00BC"

If you combine this with the answer of EvaldasBuinauskas, you get something like this:

export class FractionPipe implements PipeTransform {
    transform(value: number): string {
        const { numerator, denominator } = new Fraction(value);

        return characterFor(numerator, denominator);
    }
}

Upvotes: 0

Evaldas Buinauskas
Evaldas Buinauskas

Reputation: 14077

I think you could use fractionjs to get it working. I haven't worked with it, but really the problem is number conversion. So if you managed to get those two working together, I think it could be something as follows:

export class FractionPipe implements PipeTransform {
    transform(value: number): string {
        const { numerator, denominator } = new Fraction(value);

        return `${numerator} and ${denominator} formatted`;
    }
}

Upvotes: 0

Related Questions