# Migrating from Recharts
URL: /diagrammatic/docs/migrating-from-recharts

Map a Recharts dashboard onto diagrammatic forms, sizing, axes, tooltips, color, and legends.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

A working map from a Recharts dashboard to `diagrammatic`, written from the parts that actually differ.

Two differences drive everything else. A recharts chart is a client component that measures its container and renders in pixel space; a diagrammatic chart is a server component that renders in pixel space at a stated `width`, so size is a host decision rather than a measurement and only a fluid box needs the client. And a recharts tooltip receives the row; a diagrammatic tooltip receives an address into the data the host already has.

## Forms

| recharts                               | diagrammatic                                                           |
| -------------------------------------- | ---------------------------------------------------------------------- |
| `AreaChart` with one `Area`            | `Area data`                                                            |
| `AreaChart` with several `Area`        | `Area series`                                                          |
| `AreaChart` with `stackId`             | `StackedArea`                                                          |
| `LineChart`                            | `Line data` or `Line series`                                           |
| `BarChart`                             | `Column`, or `Bar` for a ranked horizontal breakdown                   |
| `BarChart layout="vertical"`           | `Bar`                                                                  |
| `BarChart` with `stackId`              | `StackedBar`, plus `normalize` for percent                             |
| several `Bar` side by side             | `GroupedBar`                                                           |
| `PieChart` with `innerRadius`          | `Pie inner center centerLabel`, plus `legend={false}` for a DOM legend |
| `Treemap`                              | `Treemap root`                                                         |
| `ScatterChart` with `ZAxis`            | `Scatter points[].size`                                                |
| `Scatter` with a `Cell` per category   | `Scatter points[].series`                                              |
| `RadarChart`                           | `Radar`                                                                |
| `FunnelChart`                          | `Funnel`                                                               |
| a hand-rolled hour by weekday grid     | `Heatmap mark="dot"`                                                   |
| a hand-rolled contribution calendar    | `Heatmap mark="calendar"`                                              |
| a hand-rolled composition bar          | `SplitBar items block`                                                 |
| a hand-rolled label truncation         | nothing: labels are cut to their margin                                |
| `ResponsiveContainer`                  | `width` and `height`, or `Fit` when the width is fluid                 |
| `CartesianGrid` plus `YAxis tickCount` | `yTicks={4}`                                                           |
| `XAxis ticks={...}`                    | `labels`, one per tick you want printed                                |
| `XAxis tick={{ fontSize: 12 }}`        | `fontSize={12}` on the chart, every role follows                       |
| `Tooltip content`                      | `Root` plus `Tooltip`, looking the row up by `datum.index`             |
| `Legend`                               | automatic, or `legend={false}` plus the DOM `Legend`                   |
| `isAnimationActive={false}`            | nothing to turn off                                                    |

## Sizing

```
<ResponsiveContainer width="100%" height={180}>
  <AreaChart data={rows}>…</AreaChart>
</ResponsiveContainer>
```

becomes, when the card width is known:

```
<Area data={values} labels={ticks} width={420} height={180} yTicks={4} />
```

and when it is not:

```
<Fit>
  {({ width }) => (
    <Area data={values} labels={ticks} width={width} height={180} yTicks={4} />
  )}
</Fit>
```

Both render in CSS pixels, so the tick labels are 11px whatever the width, and a `fontSize` on the chart is the one knob when the host's small text is another size. A figure given `width` alone takes its height from `aspect`; a figure given both ignores `aspect`. Without any of them a figure is 640 wide and shrinks, whole, inside a narrower host.

The chart text inherits the page font, so the `font-family` a recharts host set through a wrapper class is simply gone.

## Axes

A recharts axis draws itself and formats its own ticks. Here the value axis is opt-in and the category axis is a list of strings.

```
// recharts
<YAxis tickFormatter={fmtCompact} tickCount={4} />
<XAxis dataKey="date" ticks={pickTicks(rows, r => r.date)} tickFormatter={fmtDay} />

// diagrammatic
<Area yTicks={4} format={fmtCompact} labels={pickTicks(rows, 5).map(fmtDay)} />
```

`labels` are spread evenly across the plotted span, so pass as many as you want printed rather than one per point. The widest printed tick sizes the value gutter, so `YAxis width` has no counterpart. `yTicks` on log paper counts in decades; `decades()` helpers in the host can be deleted.

## Tooltips

The tooltip surface stays yours. What changes is where the values come from: the mark carries `data-i` and `data-series`, and the host reads its own row.

```
"use client";
import { Root, Tooltip } from "diagrammatic/interactive";

<Root>
  <Area series={series} labels={ticks} width={width} yTicks={4} />
  <Tooltip>
    {({ datum }) => {
      const row = rows[datum.index ?? 0];
      if (!row) return null;
      return (
        <div className="bg-popover ring-border rounded-lg px-2.5 py-2 shadow-md ring-1">
          <div className="font-mono text-xs">{fmtDay(row.date)}</div>
          {series.map((s) => (
            <Row key={s.name} label={s.name} value={format(row[s.name])} />
          ))}
        </div>
      );
    }}
  </Tooltip>
</Root>;
```

`datum.series` names the hovered series when the form has several, `datum.index2` addresses the second dimension of a grid, and `getSeriesColor(datum.element, name)` reads the rendered color for a swatch. A recharts `payload` mapper that formatted values inline becomes one lookup plus the formatting the host already owns.

Click and selection ride the same delegation: `onMarkClick` replaces a `Bar onClick`, and `highlight` replaces a selected-index style prop.

```
<Root
  onMarkClick={(datum) => router.push(rows[datum.index ?? 0].href)}
  highlight={selected == null ? null : { index: selected }}
>
```

## Color

A shadcn/ui host imports the shipped bridge once:

```
import "diagrammatic/shadcn.css";
```

It maps `--dg-c1` through `--dg-c5` to `--chart-1` through `--chart-5`, ink to `--foreground`, muted to `--muted-foreground`, grid to `--border`, and surface to `--background`. Any other system writes the same lines:

```
[data-dg],
[data-dg-legend] {
  --dg-c1: var(--chart-1);
  --dg-c2: var(--chart-2);
  --dg-c3: var(--chart-3);
  --dg-c4: var(--chart-4);
  --dg-c5: var(--chart-5);
  --dg-grid: var(--color-border);
  --dg-muted: var(--color-muted-foreground);
}
```

A `<Cell fill={chartColor(i)} />` per datum disappears: color follows the series or the category (`points[].series` on a scatter, `categorical` on a bar) and comes from the palette. The palette is seven categories; a host with an eighth ranks and folds the tail into `Other`. The `ChartConfig` that shadcn's wrapper needs to name and color each key becomes nothing: names come from the data and colors from the tokens.

## Legends

A recharts `Legend` is HTML positioned over the plot. Here the built-in legend is drawn inside the figure, and the DOM `Legend` is the counterpart on the page's own type:

```
import { Area, Legend } from "diagrammatic";

<Area series={series} legend={false} width={width} />
<Legend names={series.map((s) => s.name)} className="mt-2 text-xs text-muted-foreground" />
```

It is a plain list, server-safe, colored through the same tokens as the marks.

## Maps

`react-simple-maps` plus a `world-atlas` fetch plus a country-name table becomes `WorldMap` from `diagrammatic/world`, which carries Natural Earth 110m outlines as static path data and addresses countries by ISO 3166-1 alpha-2. Pan and zoom, which `ZoomableGroup` provided, is `Zoom` from `diagrammatic/interactive`.

```
<Root>
  <Zoom max={8}>
    {(view) => (
      <WorldMap
        values={{ US: 412, DE: 96, JP: 74 }}
        view={view}
        title="Sign-ins by country"
      />
    )}
  </Zoom>
  <Tooltip>{({ datum }) => <CountryTip iso={datum.series} />}</Tooltip>
</Root>
```

The function child is what keeps borders and country labels at their drawn weight while the map magnifies; a node child would scale them with the picture.

The name-to-ISO mapping is the host's migration, once, on the data rather than on every render.

## What does not come across

- Dual value axes. Two units on one frame is two figures.
- A `fill` per datum. Category color comes from `series` or `categorical`; an arbitrary color per row stays out, and a token override is how a host recolors.
- Animation props. The charts render once, on the server.
- A wrapper class that restyles chart internals (`[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground`). The tokens are the surface, and the type is already on the page's scale.