d3-dsv

Parsing and Formatting with d3-dsv

d3-dsv parses text you already have (d3.csvParse, d3.tsvParse, d3.dsvFormat(';')) and writes it back (d3.csvFormat); d3-format and d3-time-format turn numbers and dates into labels. A row function converts each record as it is parsed, which is clearer than autoType when types matter:

dsv.mjs: parsing, converting and formatting in NodeJavaScript
import { csvParse, format, utcParse } from 'd3';
const text = 'month,book,units\n2025-12,5,91\n2025-12,1,82\n';      // two December rows
console.log(csvParse(text)[0]);                                     // every value a string
const rows = csvParse(text, ({ month, book, units }) =>             // a row function
  ({ month: utcParse('%Y-%m')(month), book: +book, units: +units }));
console.log(rows[0]);
const usd = format('$,.2f'), pct = format('.1%'), si = format('.2~s');
console.log(usd(91 * 16.2), pct(1218 / 5425), si(118551.96), format('+d')(-169));
Output
{ month: '2025-12', book: '5', units: '91' }
{ month: 2025-12-01T00:00:00.000Z, book: 5, units: 91 }
$1,474.20 22.5% 120k −169

Specifiers follow Python's mini-language ($,.2f currency, .1% percent, ~s SI prefix). The minus is a true U+2212, d3-format's default, which d3.formatLocale() can change. d3-dsv also installs a csv2json command.

Editable CSV parsed with d3.csvParse and a row function, charted and labeled with d3-formatHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; background: #fafaf7; font: 12px system-ui, sans-serif; color: #263238; max-width: 600px; }
  .row { display: flex; gap: 10px; }
  textarea { width: 190px; height: 150px; font: 12px monospace; }
  svg { flex: 1; min-width: 0; }
  pre { margin: 4px 0; }
</style>
<script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
<div class="row">
<textarea id="csv">month,book,units
2025-12,5,124
2025-12,1,111
2025-12,3,100
2025-12,2,84
2025-12,6,64
2026-03,4,100</textarea>
<svg viewBox="0 0 380 160" font-size="11"></svg>
</div>
<pre id="out"></pre>
<script>
  const titles = { 1: 'The Quiet Harbor', 2: 'Patterns of the Deep Web', 3: 'Salt and Saffron',
    4: 'Small Steps to Big Summits', 5: "The Clockmaker's Paradox", 6: 'Gardens in Glass' };
  const usd = d3.format('$,.2f'), pct = d3.format('.1%'), si = d3.format('.2~s'), month = d3.utcFormat('%b %Y');

  function update() {
    const text = document.getElementById('csv').value;
    const raw = d3.csvParse(text);                                  // every value is a string
    // A row function converts each record as it is parsed
    const rows = d3.csvParse(text, ({ month, book, units }) =>
      ({ month: d3.utcParse('%Y-%m')(month), book: +book, units: +units }));
    const total = d3.sum(rows, d => d.units);
    const y = d3.scaleBand(rows.map((d, i) => i), [0, 150]).padding(0.15);
    const x = d3.scaleLinear([0, d3.max(rows, d => d.units) || 1], [0, 170]);
    const g = d3.select('svg').selectAll('g').data(rows).join(enter => {
      const g = enter.append('g');
      g.append('rect').attr('fill', '#1f5f8b');
      g.append('text').attr('class', 't').attr('dy', '0.35em');
      g.append('text').attr('class', 'v').attr('dy', '0.35em');
      return g;
    }).attr('transform', (d, i) => `translate(0,${y(i)})`);
    g.select('rect').attr('x', 130).attr('height', y.bandwidth()).attr('width', d => x(d.units) || 0);
    g.select('.t').attr('x', 125).attr('y', y.bandwidth() / 2).attr('text-anchor', 'end')
      .text(d => `${titles[d.book] ?? 'book ' + d.book}`.slice(0, 20));
    g.select('.v').attr('x', d => 134 + (x(d.units) || 0)).attr('y', y.bandwidth() / 2)
      .text(d => `${d.units} (${pct(d.units / total)})`);
    d3.select('#out').text(`csvParse without a row function: ${JSON.stringify(raw[0])}\n` +
      `with one: month ${rows[0] && month(rows[0].month)}, units ${rows[0]?.units} (a number)\n` +
      `formats: ${usd(124 * 16.2)}  ${pct(1218 / 5425)}  ${si(123979)}  ${d3.format('+d')(-169)}\n` +
      `back to text: ${d3.csvFormat(rows.slice(0, 1).map(d => ({ ...d, month: month(d.month) })))}`.replace(/\n(?=month,)/, ' '));
  }
  document.getElementById('csv').addEventListener('input', update);
  update();
</script>