Data

Chart

The frame for a Recharts chart, and five recipes to start from. Series take a palette color by name, so every theme recolors them. Axes are compact, the tooltip shows each value in full, through Amount for money. A legend appears from three series; name one or two in the card. With data and categoryKey, a button shows the same numbers as a Table. The plot mounts when it comes on screen, so its entrance is seen, and holds still with reduced motion. Only horizontal grid lines and no frame: the Card is the frame.

view .md
Preview
spendingOct and Nov projected
Installation
terminal
bash
npx @entrepta/cli@latest add chart

// Resolves dependencies, copies the source, and installs npm packages automatically.

Usage
chart.tsx
tsx
import {
  ChartContainer, ChartTooltip, ChartTooltipContent,
  chartColor, chartCursor, chartGrid, chartProjection, chartXAxis, chartYAxis,
  formatChartValue, useChartMotion,
} from "@/components/entrepta/chart"
import { formatMoney } from "@/lib/format"
import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Label, Pie, PieChart,
  ReferenceLine, XAxis, YAxis } from "recharts"

// money in minor units; the axis compact, the tooltip and the table in full
const compact = (v: number) => formatChartValue(v, "money", { currency: "USD", compact: true })
// inside your component: no entrance with reduced motion
const motion = useChartMotion()

// Area with a projection. The months to come are dashed, fainter and named projected.
<ChartContainer
  config={{
    spent: { label: "spent", color: "chart-1", format: "money" },
    projected: { label: "projected", color: "chart-1", format: "money" },
  }}
  height={260}
  label="Spending by month, October and November projected"
  data={months}
  categoryKey="month"
>
  <AreaChart data={months} margin={{ top: 8, right: 8, left: -4, bottom: 0 }}>
    <defs>
      <linearGradient id="fill-spent" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" style={{ stopColor: "var(--color-spent)", stopOpacity: 0.35 }} />
        <stop offset="100%" style={{ stopColor: "var(--color-spent)", stopOpacity: 0 }} />
      </linearGradient>
    </defs>
    <CartesianGrid {...chartGrid} />
    <XAxis dataKey="month" {...chartXAxis} />
    <YAxis {...chartYAxis} tickFormatter={compact} />
    <ChartTooltip cursor={{ stroke: "var(--chart-grid)" }} content={<ChartTooltipContent />} />
    <Area dataKey="spent" stroke="var(--color-spent)" strokeWidth={2} fill="url(#fill-spent)" {...motion} />
    <Area dataKey="projected" stroke="var(--color-projected)" fill="var(--color-projected)" {...chartProjection} {...motion} />
  </AreaChart>
</ChartContainer>

// Grouped bars. Two series side by side; name them in the card, so no legend.
<ChartContainer
  config={{
    income: { label: "income", color: "chart-2", format: "money" },
    spent: { label: "spent", color: "chart-1", format: "money" },
  }}
  height={260}
  label="Income and spending by month"
  data={months}
  categoryKey="month"
>
  <BarChart data={months} barGap={4}>
    <CartesianGrid {...chartGrid} />
    <XAxis dataKey="month" {...chartXAxis} />
    <YAxis {...chartYAxis} tickFormatter={compact} />
    <ChartTooltip cursor={chartCursor} content={<ChartTooltipContent />} />
    <Bar dataKey="income" fill="var(--color-income)" radius={[4, 4, 0, 0]} {...motion} />
    <Bar dataKey="spent" fill="var(--color-spent)" radius={[4, 4, 0, 0]} {...motion} />
  </BarChart>
</ChartContainer>

// Diverging bars. Above and below zero: the one place the status colors appear, always with a sign.
<ChartContainer
  config={{ net: { label: "net", color: "chart-1", format: "money", signed: true } }}
  height={260}
  label="What was left each month, income minus spending"
  data={net}
  categoryKey="month"
>
  <BarChart data={net}>
    <CartesianGrid {...chartGrid} />
    <XAxis dataKey="month" {...chartXAxis} />
    <YAxis {...chartYAxis} tickFormatter={compact} />
    <ReferenceLine y={0} stroke="var(--border-strong)" />
    <ChartTooltip cursor={chartCursor} content={<ChartTooltipContent />} />
    <Bar dataKey="net" radius={4} {...motion}>
      {net.map((row) => (
        <Cell key={row.month} fill={chartColor(row.net < 0 ? "error" : "success")} />
      ))}
    </Bar>
  </BarChart>
</ChartContainer>

// Stacked bars. Parts of a whole over time; three series, so the legend shows.
<ChartContainer
  config={{
    home: { label: "home", color: "chart-1", format: "money" },
    out: { label: "out", color: "chart-3", format: "money" },
    learning: { label: "learning", color: "chart-5", format: "money" },
  }}
  height={280}
  label="Spending by category and month"
  data={byCategory}
  categoryKey="month"
>
  <BarChart data={byCategory}>
    <CartesianGrid {...chartGrid} />
    <XAxis dataKey="month" {...chartXAxis} />
    <YAxis {...chartYAxis} tickFormatter={compact} />
    <ChartTooltip cursor={chartCursor} content={<ChartTooltipContent />} />
    <Bar dataKey="home" stackId="month" fill="var(--color-home)" {...motion} />
    <Bar dataKey="out" stackId="month" fill="var(--color-out)" {...motion} />
    <Bar dataKey="learning" stackId="month" fill="var(--color-learning)" radius={[4, 4, 0, 0]} {...motion} />
  </BarChart>
</ChartContainer>

// Donut. Five slices at most; past that, a BarList reads better.
<ChartContainer config={categoryConfig} height={260} label="Spending by category in September">
  <PieChart>
    <ChartTooltip content={<ChartTooltipContent nameKey="name" hideLabel />} />
    <Pie data={slices} dataKey="value" nameKey="name" innerRadius="62%" outerRadius="88%"
      paddingAngle={2} cornerRadius={4} stroke="none" {...motion}>
      {slices.map((s) => <Cell key={s.name} fill={`var(--color-${s.name})`} />)}
      {/* the total in the hole: the pie sits at the plot's center */}
      <Label position="center" content={() => (
        <text x="50%" y="50%" textAnchor="middle" className="fill-[var(--fg-primary)] font-mono text-heading-md">
          {formatMoney(total, { currency: "USD" })}
        </text>
      )} />
    </Pie>
  </PieChart>
</ChartContainer>
Props
PropTypeDefaultDescription
configRecord<key, { label, color, format? }>-color is a palette key ("chart-3", "success") or a CSS color; format is "money", "number" or "percent"; signed puts + and the real minus on every value
heightnumber-The whole chart, legend and toggle included
labelstring-The chart's name: what it shows
descriptionstring-The trend in words, as the plot's desc
legendbooleanfrom three seriesThe legend, built from config
data, categoryKeyrows, string-With both, the View as table button
currency, localestring-Fall back to the FormatProvider's
formatChartValue(value, format, { currency, compact })string-For an axis tickFormatter: $2.2K compact, $2,184.50 in full
chartGrid, chartXAxis, chartYAxis, chartCursor, chartProjectionprops-Presets to spread on the Recharts parts
Active theme: entrepta, dark mode.