Functional Weave
Code in TypeScript

charts.format@1.0.0

README.md

2,960 bytes · view raw

# charts.format

The text on a chart: tick labels for numbers, dates and money, SI-prefixed
values for compact labels, and percentages. A group, because each is one way of
labelling an axis and `charts.axis` picks between them.

Every number goes through `text.format-decimal`, so there is never an exponent
(`1e-7`), never binary noise (`0.30000000000000004`) and never `-0`, and all
three languages print the same text. Rounding is half away from zero on the
exact double (`math.round-float`). The minus sign is the ASCII hyphen, not
d3-format's U+2212.

- **formatTick(value, step)** uses the fewest decimal places (0 to 12) that
  write the tick *step* exactly, so every label on an axis has the same number
  of places: with a step of 0.5 the ticks read `0.0`, `0.5`, `1.0` (d3's
  `precisionFixed`). Thousands are grouped with commas: `1,000`. A step of 0,
  as for a single tick, uses the places the value needs. A step that no
  12-place decimal writes exactly (a third) gets 12 places.
- **formatSi(value, significant)** writes the value with an SI prefix and
  that many significant digits, then drops trailing zeros: 1200 is `1.2k`,
  3,400,000 is `3.4M`, 1000 is `1k`, 0.0015 is `1.5m`, 0.5 is `500m`.
  Prefixes run from y (1e-24) to Y (1e24), with µ (U+00B5) for micro, as
  d3-format writes them. The prefix is chosen from the exponent, found by
  comparing against exact powers of ten, not `log10`; a value that rounds up
  across a prefix boundary moves to the next prefix (999.96 to 3 digits is
  `1k`, not `1000`). With fewer significant digits than the whole part has,
  the value rounds to tens or hundreds (12 to 1 digit is `10`). "G" is giga,
  as SI says; finance's "B" for billions is not used.
- **formatPercent(value, decimals)** takes a fraction (0.123 is 12.3%) and
  keeps fixed places: `50.00%`. Multiplying by 100 adds binary noise
  (0.145 x 100 is 14.499999999999998), so the product is cleaned at 12 decimal
  places before being rounded to the places asked for: 0.145 is `15%`, which
  is what anyone reading 0.145 expects, where rounding the raw product says
  14%. No thousands grouping.
- **formatDate(iso, label)** labels a date for the tick interval it stands
  for: day and week ticks `23 Sep` (no leading zero), month `Sep 2026`,
  quarter `Q3 2026` (calendar quarters), year `2026`. English abbreviations
  and no locale lookup, so every language agrees. The date is checked strictly
  by `dates.add-days`.
- **formatMoneyTick(minor, currency)** labels a tick on an axis whose data are
  integer minor units. A tick can fall between them, so it is rounded half away
  from zero to a whole minor unit and printed by `money.format` with the
  currency's own digits and symbol: 50000 GBP is `£500.00`, 1500 JPY is
  `¥1,500`.

Sources: d3-format (Mike Bostock), `precisionFixed` and the `s` type
(https://github.com/d3/d3-format); BIPM, The International System of Units
(SI), 9th edition, 2019, table 7 (prefixes).