Textualize/rich

[REQUEST] Support width-aware number formatting

Opened this issue · 1 comments

Have you checked the issues for a similar suggestions?

Yes, but I'm not certain I didn't miss a relevant issue.

How would you improve Rich?

I'd like to improve the rendering for reviewing tables of accounting data at a glance. Numeric amounts are easy to skim (e.g. for order of magnitude) when they're right-aligned, but the inclusion of a $ makes it harder to separate the numbers from the units. Left-aligning the columns avoids the dollar signs becoming noise at the cost of making the numbers harder to skim.

I'd love to be able to either specify left-aligned units in the column itself or to be able to compose together to different pieces of text with different alignments on the same line/row.

In the following example, the suboptimal alignment is visible:

# example.py
from fractions import Fraction

from rich.console import Console
from rich.table import Table

entries = [
    (Fraction("10"), Fraction("20.54")),
    (Fraction("933"), Fraction("44.47")),
]

table = Table(title="Investments", show_edge=False)

table.add_column("Lot GUID")
table.add_column("Lot ID", no_wrap=True)
table.add_column("Shares", justify="right")
table.add_column("Cost Basis", justify="right")
table.add_column("Total Basis", justify="right")

for shares, basis in entries:
    table.add_row(
        "ABC",
        "2024-09-28",
        f"{shares:,.2f}",
        f"${basis:,.4f}",
        f"${basis * shares:,.2f}",
    )

console = Console()
console.print(table)
$ python example.py
                        Investments
 Lot GUID │ Lot ID     │ Shares │ Cost Basis │ Total Basis
──────────┼────────────┼────────┼────────────┼─────────────
 ABC      │ 2024-09-28 │  10.00 │   $20.5400 │     $205.40
 ABC      │ 2024-09-28 │ 933.00 │   $44.4700 │  $41,490.51

Ideally, I could ideally produce a table that looks like

$ python example.py
                        Investments
 Lot GUID │ Lot ID     │ Shares │ Cost Basis │ Total Basis
──────────┼────────────┼────────┼────────────┼─────────────
 ABC      │ 2024-09-28 │  10.00 │ $  20.5400 │ $    205.40
 ABC      │ 2024-09-28 │ 933.00 │ $  44.4700 │ $ 41,490.51

I tried Group(Align("$"), Align("41,490.51", align="right")), which produces the suboptimal:

Group-align rendering implementation:
from fractions import Fraction

from rich.align import Align
from rich.console import Console, Group
from rich.table import Table

entries = [
    (Fraction("10"), Fraction("20.54")),
    (Fraction("933"), Fraction("44.47")),
]

table = Table(title="Investments", show_edge=False)

table.add_column("Lot GUID")
table.add_column("Lot ID", no_wrap=True)
table.add_column("Shares", justify="right")
table.add_column("Cost Basis", justify="right")
table.add_column("Total Basis", justify="right")

for shares, basis in entries:
    table.add_row(
        "ABC",
        "2024-09-28",
        f"{shares:,.2f}",
        Group(Align("$"), Align(f"${basis:,.4f}", align="right")),
        Group(Align("$"), Align(f"${basis * shares:,.2f}", align="right")),
    )

console = Console()
console.print(table)
                        Investments
 Lot GUID │ Lot ID     │ Shares │ Cost Basis │ Total Basis
──────────┼────────────┼────────┼────────────┼─────────────
 ABC      │ 2024-09-28 │  10.00 │ $          │ $
          │            │        │   $20.5400 │     $205.40
 ABC      │ 2024-09-28 │ 933.00 │ $          │ $
          │            │        │   $44.4700 │  $41,490.51

I also tried a Layout-based approach, which produces the following giant wall of whitespace:

Layout-based rendering implementation
from fractions import Fraction

from rich.align import Align
from rich.console import Console
from rich.layout import Layout
from rich.table import Table


def format_dollars(value: Fraction):
    layout = Layout()
    dollar_sign = Layout("$")
    dollar_sign.size = 1
    layout.split_row(dollar_sign, Layout(Align(f"{value:,.2f}", align="right")))
    layout.size = 1
    return layout


entries = [
    (Fraction("10"), Fraction("20.54")),
    (Fraction("933"), Fraction("44.47")),
]

table = Table(title="Investments", show_edge=False)

table.add_column("Lot GUID")
table.add_column("Lot ID", no_wrap=True)
table.add_column("Shares", justify="right")
table.add_column("Cost Basis", justify="right")
table.add_column("Total Basis", justify="right")

for shares, basis in entries:
    table.add_row(
        "ABC",
        "2024-09-28",
        f"{shares:,.2f}",
        format_dollars(basis),
        format_dollars(basis * shares),
    )

console = Console()
console.print(table)
Layout-based rendering output
                                                      Investments
 Lot GUID │ Lot ID     │ Shares │                                Cost Basis │                              Total Basis
──────────┼────────────┼────────┼───────────────────────────────────────────┼──────────────────────────────────────────
 ABC      │ 2024-09-28 │  10.00 │ $                                   20.54 │ $                                 205.40
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
 ABC      │ 2024-09-28 │ 933.00 │ $                                   44.47 │ $                              41,490.51
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │
          │            │        │                                           │

A few ideal solutions for my use case:

  • add console markup syntax for alignment (e.g. [left]$[/][right]41,490.51[/])
  • add explicit column unit support (suffix units like °C are already well-supported because you can just right-align the entire column)
  • support Align renderables in a single Group without introducing extra lines

In addition to this specific formulation, I'd also love to be able to render negatives in a similar fashion:

                               Investments
 Lot GUID │ Lot ID     │ Shares │ Cost Basis │ Total Basis │   Gain(Loss)
──────────┼────────────┼────────┼────────────┼─────────────┼──────────────
 ABC      │ 2024-09-28 │  10.00 │   $20.5400 │     $205.40 │ ($    24.00)
 ABC      │ 2024-09-28 │ 933.00 │   $44.4700 │  $41,490.51 │ ($24,565.89)

Related:

  • #2234 (two of the solutions could probably generalize to support aligning columns at the decimal point)

We found the following entry in the FAQ which you may find helpful:

Feel free to close this issue if you found an answer in the FAQ. Otherwise, please give us a little time to review.

This is an automated reply, generated by FAQtory