Skip to main content
/
/
/
/
Line chart

Line chart

Basic line chart

Below is a basic line chart example. In order to get the gradient below the line, we're using the <AreaChart> component and a SVG linearGradient element with a unique id.

Code

CustomTooltip component
import {
  AreaChart,
  Area,
  XAxis,
  YAxis,
  Tooltip,
  CartesianGrid,
} from "recharts";

import CustomTooltip from "../CustomToolTip";

const lineColor = "var(--bfc-attn)";
const locale = "en-us";

/** Convert a Date object to month & year string, e.g. "Jan 2023" */
const monthYearFormatter = new Intl.DateTimeFormat(locale, {
  month: "short",
  year: "numeric",
});

/** Adds a thousand separator to large numbers, e.g. "1,337" */
const numberFormatter = new Intl.NumberFormat(locale);

const data = [
  { date: "2023-01", value: 242725 },
  { date: "2023-02", value: 237632 },
  { date: "2023-03", value: 216610 },
  { date: "2023-04", value: 225223 },
  { date: "2023-05", value: 210043 },
  { date: "2023-06", value: 237577 },
  { date: "2023-07", value: 266538 },
  { date: "2023-08", value: 278820 },
  { date: "2023-09", value: 252026 },
  { date: "2023-10", value: 258326 },
  { date: "2023-11", value: 260691 },
  { date: "2023-12", value: 262566 },
  { date: "2024-01", value: 276147 },
  { date: "2024-02", value: 240784 },
  { date: "2024-03", value: 274169 },
  { date: "2024-04", value: 224765 },
  { date: "2024-05", value: 211219 },
  { date: "2024-06", value: 267703 },
  { date: "2024-07", value: 274453 },
  { date: "2024-08", value: 246658 },
].map((x) => ({
  ...x,
  formattedDate: monthYearFormatter.format(new Date(x.date)),
}));

export default function BasicLineChart() {
  return (
    <AreaChart
      responsive
      height="100%"
      width="100%"
      data={data}
      margin={{
        // adjust margins to fit your data
        left: 10,
      }}
    >
      <defs>
        <linearGradient id="basic-line-chart-gradient" x2="0" y2="1">
          <stop offset="0%" stopColor={lineColor} stopOpacity={0.8} />
          <stop offset="100%" stopColor={lineColor} stopOpacity={0} />
        </linearGradient>
      </defs>
      <CartesianGrid
        strokeDasharray="5 5"
        vertical={false}
        stroke="var(--bfc-base-dimmed)"
      />
      <XAxis dataKey="formattedDate" hide />
      <YAxis
        axisLine={false}
        tickLine={false}
        type="number"
        tickFormatter={(value) => numberFormatter.format(value)}
        tick={{ fill: "var(--bfc-base-c-2)", fontSize: 14 }}
        dx={-8}
      />
      <Tooltip content={<CustomTooltip />} />
      <Area
        type="monotone"
        dataKey="value"
        stroke={lineColor}
        strokeWidth={2}
        fill="url(#basic-line-chart-gradient)"
      />
    </AreaChart>
  );
}

Two-line chart

Creating a two-line chart (technically two-"area") is as simple as adding another <AreaChart> component with corresponding gradients to the chart.

Code

import {
  AreaChart,
  Area,
  XAxis,
  YAxis,
  Tooltip,
  CartesianGrid,
  Legend,
} from "recharts";

import CustomTooltip from "../CustomToolTip";

const data = [
  { month: "Jan", avgMinTemp: -6.0, avgMaxTemp: -1.0 },
  { month: "Feb", avgMinTemp: -5.0, avgMaxTemp: 0.0 },
  { month: "March", avgMinTemp: -2.0, avgMaxTemp: 5.0 },
  { month: "April", avgMinTemp: 2.0, avgMaxTemp: 10.0 },
  { month: "May", avgMinTemp: 7.0, avgMaxTemp: 16.0 },
  { month: "June", avgMinTemp: 12.0, avgMaxTemp: 20.0 },
  { month: "July", avgMinTemp: 14.0, avgMaxTemp: 23.0 },
  { month: "Aug", avgMinTemp: 13.0, avgMaxTemp: 22.0 },
  { month: "Sept", avgMinTemp: 10.0, avgMaxTemp: 17.0 },
  { month: "Oct", avgMinTemp: 4.0, avgMaxTemp: 10.0 },
  { month: "Nov", avgMinTemp: -1.0, avgMaxTemp: 4.0 },
  { month: "Dec", avgMinTemp: -4.0, avgMaxTemp: 0.0 },
];

export default function TwolineChart() {
  return (
    <AreaChart
      responsive
      height="100%"
      width="100%"
      data={data}
      margin={{
        // adjust margins to fit your data
        left: -25,
      }}
    >
      <defs>
        <linearGradient id="chillGradient" x2="0" y2="1">
          <stop offset="0%" stopColor="var(--bfc-chill)" stopOpacity={0.5} />
          <stop offset="100%" stopColor="var(--bfc-chill)" stopOpacity={0} />
        </linearGradient>
        <linearGradient id="attnGradient" x2="0" y2="1">
          <stop offset="0%" stopColor="var(--bfc-attn)" stopOpacity={0.5} />
          <stop offset="100%" stopColor="var(--bfc-attn)" stopOpacity={0} />
        </linearGradient>
      </defs>
      <CartesianGrid
        strokeDasharray="5 5"
        vertical={false}
        stroke="var(--bfc-base-dimmed)"
      />
      <XAxis
        axisLine={false}
        tickLine={false}
        dataKey="month"
        tick={{ fill: "var(--bfc-base-c-2)", fontSize: 14 }}
      />
      <YAxis
        axisLine={false}
        tickLine={false}
        tick={{ fill: "var(--bfc-base-c-2)", fontSize: 14 }}
        dx={-8}
      />
      <Tooltip content={<CustomTooltip />} />
      <Area
        baseValue="dataMin"
        type="monotone"
        dataKey="avgMinTemp"
        name="Avg. Min Temp"
        strokeWidth={2}
        stroke="var(--bfc-chill)"
        fill="url(#chillGradient)"
      />
      <Area
        baseValue="dataMin"
        type="monotone"
        dataKey="avgMaxTemp"
        name="Avg. Max Temp"
        strokeWidth={2}
        stroke="var(--bfc-attn)"
        fill="url(#attnGradient)"
      />
      <Legend
        align="right"
        iconType="square"
        formatter={(value) => <span className="bfc-base-2"> {value}</span>}
      />
    </AreaChart>
  );
}

Multi-line chart

Below is a bit more complex line chart example. The chart has a clickable legend which lets you toggle lines on and off, and highlights the corresponding line when hovering.

Code

import { Checkbox, Inline } from "@intility/bifrost-react";
import { useState } from "react";
import {
  CartesianGrid,
  Legend,
  Line,
  LineChart,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";

import CustomTooltip from "../CustomToolTip";

const locale = "en-us";

/** Convert a Date object to month & year string, e.g. "Jan 2023" */
const monthYearFormatter = new Intl.DateTimeFormat(locale, {
  month: "short",
  year: "numeric",
});

const data = [
  { 1: 231, 2: 210, 3: 276, 4: 323, date: "2023-01" },
  { 1: 202, 2: 182, 3: 237, 4: 254, date: "2023-02" },
  { 1: 223, 2: 169, 3: 286, 4: 315, date: "2023-03" },
  { 1: 210, 2: 176, 3: 241, 4: 275, date: "2023-04" },
  { 1: 220, 2: 197, 3: 221, 4: 306, date: "2023-05" },
  { 1: 209, 2: 190, 3: 236, 4: 287, date: "2023-06" },
  { 1: 225, 2: 203, 3: 224, 4: 285, date: "2023-07" },
  { 1: 236, 2: 160, 3: 280, 4: 314, date: "2023-08" },
  { 1: 233, 2: 177, 3: 242, 4: 327, date: "2023-09" },
  { 1: 237, 2: 160, 3: 284, 4: 342, date: "2023-10" },
  { 1: 226, 2: 197, 3: 267, 4: 318, date: "2023-11" },
  { 1: 207, 2: 192, 3: 281, 4: 338, date: "2023-12" },
  { 1: 235, 2: 167, 3: 229, 4: 308, date: "2024-01" },
  { 1: 232, 2: 208, 3: 265, 4: 324, date: "2024-02" },
  { 1: 233, 2: 173, 3: 264, 4: 305, date: "2024-03" },
  { 1: 227, 2: 163, 3: 293, 4: 291, date: "2024-04" },
  { 1: 236, 2: 171, 3: 205, 4: 321, date: "2024-05" },
  { 1: 210, 2: 173, 3: 223, 4: 275, date: "2024-06" },
  { 1: 228, 2: 161, 3: 207, 4: 331, date: "2024-07" },
  { 1: 222, 2: 196, 3: 271, 4: 314, date: "2024-08" },
].map((x) => ({
  ...x,
  formattedDate: monthYearFormatter.format(new Date(x.date)),
}));

const lines = [
  { dataKey: "1", name: "Data 1", className: "bf-theme-green" },
  { dataKey: "2", name: "Data 2", className: "bf-theme-pink" },
  { dataKey: "3", name: "Data 3", className: "bf-theme-purple" },
  { dataKey: "4", name: "Data 4", className: "bf-theme-yellow" },
];

export default function MultiLineChart() {
  const [hoveredLine, setHoveredLine] = useState<string>();
  const [hiddenLines, setHiddenLines] = useState<string[]>([]);

  const toggleLine = (lineName: string) => {
    setHiddenLines((prevState) => {
      if (prevState.includes(lineName)) {
        return prevState.filter((name) => name !== lineName);
      } else {
        return [...prevState, lineName];
      }
    });
  };

  return (
    <LineChart
      responsive
      height="100%"
      width="100%"
      data={data}
      margin={{ left: -20 }}
    >
      <CartesianGrid
        strokeDasharray="5 5"
        vertical={false}
        stroke="var(--bfc-base-dimmed)"
      />
      <XAxis
        axisLine={false}
        tickLine={false}
        tick={{ fill: "var(--bfc-base-c-2)", fontSize: 14 }}
        dataKey="formattedDate"
        dy={8}
      />
      <YAxis
        axisLine={false}
        tickLine={false}
        tick={{ fill: "var(--bfc-base-c-2)", fontSize: 14 }}
        dx={-8}
      />
      <Tooltip content={<CustomTooltip />} />
      {lines.map((line) => (
        <Line
          isAnimationActive={false}
          key={line.dataKey}
          type="monotone"
          dot={false}
          strokeWidth={2}
          stroke="var(--bfc-theme)"
          opacity={hoveredLine && hoveredLine !== line.dataKey ? 0.3 : 1}
          hide={hiddenLines.includes(line.dataKey)}
          {...line}
        />
      ))}
      <Legend
        content={() => (
          <Inline style={{ marginTop: 12 }}>
            <Inline.Separator />
            {lines.map(({ dataKey, name, className }) => (
              <Checkbox
                key={dataKey}
                label={name}
                checked={!hiddenLines.includes(dataKey)}
                onChange={() => toggleLine(dataKey)}
                labelProps={{
                  onMouseEnter: () => setHoveredLine(dataKey),
                  onMouseLeave: () => setHoveredLine(undefined),
                  className: `bfc-base-2 ${className}`,
                }}
              />
            ))}
          </Inline>
        )}
      />
    </LineChart>
  );
}

Try it yourself

import { Box, Checkbox, Grid, Inline } from "@intility/bifrost-react";
import { useState } from "react";
import {
  CartesianGrid,
  Legend,
  Line,
  LineChart,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";

const locale = "en-us"; // "nb-no" for Norwegian

/** Convert Date to month & year string, e.g. "Jan 2023" for en-us */
const monthYearFormatter = new Intl.DateTimeFormat(locale, {
  month: "short",
  year: "numeric",
});

const data = [
  { 1: 231, 2: 210, 3: 276, 4: 323, date: "2023-01" },
  { 1: 202, 2: 182, 3: 237, 4: 254, date: "2023-02" },
  { 1: 223, 2: 169, 3: 286, 4: 315, date: "2023-03" },
  { 1: 210, 2: 176, 3: 241, 4: 275, date: "2023-04" },
  { 1: 220, 2: 197, 3: 221, 4: 306, date: "2023-05" },
  { 1: 209, 2: 190, 3: 236, 4: 287, date: "2023-06" },
  { 1: 225, 2: 203, 3: 224, 4: 285, date: "2023-07" },
  { 1: 236, 2: 160, 3: 280, 4: 314, date: "2023-08" },
  { 1: 233, 2: 177, 3: 242, 4: 327, date: "2023-09" },
  { 1: 237, 2: 160, 3: 284, 4: 342, date: "2023-10" },
  { 1: 226, 2: 197, 3: 267, 4: 318, date: "2023-11" },
  { 1: 207, 2: 192, 3: 281, 4: 338, date: "2023-12" },
  { 1: 235, 2: 167, 3: 229, 4: 308, date: "2024-01" },
  { 1: 232, 2: 208, 3: 265, 4: 324, date: "2024-02" },
  { 1: 233, 2: 173, 3: 264, 4: 305, date: "2024-03" },
  { 1: 227, 2: 163, 3: 293, 4: 291, date: "2024-04" },
  { 1: 236, 2: 171, 3: 205, 4: 321, date: "2024-05" },
  { 1: 210, 2: 173, 3: 223, 4: 275, date: "2024-06" },
  { 1: 228, 2: 161, 3: 207, 4: 331, date: "2024-07" },
  { 1: 222, 2: 196, 3: 271, 4: 314, date: "2024-08" },
].map((x) => ({
  ...x,
  formattedDate: monthYearFormatter.format(new Date(x.date)),
}));

const lines = [
  { dataKey: "1", name: "Data 1", className: "bf-theme-green" },
  { dataKey: "2", name: "Data 2", className: "bf-theme-pink" },
  { dataKey: "3", name: "Data 3", className: "bf-theme-purple" },
  { dataKey: "4", name: "Data 4", className: "bf-theme-yellow" },
];

/** Adds thousands separator to large numbers, e.g. "1,337" for en-us */
const numberFormatter = new Intl.NumberFormat(locale);

function CustomTooltip({
  payload,
  label,
}: {
  label?: string | number;
  payload?: any[];
}) {
  return (
    <Box background radius shadow padding={14}>
      <Grid gap={4}>
        <strong className="bf-large">{label}</strong>
        <div>
          {payload?.map((entry, index) => (
            <div key={index}>
              {entry.name}:{" "}
              <strong>
                {typeof entry.value === "number"
                  ? numberFormatter.format(entry.value)
                  : entry.value}
              </strong>
            </div>
          ))}
        </div>
      </Grid>
    </Box>
  );
}

export default function MultiLineChart() {
  const [hoveredLine, setHoveredLine] = useState<string>();
  const [hiddenLines, setHiddenLines] = useState<string[]>([]);

  const toggleLine = (lineName: string) => {
    setHiddenLines((prevState) => {
      if (prevState.includes(lineName)) {
        return prevState.filter((name) => name !== lineName);
      } else {
        return [...prevState, lineName];
      }
    });
  };

  return (
    <LineChart
      responsive
      height={300}
      width="100%"
      data={data}
      margin={{ left: -20 }}
    >
      <CartesianGrid
        strokeDasharray="5 5"
        vertical={false}
        stroke="var(--bfc-base-dimmed)"
      />
      <XAxis
        axisLine={false}
        tickLine={false}
        tick={{ fill: "var(--bfc-base-c-2)", fontSize: 14 }}
        dataKey="formattedDate"
        dy={8}
      />
      <YAxis
        axisLine={false}
        tickLine={false}
        tick={{ fill: "var(--bfc-base-c-2)", fontSize: 14 }}
        dx={-8}
      />
      <Tooltip content={<CustomTooltip />} />
      {lines.map((line) => (
        <Line
          isAnimationActive={false}
          key={line.dataKey}
          type="monotone"
          dot={false}
          strokeWidth={2}
          stroke="var(--bfc-theme)"
          opacity={hoveredLine && hoveredLine !== line.dataKey ? 0.3 : 1}
          hide={hiddenLines.includes(line.dataKey)}
          {...line}
        />
      ))}
      <Legend
        content={() => (
          <Inline style={{ marginTop: 12 }}>
            <Inline.Separator />
            {lines.map(({ dataKey, name, className }) => (
              <Checkbox
                key={dataKey}
                label={name}
                checked={!hiddenLines.includes(dataKey)}
                onChange={() => toggleLine(dataKey)}
                labelProps={{
                  onMouseEnter: () => setHoveredLine(dataKey),
                  onMouseLeave: () => setHoveredLine(undefined),
                  className: `bfc-base-2 ${className}`,
                }}
              />
            ))}
          </Inline>
        )}
      />
    </LineChart>
  );
}