[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 singleGroup
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)