Patterns and gradients

Most charts tell their series apart by color alone. That leaves out people who see colors differently, and anyone looking at a printout in black and white. The WCAG success criterion 1.4.1 asks for a second way. Patterns are that second way: stripes, dots, or shapes drawn over the color of the series.

Wherever Recharts accepts a fill color for a shape that fills an area, it also accepts a fill object. A fill object has a solid color, and a definition that paints the shapes, such as a pattern or a gradient.

import { pattern, patterns } from 'recharts';

// A color, the same as always
<Bar dataKey="uv" fill="#8884d8" />

// A fill object: a solid color, and a definition that paints the shapes
<Bar dataKey="uv" fill={{ color: '#8884d8', definition: pattern(patterns.diagonal) }} />

// Without a color, the Bar keeps its theme color
<Bar dataKey="uv" fill={{ definition: pattern(patterns.diagonal) }} />

Recharts renders the definition in SVG <defs> with a unique ID, and paints the shapes with it. You never write url(#…) or manage IDs yourself. Everything that needs a plain color uses the color: the Tooltip, the Legend text, and the labels. The Legend icon draws the same pattern.

If the fill object has no color, the shapes keep their color from the theme, and the pattern is drawn in that color. The texture of the built-in patterns is drawn in the pageBackground color of the theme, so it looks cut out of the shape in light and dark mode alike.

Built-in patterns

patterns holds the built-in patterns, and pattern turns one into a definition. The stripes and grids come first, then the same shapes as the Symbols in the Legend and in Scatter, two per tile in staggered rows. dots is the same pattern as circle.

import { Bar, BarChart, RechartsFill, XAxis, YAxis, pattern, patterns } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';

/*
 * One bar for each built-in pattern. Each data entry sets its own fill object,
 * and none of them sets a color, so every bar keeps the theme color of the series.
 */
const names: ReadonlyArray<keyof typeof patterns> = [
  'diagonal',
  'crosshatch',
  'horizontal',
  'vertical',
  'grid',
  'zigzag',
  'circle',
  'cross',
  'diamond',
  'square',
  'star',
  'triangle',
  'wye',
];

const data = names.map(name => {
  const fill: RechartsFill = { definition: pattern(patterns[name]) };
  return { name, value: 1, fill };
});

// Each strip gets a fixed height, so that every pattern is large enough to see, and every label fits
const stripHeight = 40;
const margin = { top: 10, right: 10, left: 10, bottom: 10 };
const chartStyle = { width: '100%', maxWidth: 700, height: names.length * stripHeight + margin.top + margin.bottom };

export default function BuiltInPatternsExample({ isAnimationActive = true }: { isAnimationActive?: boolean }) {
  return (
    <BarChart style={chartStyle} responsive data={data} layout="vertical" margin={margin}>
      <XAxis type="number" hide />
      <YAxis type="category" dataKey="name" width="auto" />
      <Bar dataKey="value" isAnimationActive={isAnimationActive} />
      <RechartsDevtools />
    </BarChart>
  );
}

A pattern is a plain object: the tile size, SVG path data, and how to draw it. Spread one of the built-in patterns to adjust it.

import { pattern, patterns } from 'recharts';

// Built-in patterns are plain objects, so you can adjust them by spreading
const denseDots = pattern({ ...patterns.dots, width: 6, height: 6 });
const darkStripes = pattern({ ...patterns.diagonal, color: '#000', opacity: 0.3 });

Gradients

gradients holds the built-in linear gradients, and linearGradient turns one into a definition. They fade the base color of the fill, so they work with any color. The gradient stretches over each shape: over each bar, or here, over each area.

import { Area, AreaChart, CartesianGrid, RechartsFill, XAxis, YAxis, gradients, linearGradient } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';

const data = [
  { month: 'Jan', desktop: 420, mobile: 240 },
  { month: 'Feb', desktop: 380, mobile: 310 },
  { month: 'Mar', desktop: 510, mobile: 290 },
  { month: 'Apr', desktop: 470, mobile: 400 },
  { month: 'May', desktop: 560, mobile: 380 },
];

// Create the definitions once, outside of components.
const fadeDown: RechartsFill = { definition: linearGradient(gradients.fadeDown) };

const chartStyle = { width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 };
const margin = { top: 10, right: 20, left: 0, bottom: 0 };

export default function GradientsExample({ isAnimationActive = true }: { isAnimationActive?: boolean }) {
  return (
    <AreaChart style={chartStyle} responsive data={data} margin={margin}>
      <CartesianGrid vertical={false} />
      <XAxis dataKey="month" />
      <YAxis width="auto" />
      {/* The gradient fades the theme color of each Area */}
      <Area dataKey="desktop" fill={fadeDown} isAnimationActive={isAnimationActive} />
      <Area dataKey="mobile" fill={fadeDown} isAnimationActive={isAnimationActive} />
      <RechartsDevtools />
    </AreaChart>
  );
}

Custom patterns and definitions

There are two ways to draw your own fill. A custom pattern is a RechartsPattern object: a tile size, and SVG path data drawn in each tile. Recharts paints the base color under it. In small areas, such as a Legend icon, it draws the tiles smaller so that at least three fit.

For full control, write the definition yourself. A definition is a React component that renders an SVG paint server, such as a <linearGradient>, <radialGradient>, or <pattern>. It receives the id to put on its root element, the base color of the fill, and bounds: the size of the painted area, when it paints one area of known size. It renders inside the chart, so it can call hooks, Recharts hooks included.

import {
  Bar,
  BarChart,
  CartesianGrid,
  Legend,
  RechartsFill,
  RechartsFillDefinitionProps,
  RechartsPattern,
  XAxis,
  YAxis,
  pattern,
  useRechartsTheme,
} from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';

const data = [
  { quarter: 'Q1', north: 590, south: 800 },
  { quarter: 'Q2', north: 868, south: 967 },
  { quarter: 'Q3', north: 1397, south: 1098 },
];

// A custom pattern: the tile size, and SVG path data drawn in each tile.
const hearts: RechartsPattern = {
  width: 12,
  height: 12,
  path: 'M6 10.5L1.8 6.3a2.4 2.4 0 0 1 3.4-3.4L6 3.7l.8-.8a2.4 2.4 0 0 1 3.4 3.4z',
};

/*
 * A custom definition: any component that renders an SVG paint server with the `id` it receives.
 * It gets the base color of the fill, and it can call hooks, like useRechartsTheme here.
 */
function FadeToBackground({ id, color }: RechartsFillDefinitionProps) {
  const theme = useRechartsTheme();
  return (
    <linearGradient id={id} x1={0} y1={0} x2={0} y2={1}>
      <stop offset={0} stopColor={color} />
      <stop offset={1} stopColor={theme?.pageBackground ?? '#fff'} />
    </linearGradient>
  );
}

const heartsFill: RechartsFill = { definition: pattern(hearts) };
const fadeFill: RechartsFill = { definition: FadeToBackground };

const chartStyle = { width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 };
const margin = { top: 10, right: 20, left: 0, bottom: 0 };

export default function CustomFillsExample({ isAnimationActive = true }: { isAnimationActive?: boolean }) {
  return (
    <BarChart style={chartStyle} responsive data={data} margin={margin}>
      <CartesianGrid vertical={false} />
      <XAxis dataKey="quarter" />
      <YAxis width="auto" />
      <Legend iconSize={24} />
      <Bar dataKey="north" fill={heartsFill} isAnimationActive={isAnimationActive} />
      <Bar dataKey="south" fill={fadeFill} isAnimationActive={isAnimationActive} />
      <RechartsDevtools />
    </BarChart>
  );
}

Create definitions once, outside of your components. A new component on every render makes React remount the definition every time.

The Legend icon draws the definition with its own ID, and gives it the size of the icon as bounds. A definition sized in pixels, such as a <pattern> with patternUnits="userSpaceOnUse", can read bounds to fit the small icon. Recharts also passes bounds from a ReferenceArea, a ReferenceDot, and the Rectangle, Dot, Trapezoid, and Symbols shapes. It is undefined where one definition paints shapes of different sizes, such as every bar of a series. Gradients in the default units stretch over each shape anyway.

A fill for each data entry

Graphical items that draw one shape per data entry, like Bar, Pie, Funnel, RadialBar, Scatter, Treemap, SunburstChart, and the nodes of Sankey, read fill from each data entry too. A data entry can set a color, or a fill object. The data entry wins over the graphical item.

const data = [
  { name: 'Search', value: 400 },
  // This sector paints with its own pattern
  { name: 'Direct', value: 300, fill: { definition: pattern(patterns.star) } },
  // This sector has its own color, and no pattern
  { name: 'Social', value: 300, fill: '#ff7300' },
];

<Pie data={data} dataKey="value" fill={{ definition: pattern(patterns.diagonal) }} />

Patterns in a theme

The fill of each theme entry in graphicalItems can be a fill object too, the same as the fill of a graphical item. Graphical items without a fill of their own then paint with it. Give each entry a different pattern, and every chart that uses the theme tells its series apart without relying on color alone, without setting a single fill.

import {
  Legend,
  Pie,
  PieChart,
  RechartsTheme,
  RechartsThemeProvider,
  Tooltip,
  lightTheme,
  pattern,
  patterns,
  useRechartsTheme,
} from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';

const data = [
  { name: 'Search', value: 400 },
  { name: 'Direct', value: 300 },
  { name: 'Social', value: 300 },
  { name: 'Email', value: 200 },
  { name: 'Referral', value: 100 },
];

// One pattern for each theme entry. Create the definitions once, outside of components.
const seriesPatterns = [
  pattern(patterns.diagonal),
  pattern(patterns.circle),
  pattern(patterns.crosshatch),
  pattern(patterns.triangle),
  pattern(patterns.zigzag),
];

// Adds a pattern to each graphical item of a theme, and keeps its colors.
function withPatterns(theme: RechartsTheme): RechartsTheme {
  return {
    ...theme,
    graphicalItems: theme.graphicalItems.map((item, index) => {
      const definition = seriesPatterns[index % seriesPatterns.length];
      if (definition == null) {
        return item;
      }
      return { ...item, fill: { color: typeof item.fill === 'object' ? item.fill.color : item.fill, definition } };
    }),
  };
}

const chartStyle = { width: '100%', maxWidth: 500, maxHeight: '70vh', aspectRatio: 1.2 };

export default function ThemePatternsExample({ isAnimationActive = true }: { isAnimationActive?: boolean }) {
  // Build on the active theme, so that the example follows light and dark mode
  const theme = useRechartsTheme() ?? lightTheme;
  const patternedTheme = withPatterns(theme);
  return (
    <RechartsThemeProvider value={patternedTheme}>
      {/* Pie takes one theme entry per sector, so each sector gets its own color and pattern */}
      <PieChart style={chartStyle} responsive>
        <Pie data={data} dataKey="value" nameKey="name" isAnimationActive={isAnimationActive} />
        <Legend iconSize={24} />
        <Tooltip />
        <RechartsDevtools />
      </PieChart>
    </RechartsThemeProvider>
  );
}

Pie, Funnel, Treemap, SunburstChart, and Sankey take one theme entry per sector, trapezoid, branch, or node. The other graphical items take one per series. A graphical item or data entry that sets its own color does not take the theme pattern. A theme with fill objects holds React components, so it cannot be stored as JSON.

The other theme styles that fill an area take fill objects too: grid, reference, brush, barBackground, and cursor. A grid, reference element, Brush, background, or cursor without a fill of its own paints with the fill object of the theme. The active styles of a theme entry and the cross cursor of a ScatterChart use the color.

import { lightTheme, pattern, patterns } from 'recharts';

const theme = {
  ...lightTheme,
  grid: { ...lightTheme.grid, fill: { color: '#fafafa', definition: pattern(patterns.dots) } },
  reference: { ...lightTheme.reference, fill: { color: '#eee', definition: pattern(patterns.diagonal) } },
};

Where fill objects work

WhatWhere
Graphical itemsThe fill of Bar, Area, Radar, Scatter, Pie, RadialBar, Funnel, Treemap, and SunburstChart, and the fill of their data entries
Reference elements and gridsThe fill of ReferenceArea, ReferenceDot, CartesianGrid, and PolarGrid, and each entry of horizontalFill and verticalFill
Active shapes and backgroundsThe object form of background, activeBar, activeShape, activeDot, dot, and the Tooltip cursor
Other componentsThe background of Brush, and the shapes: Rectangle, Sector, Trapezoid, Polygon, Symbols, Curve, and Dot
const stripes = { color: '#eee', definition: pattern(patterns.diagonal) };

<CartesianGrid horizontalFill={['#fafafa', stripes]} />
<ReferenceArea x1="Feb" x2="Apr" fill={stripes} />
<Bar dataKey="uv" background={{ fill: stripes }} activeBar={{ fill: stripes }} />
<Tooltip cursor={{ fill: stripes }} />

The shapes, active shapes, backgrounds, and the cursor have no theme color to fall back to, so give their fill objects a color. Labels and text keep plain colors, and so do the lines of a Line, the links of a Sankey, and the Brush travellers.