Skip to main content
AreaChart
@coinbase/cds-web-visualization@3.4.0-beta.8
A chart component that displays data as filled areas beneath lines. Ideal for showing cumulative values, stacked data, or emphasizing volume over time.
Import
import { AreaChart } from '@coinbase/cds-web-visualization'
SourceView source code
Peer dependencies
  • framer-motion: ^10.18.0
View as Markdown

AreaChart is a cartesian chart variant that allows for easy visualization of stacked data.

Basic Example

Loading...
Live Code
<AreaChart
  height={{ base: 150, tablet: 200, desktop: 250 }}
  inset={{ top: 8, bottom: 8, left: 0, right: 0 }}
  series={[
    {
      id: 'prices',
      data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 2, 68, 20, 21, 58],
    },
  ]}
  type="gradient"
  showLines
  showYAxis
  yAxis={{
    showGrid: true,
    width: 32,
  }}
/>

Simple

Loading...
Live Code
<AreaChart
  height={{ base: 150, tablet: 200, desktop: 250 }}
  inset={0}
  series={[
    {
      id: 'prices',
      data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 2, 68, 20, 21, 58],
    },
  ]}
  type="gradient"
  showLines
/>

Stacking

You can use the stacked prop to stack all areas on top of each other. You can also use the stackId prop on a series to create different stack groups. See CartesianChart for more details.

Loading...
Live Code
<AreaChart
  showLines
  stacked
  curve="natural"
  height={{ base: 150, tablet: 200, desktop: 250 }}
  inset={0}
  series={[
    {
      id: 'currentRewards',
      data: [
        100, 150, 200, 280, 380, 500, 650, 820, 1020, 1250, 1510, 1800, 2120, 2470, 2850, 3260,
        3700, 4170,
      ],
      color: 'var(--color-fg)',
    },
    {
      id: 'potentialRewards',
      data: [
        150, 220, 300, 400, 520, 660, 820, 1000, 1200, 1420, 1660, 1920, 2200, 2500, 2820, 3160,
        3520, 3900,
      ],
      color: 'var(--color-fgPositive)',
      LineComponent: DottedLine,
    },
  ]}
  AreaComponent={(props) => <DottedArea {...props} peakOpacity={0.4} baselineOpacity={0.4} />}
  type="dotted"
/>

Negative Values

When an area chart contains negative values, the baseline automatically adjusts to zero instead of the bottom of the chart. The area fills from the data line to the zero baseline, properly showing both positive and negative regions.

Loading...
Live Code
<AreaChart
  height={{ base: 150, tablet: 200, desktop: 250 }}
  inset={0}
  series={[
    {
      id: 'pageViews',
      data: [24, 13, -98, 39, 48, 38, 43],
    },
  ]}
  AreaComponent={(props) => <SolidArea {...props} opacity={0.4} />}
  showLines
  showYAxis
  yAxis={{
    showGrid: true,
  }}
/>

Area Styles

You can have different area styles for each series.

Loading...
Live Code
<AreaChart
  height={{ base: 150, tablet: 200, desktop: 250 }}
  inset={0}
  series={[
    {
      id: 'visitors',
      data: [450, 520, 480, 600, 750, 680, 590],
      label: 'Weekly Visitors',
      color: '#fb4d3d',
      type: 'dotted',
    },
    {
      id: 'repeatVisitors',
      data: [250, 200, 150, 140, 100, 80, 50],
      label: 'Weekly Repeat Visitors',
      color: '#16a34a',
    },
    {
      id: 'signups',
      data: [45, 62, 55, 250, 380, 400, 450],
      label: 'Weekly Signups',
      color: '#2563eb',
      type: 'gradient',
    },
  ]}
/>

Animations

You can configure chart transitions using the transition prop.

Customized Transitions

You can pass in a custom spring based transition to your AreaChart for a custom transition.

Loading...
Live Code
function AnimatedStackedAreas() {
  const dataCount = 20;
  const minYValue = 5000;
  const maxDataOffset = 15000;
  const minStepOffset = 2500;
  const maxStepOffset = 10000;
  const updateInterval = 500;
  const seriesSpacing = 2000;
  const myTransition = { type: 'spring', stiffness: 700, damping: 20 };

  const seriesConfig = [
    { id: 'red', label: 'Red', color: 'rgb(var(--red40))' },
    { id: 'orange', label: 'Orange', color: 'rgb(var(--orange40))' },
    { id: 'yellow', label: 'Yellow', color: 'rgb(var(--yellow40))' },
    { id: 'green', label: 'Green', color: 'rgb(var(--green40))' },
    { id: 'blue', label: 'Blue', color: 'rgb(var(--blue40))' },
    { id: 'indigo', label: 'Indigo', color: 'rgb(var(--indigo40))' },
    { id: 'purple', label: 'Purple', color: 'rgb(var(--purple40))' },
  ];

  const domainLimit = maxDataOffset + seriesConfig.length * seriesSpacing;

  function generateNextValue(previousValue) {
    const range = maxStepOffset - minStepOffset;
    const offset = Math.random() * range + minStepOffset;

    let direction;
    if (previousValue >= maxDataOffset) {
      direction = -1;
    } else if (previousValue <= minYValue) {
      direction = 1;
    } else {
      direction = Math.random() < 0.5 ? -1 : 1;
    }

    let newValue = previousValue + offset * direction;
    newValue = Math.max(minYValue, Math.min(maxDataOffset, newValue));
    return newValue;
  }

  function generateInitialData() {
    const data = [];

    let previousValue = minYValue + Math.random() * (maxDataOffset - minYValue);
    data.push(previousValue);

    for (let i = 1; i < dataCount; i++) {
      const newValue = generateNextValue(previousValue);
      data.push(newValue);
      previousValue = newValue;
    }

    return data;
  }

  const MemoizedDottedArea = memo((props) => (
    <DottedArea {...props} baselineOpacity={1} peakOpacity={1} />
  ));

  function AnimatedChart() {
    const [data, setData] = useState(generateInitialData);

    useEffect(() => {
      const intervalId = setInterval(() => {
        setData((currentData) => {
          const lastValue = currentData[currentData.length - 1] ?? 0;
          const newValue = generateNextValue(lastValue);

          return [...currentData.slice(1), newValue];
        });
      }, updateInterval);

      return () => clearInterval(intervalId);
    }, []);

    const series = seriesConfig.map((config, index) => ({
      id: config.id,
      label: config.label,
      color: config.color,
      data: index === 0 ? data : Array(dataCount).fill(seriesSpacing),
    }));

    return (
      <AreaChart
        overflow="visible"
        stacked
        height={{ base: 200, tablet: 250, desktop: 300 }}
        series={series}
        type="dotted"
        showLines
        AreaComponent={MemoizedDottedArea}
        transition={myTransition}
        inset={0}
        showYAxis
        yAxis={{
          showGrid: true,
          width: 0,
          tickLabelFormatter: () => '',
          domain: { min: 0, max: domainLimit },
        }}
      />
    );
  }

  return <AnimatedChart />;
}

Disable Animations

You can also disable animations by setting the animate prop to false.

Loading...
Live Code
function AnimatedStackedAreas() {
  const dataCount = 20;
  const minYValue = 5000;
  const maxDataOffset = 15000;
  const minStepOffset = 2500;
  const maxStepOffset = 10000;
  const updateInterval = 500;
  const seriesSpacing = 2000;
  const myTransition = { type: 'spring', stiffness: 700, damping: 20 };

  const seriesConfig = [
    { id: 'red', label: 'Red', color: 'rgb(var(--red40))' },
    { id: 'orange', label: 'Orange', color: 'rgb(var(--orange40))' },
    { id: 'yellow', label: 'Yellow', color: 'rgb(var(--yellow40))' },
    { id: 'green', label: 'Green', color: 'rgb(var(--green40))' },
    { id: 'blue', label: 'Blue', color: 'rgb(var(--blue40))' },
    { id: 'indigo', label: 'Indigo', color: 'rgb(var(--indigo40))' },
    { id: 'purple', label: 'Purple', color: 'rgb(var(--purple40))' },
  ];

  const domainLimit = maxDataOffset + seriesConfig.length * seriesSpacing;

  function generateNextValue(previousValue) {
    const range = maxStepOffset - minStepOffset;
    const offset = Math.random() * range + minStepOffset;

    let direction;
    if (previousValue >= maxDataOffset) {
      direction = -1;
    } else if (previousValue <= minYValue) {
      direction = 1;
    } else {
      direction = Math.random() < 0.5 ? -1 : 1;
    }

    let newValue = previousValue + offset * direction;
    newValue = Math.max(minYValue, Math.min(maxDataOffset, newValue));
    return newValue;
  }

  function generateInitialData() {
    const data = [];

    let previousValue = minYValue + Math.random() * (maxDataOffset - minYValue);
    data.push(previousValue);

    for (let i = 1; i < dataCount; i++) {
      const newValue = generateNextValue(previousValue);
      data.push(newValue);
      previousValue = newValue;
    }

    return data;
  }

  const MemoizedDottedArea = memo((props) => (
    <DottedArea {...props} baselineOpacity={1} peakOpacity={1} />
  ));

  function AnimatedChart() {
    const [data, setData] = useState(generateInitialData);

    useEffect(() => {
      const intervalId = setInterval(() => {
        setData((currentData) => {
          const lastValue = currentData[currentData.length - 1] ?? 0;
          const newValue = generateNextValue(lastValue);

          return [...currentData.slice(1), newValue];
        });
      }, updateInterval);

      return () => clearInterval(intervalId);
    }, []);

    const series = seriesConfig.map((config, index) => ({
      id: config.id,
      label: config.label,
      color: config.color,
      // First series gets animated data, others get constant height
      data: index === 0 ? data : Array(dataCount).fill(seriesSpacing),
    }));

    return (
      <AreaChart
        animate={false}
        overflow="visible"
        stacked
        height={{ base: 200, tablet: 250, desktop: 300 }}
        series={series}
        type="dotted"
        showLines
        AreaComponent={MemoizedDottedArea}
        inset={0}
        showYAxis
        yAxis={{
          showGrid: true,
          width: 0,
          tickLabelFormatter: () => '',
          domain: { min: 0, max: domainLimit },
        }}
      />
    );
  }

  return <AnimatedChart />;
}

Gradients

You can use the gradient prop on series or Area components to enable gradients.

Each stop requires an offset, which is based on the data within the x/y scale and color, with an optional opacity (defaults to 1).

Values in between stops will be interpolated smoothly using srgb color space.

Loading...
Live Code
function ContinuousGradient() {
  const spectrumColors = [
    'blue',
    'green',
    'orange',
    'yellow',
    'gray',
    'indigo',
    'pink',
    'purple',
    'red',
    'teal',
    'chartreuse',
  ];
  const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];

  const [currentSpectrumColor, setCurrentSpectrumColor] = useState('pink');

  return (
    <VStack gap={2}>
      <HStack gap={1} justifyContent="flex-end" flexWrap="wrap">
        {spectrumColors.map((color) => (
          <Pressable
            key={color}
            onClick={() => setCurrentSpectrumColor(color)}
            accessibilityLabel={`Select ${color}`}
            style={{
              backgroundColor: `rgb(var(--${color}20))`,
              border: `2px solid rgb(var(--${color}50))`,
              outlineColor: `rgb(var(--${color}80))`,
              outline:
                currentSpectrumColor === color ? `2px solid rgb(var(--${color}80))` : undefined,
            }}
            width={{ base: 16, tablet: 24, desktop: 24 }}
            height={{ base: 16, tablet: 24, desktop: 24 }}
            borderRadius={1000}
          />
        ))}
      </HStack>
      <AreaChart
        enableScrubbing
        height={{ base: 150, tablet: 200, desktop: 250 }}
        series={[
          {
            id: 'prices',
            data: data,
            gradient: {
              stops: ({ min, max }) => [
                // Allows a function which accepts min/max or direct array
                { offset: min, color: `rgb(var(--${currentSpectrumColor}80))` },
                { offset: max, color: `rgb(var(--${currentSpectrumColor}20))` },
              ],
            },
          },
        ]}
        showYAxis
        yAxis={{
          showGrid: true,
        }}
      >
        <Scrubber />
      </AreaChart>
    </VStack>
  );
}

Discrete

You can set multiple stops at the same offset to create a discrete gradient.

Loading...
Live Code
function DiscreteGradient() {
  const spectrumColors = [
    'blue',
    'green',
    'orange',
    'yellow',
    'gray',
    'indigo',
    'pink',
    'purple',
    'red',
    'teal',
    'chartreuse',
  ];
  const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];

  const [currentSpectrumColor, setCurrentSpectrumColor] = useState('pink');

  return (
    <VStack gap={2}>
      <HStack gap={1} justifyContent="flex-end" flexWrap="wrap">
        {spectrumColors.map((color) => (
          <Pressable
            key={color}
            onClick={() => setCurrentSpectrumColor(color)}
            accessibilityLabel={`Select ${color}`}
            style={{
              backgroundColor: `rgb(var(--${color}20))`,
              border: `2px solid rgb(var(--${color}50))`,
              outlineColor: `rgb(var(--${color}80))`,
              outline:
                currentSpectrumColor === color ? `2px solid rgb(var(--${color}80))` : undefined,
            }}
            width={{ base: 16, tablet: 24, desktop: 24 }}
            height={{ base: 16, tablet: 24, desktop: 24 }}
            borderRadius={1000}
          />
        ))}
      </HStack>
      <AreaChart
        enableScrubbing
        height={{ base: 150, tablet: 200, desktop: 250 }}
        series={[
          {
            id: 'prices',
            data: data,
            gradient: {
              stops: ({ min, max }) => [
                { offset: min, color: `rgb(var(--${currentSpectrumColor}80))` },
                { offset: min + (max - min) / 3, color: `rgb(var(--${currentSpectrumColor}80))` },
                { offset: min + (max - min) / 3, color: `rgb(var(--${currentSpectrumColor}50))` },
                {
                  offset: min + ((max - min) / 3) * 2,
                  color: `rgb(var(--${currentSpectrumColor}50))`,
                },
                {
                  offset: min + ((max - min) / 3) * 2,
                  color: `rgb(var(--${currentSpectrumColor}20))`,
                },
                { offset: max, color: `rgb(var(--${currentSpectrumColor}20))` },
              ],
            },
          },
        ]}
        showLines
        strokeWidth={4}
        showYAxis
        yAxis={{
          showGrid: true,
        }}
        fillOpacity={0.5}
      >
        <Scrubber />
      </AreaChart>
    </VStack>
  );
}

Axes

By default, gradients will be applied to the y-axis. You can apply a gradient to the x-axis by setting axis to x in the gradient definition.

Loading...
Live Code
function XAxisGradient() {
  const spectrumColors = [
    'blue',
    'green',
    'orange',
    'yellow',
    'gray',
    'indigo',
    'pink',
    'purple',
    'red',
    'teal',
    'chartreuse',
  ];
  const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];

  const [currentSpectrumColor, setCurrentSpectrumColor] = useState('pink');

  return (
    <VStack gap={2}>
      <HStack gap={1} justifyContent="flex-end" flexWrap="wrap">
        {spectrumColors.map((color) => (
          <Pressable
            key={color}
            onClick={() => setCurrentSpectrumColor(color)}
            accessibilityLabel={`Select ${color}`}
            style={{
              backgroundColor: `rgb(var(--${color}20))`,
              border: `2px solid rgb(var(--${color}50))`,
              outlineColor: `rgb(var(--${color}80))`,
              outline:
                currentSpectrumColor === color ? `2px solid rgb(var(--${color}80))` : undefined,
            }}
            width={{ base: 16, tablet: 24, desktop: 24 }}
            height={{ base: 16, tablet: 24, desktop: 24 }}
            borderRadius={1000}
          />
        ))}
      </HStack>
      <AreaChart
        enableScrubbing
        height={{ base: 150, tablet: 200, desktop: 250 }}
        series={[
          {
            id: 'prices',
            data: data,
            gradient: {
              axis: 'x',
              stops: ({ min, max }) => [
                { offset: min, color: `rgb(var(--${currentSpectrumColor}80))`, opacity: 0 },
                { offset: max, color: `rgb(var(--${currentSpectrumColor}20))`, opacity: 1 },
              ],
            },
          },
        ]}
        showYAxis
        yAxis={{
          showGrid: true,
        }}
      >
        <Scrubber />
      </AreaChart>
    </VStack>
  );
}

Is this page useful?

Coinbase Design is an open-source, adaptable system of guidelines, components, and tools that aid the best practices of user interface design for crypto products.