# Value labels

> Write each value next to its point or bar with valueLabels(), format the text like a tooltip's, and let labels that don't fit move to where they do.

Some charts are read for their numbers. A reader who needs to know that Q4 made 15, not "about 15", shouldn't have to hover. `valueLabels()` writes each value next to the point or bar that shows it: above points, past the ends of bars, below them for negative values, and in the middle of stacked segments.

It comes from `@chartist-js/essentials`, with its stylesheet after Chartist's:

```ts
import 'chartist/styles.css';
import '@chartist-js/essentials/styles.css';
```

## Labels on bars

Add it as an extra layer, `above` on a preset:

**Profit per quarter** (runs in the browser):

`src/main.ts`:

```ts
import 'chartist/styles.css';
import '@chartist-js/essentials/styles.css';
import './style.css';
import { barChart } from 'chartist';
import { valueLabels } from '@chartist-js/essentials';

// Profit per quarter in thousands: labels past the end of each bar, below
// it for a loss. `high` leaves room above the highest bar for its label.
barChart(
  '#chart',
  {
    labels: ['Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6'],
    series: [[12, -4, 8, 15, -2, 9]]
  },
  {
    high: 18,
    layers: {
      above: [
        valueLabels({
          locale: 'en-US',
          numberFormat: { signDisplay: 'exceptZero' }
        })
      ]
    }
  }
);
```

`src/style.css`:

```css
#chart {
  --ct-bar-width: 24px;
}
```

`index.html`:

```html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script type="module" src="/src/main.ts"></script>
  </head>
  <body>
    <div id="chart" style="height: 50vh"></div>
  </body>
</html>
```

The layer reads the bars and points after the other layers drew them, so it follows stacking, `seriesBarDistance`, horizontal bars and anything a `compose` listener changed, without knowing their options. A bar a listener dropped gets no label.

## Text

The default text is the value along the value axis, written with `Intl.NumberFormat`. `numberFormat` takes its options, and `locale` its locale:

```ts
import { valueLabels } from '@chartist-js/essentials';

export const money = valueLabels({
  locale: 'de-CH',
  numberFormat: { style: 'currency', currency: 'CHF' }
});
```

Set `locale` when charts render on a server: without it, the server's locale writes the numbers.

For anything else, `format(item)` returns the text. It gets the same item as the `format` of [`@chartist-js/tooltips`](https://chartist.dev/packages/tooltips/content-and-styling/): the value, its `meta`, the series and its name, the label at the value's index, and the node's `context`. Return `undefined` to keep the default text, `null` or an empty string for no label, and `\n` for a second line. Here the text comes from each value's `meta`:

**Work orders on three machines** (runs in the browser):

`src/main.ts`:

```ts
import 'chartist/styles.css';
import '@chartist-js/essentials/styles.css';
import './style.css';
import { barChart } from 'chartist';
import { valueLabels } from '@chartist-js/essentials';

// Hours per work order on three machines, stacked: each segment says
// which order it is, from the value's meta.
const order = (value: number, meta: string) => ({ value, meta });

barChart(
  '#chart',
  {
    labels: ['Press', 'Lathe', 'Mill'],
    series: [
      [order(14, 'WO 4182'), order(9, 'WO 4190'), order(11, 'WO 4201')],
      [order(8, 'WO 4185'), order(12, 'WO 4193'), order(6, 'WO 4204')],
      [order(5, 'WO 4187'), order(7, 'WO 4198'), order(10, 'WO 4207')]
    ]
  },
  {
    horizontalBars: true,
    stackBars: true,
    axisY: { offset: 60 },
    layers: {
      above: [
        valueLabels({
          format: item => `${String(item.meta)}\n${String(item.value)} h`
        })
      ]
    }
  }
);
```

`src/style.css`:

```css
/* Bars thick enough for two lines of text inside them. */
#chart {
  --ct-bar-width: 40px;
}

/* Dark text on the light third series. */
.ct-value-label-inside.ct-series-c {
  --ct-value-label-inside-color: #453d3f;
}
```

`index.html`:

```html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script type="module" src="/src/main.ts"></script>
  </head>
  <body>
    <div id="chart" style="height: 50vh"></div>
  </body>
</html>
```

Holes get no label, and `format` isn't called for them. A value of 0 is labeled.

## Where they go

| Option          | What it does                                                                               |
| --------------- | ------------------------------------------------------------------------------------------ |
| `pointPosition` | `above` (default), `below`, `left`, `right` or `center` of a point                         |
| `barPosition`   | `end` (default), `inside-end`, `center` or `inside-base`, from the bar's base to its value |
| `offset`        | The gap in px between the point or the bar's end and the label: 8 for points, 4 for bars   |
| `roles`         | `['point']`, `['bar']` or both (default)                                                   |
| `series`        | The series to label, by name or index; default: all of them                                |
| `overflow`      | `fit` (default) or `show`                                                                  |
| `labels`        | `svg` (default) or `html`                                                                  |

Segments of stacked bars are labeled in their middle, since past a segment's end is the next one. A line drawn without points (`showPoint: false`) gets its labels where the points would be.

### When they don't fit

With `overflow: 'fit'`, a label that doesn't fit inside its bar goes past its end instead; in a stacked segment, it's left out. A label that would leave the chart moves inward, and a bar's label that would leave it at the top goes inside the bar's end. Bars are 10 px thin by default, so a label inside one is cramped: give the chart room instead, with a `high` above the highest value or more `chartPadding`, or make the bars wider with `--ct-bar-width`, as the examples do. `overflow: 'show'` draws every label where its position puts it.

## Styling

Labels look like the axis labels until you say otherwise. Each one has the class `ct-value-label` and its series' class, so one line gives them their series' colors:

**Temperatures in two cities** (runs in the browser):

`src/style.css`:

```css
/* Each label in its series' color, set by its ct-series-* class. */
.ct-value-label {
  --ct-value-label-color: var(--ct-color);
}
```

`src/main.ts`:

```ts
import 'chartist/styles.css';
import '@chartist-js/essentials/styles.css';
import './style.css';
import { lineChart } from 'chartist';
import { valueLabels } from '@chartist-js/essentials';

// Temperatures in two cities, each labeled in its series' color.
lineChart(
  '#chart',
  {
    labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
    series: [
      { name: 'Basel', data: [18, 21, 19, 24, 22] },
      { name: 'Chur', data: [11, 13, 15, 14, 17] }
    ]
  },
  {
    low: 5,
    high: 30,
    layers: {
      above: [valueLabels({ format: item => `${String(item.value)} °C` })]
    }
  }
);
```

`index.html`:

```html
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script type="module" src="/src/main.ts"></script>
  </head>
  <body>
    <div id="chart" style="height: 50vh"></div>
  </body>
</html>
```

| Property                        | What it sets                                                  |
| ------------------------------- | ------------------------------------------------------------- |
| `--ct-value-label-color`        | The text color; default: the axis label color, light and dark |
| `--ct-value-label-inside-color` | The color of labels inside a bar; default: white              |
| `--ct-value-label-size`         | The font size; default: the axis label size                   |

Labels inside or centered in a bar also have `ct-value-label-inside`. `classNames` replaces `group` (`ct-value-labels`), `label` and `inside`.

## SVG, HTML, server and terminal

Labels are SVG `text` by default, whatever the chart's `labels` option says, so they render the same on a server, in an exported image and in [the terminal](https://chartist.dev/packages/terminal/). `labels: 'html'` draws each one as a `span` in a `foreignObject` instead, for CSS boxes: a background, padding, rounded corners.

In listeners and plugins, find them by the role `valueLabel`. Its context has the labeled value, its indexes, the text and where the label ended up. They're keyed like their values, so with [live updates](https://chartist.dev/docs/live-updates/) a label keeps its element and moves with its bar.

## Coming from the label plugins

`chartist-plugin-pointlabels` and `chartist-plugin-barlabels` were 1.x `draw` listeners. `valueLabels()` covers both:

| Plugin option                        | With `valueLabels()`                                          |
| ------------------------------------ | ------------------------------------------------------------- |
| `labelInterpolationFnc`              | `format`, which gets the value and its meta, series and label |
| `labelOffset`, `textAnchor`, `align` | `pointPosition`, `barPosition` and `offset`                   |
| `labelClass`                         | `classNames: { label }`; the default is `ct-value-label`      |
| `showZeroLabels`                     | Zero is labeled; return `null` from `format` to leave it out  |
| `thresholdPercentage`, `startAtBase` | `barPosition: 'inside-end'` or `'inside-base'`, with fitting  |

Add the layer to `layers.above` instead of `plugins`. It draws in `g.ct-value-labels` above all series, so a later series doesn't cover an earlier series' labels.
