Timeline

radius={number} prop on a Bar component will round all edges of the Rectangle.

Stacked ranged charts are available since Recharts 3.6. So you can provide the ranges directly as arrays of two numbers.

This example also demonstrates the use of shape prop instead of Cell.

import {
  Bar,
  BarChart,
  BarProps,
  BarShapeProps,
  CartesianGrid,
  emptyTheme,
  RechartsThemeProvider,
  Rectangle,
  Tooltip,
  XAxis,
  YAxis,
} from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';

// #region Data and helper functions
type TimelineDataType = {
  name: string;
  type: string;
  outcome: 'success' | 'error' | 'pending';
  firstCycle: [number, number];
  secondCycle: [number, number];
};

const data: Array<TimelineDataType> = [
  {
    name: 'TEST 1',
    type: 'TR',
    outcome: 'success',
    firstCycle: [0, 3],
    secondCycle: [4.11, 14.11],
  },
  {
    name: 'TEST 2',
    type: 'MT',
    outcome: 'error',
    firstCycle: [0, 1.5],
    secondCycle: [9.11, 12.11],
  },
  {
    name: 'TEST 3',
    type: 'MT',
    outcome: 'success',
    firstCycle: [3, 5.37],
    secondCycle: [8.74, 14.48],
  },
  {
    name: 'TEST 4',
    type: 'MT',
    outcome: 'error',
    firstCycle: [5.37, 7.87],
    secondCycle: [9.61, 16.98],
  },
  {
    name: 'TEST 5',
    type: 'MT',
    outcome: 'success',
    firstCycle: [4.87, 8.24],
    secondCycle: [10.74, 17.35],
  },
  {
    name: 'TEST 6',
    type: 'MT',
    outcome: 'success',
    firstCycle: [3.24, 5.74],
    secondCycle: [8.61, 17.85],
  },
  {
    name: 'TEST 7',
    type: 'MT',
    outcome: 'success',
    firstCycle: [2.74, 9.11],
    secondCycle: [9.74, 18.22],
  },
  {
    name: 'TEST 8',
    type: 'MT',
    outcome: 'pending',
    firstCycle: [9.11, 10.61],
    secondCycle: [12.11, 19.72],
  },
];

const getBarColor = (outcome: TimelineDataType['outcome']) => {
  switch (outcome) {
    case 'success':
      return 'blue';
    case 'error':
      return 'red';
    default:
      return 'grey';
  }
};

const CustomFillRectangle = (props: BarShapeProps) => {
  // @ts-expect-error props.outcome is injected from the data array which Recharts doesn't know about
  const { outcome, isActive } = props;
  const barColor = getBarColor(outcome);
  return (
    <Rectangle {...props} stroke={isActive ? 'orange' : barColor} strokeWidth={isActive ? 3 : 1} fill={barColor} />
  );
};

const MyBar = (props: BarProps) => {
  return <Bar {...props} stackId="a" radius={25} activeBar shape={CustomFillRectangle} />;
};

// #endregion
export default function TimelineExample({ defaultIndex }: { defaultIndex?: number }) {
  return (
    <RechartsThemeProvider value={emptyTheme}>
      <BarChart
        layout="vertical"
        style={{ width: '100%', maxWidth: '700px', maxHeight: '70vh', aspectRatio: 1.618 }}
        responsive
        data={data}
        margin={{ bottom: 20 }}
      >
        <CartesianGrid strokeDasharray="2 2" />
        <Tooltip shared={false} defaultIndex={defaultIndex} />
        <XAxis type="number" height={50} label={{ value: 'Time (s)', position: 'insideBottomRight' }} />
        <YAxis
          type="category"
          dataKey="name"
          width="auto"
          label={{
            value: 'Test run',
            angle: -90,
            position: 'insideTopLeft',
            textAnchor: 'end',
          }}
        />
        <MyBar dataKey="firstCycle" />
        <MyBar dataKey="secondCycle" />
        <RechartsDevtools />
      </BarChart>
    </RechartsThemeProvider>
  );
}