--- url: https://chartbuddy.io/embed/docs/configuration/number-formats.md --- > Docs index: [llms.txt](https://chartbuddy.io/embed/docs/llms.txt) # Number formats Every numeric surface uses the same **spec** shape. There is no separate sibling `format` bag. ```js { mode: 'custom', // or 'linked' source: undefined, // only when mode is 'linked' customFormat: { prefix: '', postfix: '', thousandSeparator: ',', decimalSeparator: '.', decimalPlaces: 0, forceDecimals: true, signDisplay: 'auto', // 'auto' | 'always' | 'parens' scaleDown: 0, }, } ``` | Field | Role | |---|---| | `mode` | `'custom'` — this surface owns `customFormat`. `'linked'` — inherit another surface | | `source` | When linked: `'primaryRangeAxis'` | `'dataLabels'` | `'totalLabels'` | | `customFormat` | The formatting knobs (also kept as an unlink stash when linked) | | `prefix` / `postfix` | Strings before / after the number (`postfix: '%'` on percentage charts) | | `thousandSeparator` / `decimalSeparator` | Grouping and decimal marks | | `decimalPlaces` | Digits after the decimal | | `forceDecimals` | Pad to `decimalPlaces` even with trailing zeros | | `signDisplay` | `'auto'` | `'always'` | `'parens'` | | `scaleDown` | Scale exponent (`0` = raw; higher values shorten large magnitudes) | **Not a number format:** category date patterns live at `axes..labels.dateFormat.format` (a string like `'MMM D, YYYY'`). *** ## 1. Axes Value-axis tick labels. Path is always under the side that holds that axis for the current orientation. | Surface | Path | Notes | |---|---|---| | Primary value axis (Y on vertical charts) | `axes.left.labels.numberFormat` | Usually the main “Y-axis” format hub | | Primary value axis (Y on horizontal charts) | `axes.bottom.labels.numberFormat` | Same role after orientation flip | | Secondary value axis | `axes.right` (vertical) or `axes.top` (horizontal) `.labels.numberFormat` | Dual-axis; typically `mode: 'custom'` only — see [Secondary](https://chartbuddy.io/embed/docs/axes/secondary.md) | | Domain / category axis | `axes..labels.numberFormat` | Rarely used for numbers; mekko-style % domain ticks are the main case | Link targets elsewhere refer to the primary value axis as `source: 'primaryRangeAxis'` (not the side name). ```js axes: { left: { labels: { numberFormat: { mode: 'custom', customFormat: { prefix: '$', postfix: '', thousandSeparator: ',', decimalSeparator: '.', decimalPlaces: 0, forceDecimals: true, signDisplay: 'auto', scaleDown: 0, }, }, }, }, } ``` *** ## 2. Chart labels Whole-chart label formats under `annotations`. ### Data labels | Surface | Path | Default / notes | |---|---|---| | Main data-label format | `annotations.dataLabels.numberFormat` | Hub used by Absolute when Absolute is linked | | Absolute part | `annotations.dataLabels.numberFormatAbsolute` | Defaults to `mode: 'linked', source: 'dataLabels'` | | Percentage part | `annotations.dataLabels.numberFormatPercentage` | Defaults to custom with `postfix: '%'` | Which part paints depends on `annotations.dataLabels.labelParts` (e.g. `['absolute']`, `['percentage']`, `['absolute','percentage']`). Pie / donut slice labels also use these Absolute / Percentage helpers. ```js annotations: { dataLabels: { labelParts: ['absolute'], numberFormat: { mode: 'custom', customFormat: { /* … */ }, }, numberFormatAbsolute: { mode: 'custom', // or keep linked to 'dataLabels' customFormat: { /* … */ }, }, numberFormatPercentage: { mode: 'custom', customFormat: { prefix: '', postfix: '%', thousandSeparator: ',', decimalSeparator: '.', decimalPlaces: 0, forceDecimals: true, signDisplay: 'auto', scaleDown: 0, }, }, }, } ``` ### Stack / column totals | Surface | Path | Charts | |---|---|---| | Totals | `annotations.totals.numberFormat` | Stacked bar / `stackedBar100`, mekko, bar mekko, waterfall | Not used on line or area (no stack totals). Changing the Y-axis does **not** change totals unless totals are linked to `primaryRangeAxis`. ```js annotations: { totals: { visible: true, numberFormat: { mode: 'custom', customFormat: { /* … */ }, }, }, } ``` *** ## 3. Annotation overlays Per-object formats. Each arrow / line has its own spec (not shared chart-wide). ### Level lines | Surface | Path | |---|---| | Level-line value label | `annotations.levelLines..labelNumberFormatSpec` | Same shape as `numberFormat`. Default when unset: follow the primary Y-axis (`linked` → `primaryRangeAxis`). Lines bound to the secondary axis follow that axis’s custom format. See [Level lines](https://chartbuddy.io/embed/docs/configuration/level-lines.md). ```js annotations: { levelLines: { target: { value: 70, labelNumberFormatSpec: { mode: 'custom', customFormat: { /* … */ }, }, }, }, } ``` ### Arrows (difference, CAGR, level) Each entry under these maps can carry Absolute and Percentage specs: | Kind | Map path | Absolute | Percentage | |---|---|---|---| | Difference | `annotations.differenceArrows.` | `numberFormatAbsolute` | `numberFormatPercentage` | | CAGR | `annotations.cagrs.` | `numberFormatAbsolute` | `numberFormatPercentage` | | Level | `annotations.levelArrows.` | `numberFormatAbsolute` | `numberFormatPercentage` | Which of Absolute / Percentage appears depends on that arrow’s `displayMode` / `labelParts`. See [Arrows](https://chartbuddy.io/embed/docs/configuration/arrows.md). ```js annotations: { differenceArrows: { q1VsQ2: { start: 0, end: 1, displayMode: 'absoluteWithPercentage', numberFormatAbsolute: { mode: 'custom', customFormat: { /* … */ }, }, numberFormatPercentage: { mode: 'custom', customFormat: { prefix: '', postfix: '%', thousandSeparator: ',', decimalSeparator: '.', decimalPlaces: 1, forceDecimals: true, signDisplay: 'auto', scaleDown: 0, }, }, }, }, } ``` *** ## 4. Linking Three **hubs** can be link sources: primary Y-axis (`primaryRangeAxis`), data labels (`dataLabels`), totals (`totalLabels`). | Target | Can link to | |---|---| | Primary Y-axis | `dataLabels`, `totalLabels` | | Data labels | `primaryRangeAxis`, `totalLabels` | | Totals | `primaryRangeAxis`, `dataLabels` | | Absolute / Percentage parts | `dataLabels`, `primaryRangeAxis`, `totalLabels` | | Arrow Absolute / Percentage | `primaryRangeAxis`, `dataLabels`, `totalLabels` | | Level-line labels | `primaryRangeAxis`, `dataLabels`, `totalLabels` | Hand-authored configs should prefer `mode: 'custom'` so the format does not depend on another surface. ```js // Totals follow the Y-axis annotations: { totals: { numberFormat: { mode: 'linked', source: 'primaryRangeAxis', customFormat: { /* stash if unlinked later */ }, }, }, } ``` *** ## 5. Quick path index | Category | Full path | |---|---| | Primary / secondary / domain axis | `axes..labels.numberFormat` | | Data labels (main) | `annotations.dataLabels.numberFormat` | | Data-label Absolute | `annotations.dataLabels.numberFormatAbsolute` | | Data-label Percentage | `annotations.dataLabels.numberFormatPercentage` | | Stack totals | `annotations.totals.numberFormat` | | Level-line label | `annotations.levelLines..labelNumberFormatSpec` | | Difference arrow Absolute / % | `annotations.differenceArrows..numberFormatAbsolute` / `numberFormatPercentage` | | CAGR arrow Absolute / % | `annotations.cagrs..numberFormatAbsolute` / `numberFormatPercentage` | | Level arrow Absolute / % | `annotations.levelArrows..numberFormatAbsolute` / `numberFormatPercentage` | Percentage chart types (`stackedBar100`, `stackedArea100`, `mekko`) often set `customFormat.postfix: '%'` on the surfaces you care about (usually the primary axis and/or totals).