# LineChart

A flexible line chart component for displaying data trends over time. Supports multiple series, custom curves, areas, scrubbing, and interactive data exploration.

## Import

```tsx
import { LineChart } from '@coinbase/cds-web-visualization'
```

## Examples

### Basic Example

```jsx live
function BasicExample() {
  const [scrubIndex, setScrubIndex] = useState(undefined);
  const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];

  const accessibilityLabel = useMemo(() => {
    if (scrubIndex === undefined) return undefined;
    return `Value: ${data[scrubIndex]} at index ${scrubIndex}`;
  }, [scrubIndex, data]);

  return (
    <LineChart
      enableScrubbing
      onScrubberPositionChange={setScrubIndex}
      height={{ base: 150, tablet: 200, desktop: 250 }}
      series={[
        {
          id: 'prices',
          data: data,
        },
      ]}
      curve="monotone"
      showYAxis
      showArea
      yAxis={{
        showGrid: true,
      }}
      accessibilityLabel={accessibilityLabel}
    >
      <Scrubber />
    </LineChart>
  );
}
```

### Simple

```jsx live
<LineChart
  height={{ base: 150, tablet: 200, desktop: 250 }}
  series={[
    {
      id: 'prices',
      data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
    },
  ]}
  curve="monotone"
/>
```

### Compact

You can specify the dimensions of the chart to make it more compact.

```jsx live
function CompactLineChart() {
  const dimensions = { width: 62, height: 18 };

  const sparklineData = prices
    .map((price) => parseFloat(price))
    .filter((price, index) => index % 10 === 0);
  const positiveFloor = Math.min(...sparklineData) - 10;

  const negativeData = sparklineData.map((price) => -1 * price).reverse();
  const negativeCeiling = Math.max(...negativeData) + 10;

  const formatPrice = useCallback((price: number) => {
    return `$${price.toLocaleString('en-US', {
      minimumFractionDigits: 2,
      maximumFractionDigits: 2,
    })}`;
  }, []);

  const CompactChart = memo(({ data, showArea, color, referenceY }) => (
    <Box style={{ padding: 1 }}>
      <LineChart
        {...dimensions}
        enableScrubbing={false}
        overflow="visible"
        inset={0}
        showArea={showArea}
        series={[
          {
            id: 'btc',
            data,
            color,
          },
        ]}
      >
        <ReferenceLine dataY={referenceY} />
      </LineChart>
    </Box>
  ));

  const ChartCell = memo(({ data, showArea, color, referenceY, subdetail, variant }) => {
    const { isPhone } = useBreakpoints();

    return (
      <ListCell
        spacingVariant="condensed"
        description={isPhone ? undefined : assets.btc.symbol}
        detail={formatPrice(parseFloat(prices[0]))}
        intermediary={
          <CompactChart data={data} showArea={showArea} color={color} referenceY={referenceY} />
        }
        media={<Avatar src={assets.btc.imageUrl} />}
        onClick={() => console.log('clicked')}
        subdetail={subdetail}
        title={isPhone ? undefined : assets.btc.name}
        variant={variant}
        style={{ padding: 0 }}
      />
    );
  });

  return (
    <VStack>
      <ChartCell
        data={sparklineData}
        color={assets.btc.color}
        referenceY={parseFloat(prices[Math.floor(prices.length / 4)])}
        subdetail="-4.55%"
        variant="negative"
      />
      <ChartCell
        data={sparklineData}
        showArea
        color={assets.btc.color}
        referenceY={parseFloat(prices[Math.floor(prices.length / 4)])}
        subdetail="-4.55%"
        variant="negative"
      />
      <ChartCell
        data={sparklineData}
        showArea
        color="var(--color-fgPositive)"
        referenceY={positiveFloor}
        subdetail="+0.25%"
        variant="positive"
      />
      <ChartCell
        data={negativeData}
        showArea
        color="var(--color-fgNegative)"
        referenceY={negativeCeiling}
        subdetail="-4.55%"
        variant="negative"
      />
    </VStack>
  );
};
```

### Gain/Loss

You can use the y-axis scale and a [linearGradient](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/linearGradient) to create a gain/loss chart.

```jsx live
function GainLossChart() {
  const gradientId = useId();

  const data = [-40, -28, -21, -5, 48, -5, -28, 2, -29, -46, 16, -30, -29, 8];

  const priceFormatter = useCallback(
    (value) =>
      new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD',
        maximumFractionDigits: 0,
      }).format(value),
    [],
  );

  const ChartDefs = ({ threshold = 0 }) => {
    const { getYScale } = useCartesianChartContext();
    // get the default y-axis scale
    const yScale = getYScale();

    if (yScale) {
      const domain = yScale.domain();
      const range = yScale.range();

      const baselinePercentage = ((threshold - domain[0]) / (domain[1] - domain[0])) * 100;

      const negativeColor = 'rgb(var(--gray15))';
      const positiveColor = 'var(--color-fgPositive)';

      return (
        <defs>
          <linearGradient
            gradientUnits="userSpaceOnUse"
            id={`${gradientId}-solid`}
            x1="0%"
            x2="0%"
            y1={range[0]}
            y2={range[1]}
          >
            <stop offset="0%" stopColor={negativeColor} />
            <stop offset={`${baselinePercentage}%`} stopColor={negativeColor} />
            <stop offset={`${baselinePercentage}%`} stopColor={positiveColor} />
            <stop offset="100%" stopColor={positiveColor} />
          </linearGradient>
          <linearGradient
            gradientUnits="userSpaceOnUse"
            id={`${gradientId}-gradient`}
            x1="0%"
            x2="0%"
            y1={range[0]}
            y2={range[1]}
          >
            <stop offset="0%" stopColor={negativeColor} stopOpacity={0.3} />
            <stop offset={`${baselinePercentage}%`} stopColor={negativeColor} stopOpacity={0} />
            <stop offset={`${baselinePercentage}%`} stopColor={positiveColor} stopOpacity={0} />
            <stop offset="100%" stopColor={positiveColor} stopOpacity={0.3} />
          </linearGradient>
        </defs>
      );
    }

    return null;
  };

  const solidColor = `url(#${gradientId}-solid)`;

  return (
    <CartesianChart
      enableScrubbing
      height={{ base: 150, tablet: 200, desktop: 250 }}
      series={[
        {
          id: 'prices',
          data: data,
          color: solidColor,
        },
      ]}
      padding={{ top: 1.5, bottom: 1.5, left: 2, right: 0 }}
    >
      <ChartDefs />
      <YAxis requestedTickCount={2} showGrid tickLabelFormatter={priceFormatter} />
      <Area seriesId="prices" curve="monotone" fill={`url(#${gradientId}-gradient)`} />
      <Line strokeWidth={3} curve="monotone" seriesId="prices" stroke={solidColor} />
      <Scrubber hideOverlay />
    </CartesianChart>
  );
}
```

### Multiple Series

You can add multiple series to a line chart.

```jsx live
function MultipleSeriesChart() {
  const [scrubIndex, setScrubIndex] = useState(undefined);

  const prices = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
  const volume = [4, 8, 11, 15, 16, 14, 16, 10, 12, 14, 16, 14, 16, 10];

  return (
    <LineChart
      enableScrubbing
      height={{ base: 150, tablet: 200, desktop: 250 }}
      series={[
        {
          id: 'prices',
          data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
          label: 'Prices',
          color: 'var(--color-accentBoldBlue)',
        },
        {
          id: 'volume',
          data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14, 16, 14, 16, 10],
          label: 'Volume',
          color: 'var(--color-accentBoldGreen)',
        },
      ]}
      showYAxis
      yAxis={{
        domain: {
          min: 0,
        },
        showGrid: true,
      }}
      curve="monotone"
    >
      <Scrubber />
    </LineChart>
  );
}
```

### Points

You can use the `renderPoints` prop to dynamically show points on a line.

```jsx live
function PointsChart() {
  const keyMarketShiftIndices = [4, 6, 7, 9, 10];
  const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];

  return (
    <CartesianChart
      height={{ base: 150, tablet: 200, desktop: 250 }}
      series={[
        {
          id: 'prices',
          data: data,
        },
      ]}
    >
      <Area seriesId="prices" curve="monotone" fill="rgb(var(--blue5))" />
      <Line
        seriesId="prices"
        renderPoints={({ dataX, dataY, ...props }) =>
          keyMarketShiftIndices.includes(dataX)
            ? {
                ...props,
                strokeWidth: 2,
                stroke: 'var(--color-bg)',
                radius: 5,
                onClick: () =>
                  alert(
                    `You have clicked a key market shift at position ${dataX + 1} with value ${dataY}!`,
                  ),
                accessibilityLabel: `Key market shift point at position ${dataX + 1}, value ${dataY}. Click to view details.`,
              }
            : false
        }
        curve="monotone"
      />
    </CartesianChart>
  );
}
```

### Empty State

This example shows how to use an empty state for a line chart.

```jsx live
<LineChart
  series={[
    {
      id: 'line',
      color: 'rgb(var(--gray50))',
      data: [1, 1],
      showArea: true,
    },
  ]}
  yAxis={{ domain: { min: -1, max: 3 } }}
  height={{ base: 150, tablet: 200, desktop: 250 }}
/>
```

### Line Styles

```jsx live
<LineChart
  height={{ base: 150, tablet: 200, desktop: 250 }}
  series={[
    {
      id: 'top',
      data: [15, 28, 32, 44, 46, 36, 40, 45, 48, 38],
    },
    {
      id: 'upperMiddle',
      data: [12, 23, 21, 29, 34, 28, 31, 38, 42, 35],
      color: '#ef4444',
      type: 'dotted',
    },
    {
      id: 'lowerMiddle',
      data: [8, 15, 14, 25, 20, 18, 22, 28, 24, 30],
      color: '#f59e0b',
      curve: 'natural',
      LineComponent: (props) => (
        <GradientLine {...props} endColor="#F7931A" startColor="#E3D74D" strokeWidth={4} />
      ),
    },
    {
      id: 'bottom',
      data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14],
      color: '#800080',
      curve: 'step',
      AreaComponent: DottedArea,
      showArea: true,
    },
  ]}
/>
```

### Live Data

```jsx live
function LiveAssetPrice() {
  const scrubberRef = useRef(null);
  const [scrubIndex, setScrubIndex] = useState(undefined);

  const initialData = useMemo(() => {
    return sparklineInteractiveData.hour.map((d) => d.value);
  }, []);

  const [priceData, setPriceData] = useState(initialData);

  const lastDataPointTimeRef = useRef(Date.now());
  const updateCountRef = useRef(0);

  const intervalSeconds = 3600 / initialData.length;

  const maxPercentChange = Math.abs(initialData[initialData.length - 1] - initialData[0]) * 0.05;

  useEffect(() => {
    const priceUpdateInterval = setInterval(
      () => {
        setPriceData((currentData) => {
          const newData = [...currentData];
          const lastPrice = newData[newData.length - 1];

          const priceChange = (Math.random() - 0.5) * maxPercentChange;
          const newPrice = Math.round((lastPrice + priceChange) * 100) / 100;

          // Check if we should roll over to a new data point
          const currentTime = Date.now();
          const timeSinceLastPoint = (currentTime - lastDataPointTimeRef.current) / 1000;

          if (timeSinceLastPoint >= intervalSeconds) {
            // Time for a new data point - remove first, add new at end
            lastDataPointTimeRef.current = currentTime;
            newData.shift(); // Remove oldest data point
            newData.push(newPrice); // Add new data point
            updateCountRef.current = 0;
          } else {
            // Just update the last data point
            newData[newData.length - 1] = newPrice;
            updateCountRef.current++;
          }

          return newData;
        });

        // Pulse the scrubber on each update
        scrubberRef.current?.pulse();
      },
      2000 + Math.random() * 1000,
    );

    return () => clearInterval(priceUpdateInterval);
  }, [intervalSeconds, maxPercentChange]);

  const accessibilityLabel = useMemo(() => {
    if (scrubIndex === undefined)
      return `Bitcoin Price: $${priceData[priceData.length - 1].toFixed(2)}`;
    const price = priceData[scrubIndex];
    return `Bitcoin Price: $${price.toFixed(2)} at position ${scrubIndex + 1}`;
  }, [scrubIndex, priceData]);

  return (
    <LineChart
      enableScrubbing
      onScrubberPositionChange={setScrubIndex}
      showArea
      height={{ base: 150, tablet: 200, desktop: 250 }}
      series={[
        {
          id: 'btc',
          data: priceData,
          color: assets.btc.color,
        },
      ]}
      inset={{ right: 64 }}
      accessibilityLabel={accessibilityLabel}
    >
      <Scrubber ref={scrubberRef} labelProps={{ elevation: 1 }} />
    </LineChart>
  );
}
```

### Data Format

You can adjust the y values for a series of data by setting the `data` prop on the xAxis.

```jsx live
function DataFormatChart() {
  const [scrubIndex, setScrubIndex] = useState(undefined);

  const yData = [2, 5.5, 2, 8.5, 1.5, 5];
  const xData = [1, 2, 3, 5, 8, 10];

  const accessibilityLabel = useMemo(() => {
    if (scrubIndex === undefined) return undefined;
    return `X: ${xData[scrubIndex]}, Y: ${yData[scrubIndex]} at point ${scrubIndex + 1}`;
  }, [scrubIndex, xData, yData]);

  return (
    <LineChart
      enableScrubbing
      onScrubberPositionChange={setScrubIndex}
      series={[
        {
          id: 'line',
          data: yData,
        },
      ]}
      height={{ base: 150, tablet: 200, desktop: 250 }}
      showArea
      renderPoints={() => true}
      curve="natural"
      showXAxis
      xAxis={{ data: xData, showLine: true, showTickMarks: true, showGrid: true }}
      showYAxis
      yAxis={{
        domain: { min: 0 },
        position: 'left',
        showLine: true,
        showTickMarks: true,
        showGrid: true,
      }}
      inset={{ top: 16, right: 16, bottom: 0, left: 0 }}
      accessibilityLabel={accessibilityLabel}
    >
      <Scrubber hideOverlay />
    </LineChart>
  );
}
```

### Accessibility

You can use `accessibilityLabel` to provide a descriptive label for the chart. This is especially important for charts with scrubbing enabled, where the label should update dynamically to reflect the current data point.

```jsx live
function AccessibleBasicChart() {
  const [scrubIndex, setScrubIndex] = useState(undefined);
  const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];

  const accessibilityLabel = useMemo(() => {
    if (scrubIndex === undefined) {
      return `Current price: ${data[data.length - 1]}`;
    }
    return `Price at position ${scrubIndex + 1}: ${data[scrubIndex]}`;
  }, [scrubIndex, data]);

  return (
    <LineChart
      enableScrubbing
      onScrubberPositionChange={setScrubIndex}
      height={{ base: 150, tablet: 200, desktop: 250 }}
      series={[
        {
          id: 'prices',
          data: data,
        },
      ]}
      curve="monotone"
      showYAxis
      showArea
      yAxis={{
        showGrid: true,
      }}
      accessibilityLabel={accessibilityLabel}
    >
      <Scrubber />
    </LineChart>
  );
}
```

When a chart has a visible header or title, you can use `aria-labelledby` to reference it.

```jsx live
function AccessibleChartWithHeader() {
  const headerId = useId();
  const [scrubIndex, setScrubIndex] = useState(undefined);
  const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];

  const accessibilityLabel = useMemo(() => {
    if (scrubIndex === undefined) {
      return `Line chart showing price trend. Current value: ${data[data.length - 1]}`;
    }
    return `Value: ${data[scrubIndex]} at position ${scrubIndex + 1}`;
  }, [scrubIndex, data]);

  return (
    <VStack gap={2}>
      <Text id={headerId} font="label1">
        {accessibilityLabel}
      </Text>
      <LineChart
        enableScrubbing
        onScrubberPositionChange={setScrubIndex}
        height={{ base: 150, tablet: 200, desktop: 250 }}
        series={[
          {
            id: 'revenue',
            data: data,
          },
        ]}
        curve="monotone"
        showYAxis
        showArea
        yAxis={{
          showGrid: true,
        }}
        aria-labelledby={headerId}
      >
        <Scrubber />
      </LineChart>
    </VStack>
  );
}
```

### Customization

#### Asset Price with Dotted Area

```jsx live
function AssetPriceWithDottedArea() {
  const BTCTab: TabComponent = memo(
    forwardRef(
      ({ label, ...props }: SegmentedTabProps, ref: React.ForwardedRef<HTMLButtonElement>) => {
        const { activeTab } = useTabsContext();
        const isActive = activeTab?.id === props.id;

        return (
          <SegmentedTab
            ref={ref}
            label={
              <TextLabel1
                style={{
                  transition: 'color 0.2s ease',
                  color: isActive ? assets.btc.color : undefined,
                }}
              >
                {label}
              </TextLabel1>
            }
            {...props}
          />
        );
      },
    ),
  );

const BTCActiveIndicator = memo(({ style, ...props }: TabsActiveIndicatorProps) => (
    <PeriodSelectorActiveIndicator
      {...props}
      style={{ ...style, backgroundColor: `${assets.btc.color}1A` }}
    />
  ));

  const AssetPriceDotted = memo(() => {
  const [scrubIndex, setScrubIndex] = useState<number | undefined>(undefined);
  const currentPrice =
    sparklineInteractiveData.hour[sparklineInteractiveData.hour.length - 1].value;
  const tabs = [
    { id: 'hour', label: '1H' },
    { id: 'day', label: '1D' },
    { id: 'week', label: '1W' },
    { id: 'month', label: '1M' },
    { id: 'year', label: '1Y' },
    { id: 'all', label: 'All' },
  ];
  const [timePeriod, setTimePeriod] = useState<TabValue>(tabs[0]);

  const sparklineTimePeriodData = useMemo(() => {
    return sparklineInteractiveData[timePeriod.id as keyof typeof sparklineInteractiveData];
  }, [timePeriod]);

  const sparklineTimePeriodDataValues = useMemo(() => {
    return sparklineTimePeriodData.map((d) => d.value);
  }, [sparklineTimePeriodData]);

  const sparklineTimePeriodDataTimestamps = useMemo(() => {
    return sparklineTimePeriodData.map((d) => d.date);
  }, [sparklineTimePeriodData]);

  const onPeriodChange = useCallback(
    (period: TabValue | null) => {
      setTimePeriod(period || tabs[0]);
    },
    [tabs, setTimePeriod],
  );

  const priceFormatter = useMemo(
    () =>
      new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD',
      }),
    [],
  );

  const scrubberPriceFormatter = useMemo(
    () =>
      new Intl.NumberFormat('en-US', {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
      }),
    [],
  );

  const formatPrice = useCallback((price: number) => {
    return priceFormatter.format(price);
  }, [priceFormatter]);

  const formatDate = useCallback((date: Date) => {
    const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });

    const monthDay = date.toLocaleDateString('en-US', {
      month: 'short',
      day: 'numeric',
    });

    const time = date.toLocaleTimeString('en-US', {
      hour: 'numeric',
      minute: '2-digit',
      hour12: true,
    });

    return `${dayOfWeek}, ${monthDay}, ${time}`;
  }, []);

  const scrubberLabel = useCallback(
    (index: number) => {
      const price = scrubberPriceFormatter.format(sparklineTimePeriodDataValues[index]);
      const date = formatDate(sparklineTimePeriodDataTimestamps[index]);
      return (
        <>
          <tspan style={{ fontWeight: 'bold' }}>{price} USD</tspan> {date}
        </>
      );
    },
    [scrubberPriceFormatter, sparklineTimePeriodDataValues, sparklineTimePeriodDataTimestamps, formatDate],
  );

  const accessibilityLabel = useCallback(
    (index: number) => {
      const price = scrubberPriceFormatter.format(sparklineTimePeriodDataValues[index]);
      const date = formatDate(sparklineTimePeriodDataTimestamps[index]);
      return `${price} USD ${date}`;
    },
    [scrubberPriceFormatter, sparklineTimePeriodDataValues, sparklineTimePeriodDataTimestamps, formatDate],
  );

  return (
    <VStack gap={2}>
      <SectionHeader
        style={{ padding: 0 }}
        title={<Text font="title1">Bitcoin</Text>}
        balance={<Text font="title2">{formatPrice(currentPrice)}</Text>}
        end={
          <VStack justifyContent="center">
            <RemoteImage source={assets.btc.imageUrl} size="xl" shape="circle" />
          </VStack>
        }
      />
      <LineChart
        overflow="visible"
        enableScrubbing
        onScrubberPositionChange={setScrubIndex}
        series={[
          {
            id: 'btc',
            data: sparklineTimePeriodDataValues,
            color: assets.btc.color,
          },
        ]}
        showArea
        areaType="dotted"
        height={{ base: 150, tablet: 200, desktop: 250 }}
        style={{ outlineColor: assets.btc.color }}
        accessibilityLabel={scrubberLabel}
        padding={{ left: 2, right: 2 }}
      >
        <Scrubber label={scrubberLabel} labelProps={{ elevation: 1 }} idlePulse />
      </LineChart>
      <PeriodSelector
        TabComponent={BTCTab}
        TabsActiveIndicatorComponent={BTCActiveIndicator}
        tabs={tabs}
        activeTab={timePeriod}
        onChange={onPeriodChange}
      />
    </VStack>
  )});

  return <AssetPriceDotted />;
};
```

#### Forecast Asset Price

```jsx live
function ForecastAssetPrice() {
  const ForecastAreaComponent = memo(
    (props: AreaComponentProps) => (
      <DottedArea {...props} peakOpacity={0.4} baselineOpacity={0.4} />
    ),
  );

  const ForecastChart = memo(() => {
    const [scrubIndex, setScrubIndex] = useState<number | undefined>(undefined);

    const getDataFromSparkline = useCallback((startDate: Date) => {
      const allData = sparklineInteractiveData.all;
      if (!allData || allData.length === 0) return [];

      const timelineData = allData.filter((point) => point.date >= startDate);

      return timelineData.map((point) => ({
        date: point.date,
        value: point.value,
      }));
    }, []);

    const historicalData = useMemo(() => getDataFromSparkline(new Date('2019-01-01')), [getDataFromSparkline]);

    const annualGrowthRate = 10;

    const generateForecastData = useCallback(
      (lastDate: Date, lastPrice: number, growthRate: number) => {
        const dailyGrowthRate = Math.pow(1 + growthRate / 100, 1 / 365) - 1;
        const forecastData = [];
        const fiveYearsFromNow = new Date(lastDate);
        fiveYearsFromNow.setFullYear(fiveYearsFromNow.getFullYear() + 5);

        // Generate daily forecast points for 5 years
        const currentDate = new Date(lastDate);
        let currentPrice = lastPrice;

        while (currentDate <= fiveYearsFromNow) {
          currentPrice = currentPrice * (1 + dailyGrowthRate * 10);
          forecastData.push({
            date: new Date(currentDate),
            value: Math.round(currentPrice),
          });
          currentDate.setDate(currentDate.getDate() + 10);
        }

        return forecastData;
      },
      [],
    );

    const priceFormatter = useMemo(
      () =>
        new Intl.NumberFormat('en-US', {
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
        }),
      [],
    );

    const formatDate = useCallback((date: Date) => {
      const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });

      const monthDay = date.toLocaleDateString('en-US', {
        month: 'short',
        day: 'numeric',
      });

      const time = date.toLocaleTimeString('en-US', {
        hour: 'numeric',
        minute: '2-digit',
        hour12: true,
      });

      return `${dayOfWeek}, ${monthDay}, ${time}`;
    }, []);

    const forecastData = useMemo(() => {
      if (historicalData.length === 0) return [];
      const lastPoint = historicalData[historicalData.length - 1];
      return generateForecastData(lastPoint.date, lastPoint.value, annualGrowthRate);
    }, [generateForecastData, historicalData, annualGrowthRate]);

    // Combine all data points with dates converted to timestamps for x-axis
    const allDataPoints = useMemo(
      () => [...historicalData, ...forecastData],
      [historicalData, forecastData],
    );

    const historicalDataValues = useMemo(
      () => historicalData.map((d) => d.value),
      [historicalData],
    );

    const forecastDataValues = useMemo(
      () => [...historicalData.map((d) => null), ...forecastData.map((d) => d.value)],
      [historicalData, forecastData],
    );

    const xAxisData = useMemo(
      () => allDataPoints.map((d) => d.date.getTime()),
      [allDataPoints],
    );

    const scrubberLabel = useCallback(
      (index: number) => {
        const price = priceFormatter.format(allDataPoints[index].value);
        const date = formatDate(allDataPoints[index].date);
        return (
          <>
            <tspan style={{ fontWeight: 'bold' }}>{price} USD</tspan> {date}
          </>
        );
      },
      [priceFormatter, allDataPoints, formatDate],
    );

    const accessibilityLabel = useCallback(
      (index: number) => {
        const price = priceFormatter.format(allDataPoints[index].value);
        const date = formatDate(allDataPoints[index].date);
        return `${price} USD ${date}`;
      },
      [priceFormatter, allDataPoints, formatDate],
    );

    return (
      <LineChart
        overflow="visible"
        animate={false}
        enableScrubbing
        showArea
        showXAxis
        AreaComponent={ForecastAreaComponent}
        height={{ base: 150, tablet: 200, desktop: 250 }}
        padding={{
          top: 4,
          left: 2,
          right: 2,
          bottom: 0,
        }}
        series={[
          {
            id: 'historical',
            data: historicalDataValues,
            color: assets.btc.color,
          },
          {
            id: 'forecast',
            data: forecastDataValues,
            color: assets.btc.color,
            type: 'dotted',
          },
        ]}
        xAxis={{
          data: xAxisData,
          tickLabelFormatter: (value: number) => {
            return new Date(value).toLocaleDateString('en-US', {
              month: 'numeric',
              year: 'numeric',
            });
          },
          tickInterval: 2,
        }}
        accessibilityLabel={accessibilityLabel}
        style={{ outlineColor: assets.btc.color }}
      >
        <Scrubber label={scrubberLabel} labelProps={{ elevation: 1 }} />
      </LineChart>
    );
  });

  return <ForecastChart />;
};
```

#### Availability

```jsx live
function AvailabilityChart() {
  const [scrubIndex, setScrubIndex] = useState(undefined);

  const availabilityEvents = [
    {
      date: new Date('2022-01-01'),
      availability: 79,
    },
    {
      date: new Date('2022-01-03'),
      availability: 81,
    },
    {
      date: new Date('2022-01-04'),
      availability: 82,
    },
    {
      date: new Date('2022-01-06'),
      availability: 91,
    },
    {
      date: new Date('2022-01-07'),
      availability: 92,
    },
    {
      date: new Date('2022-01-10'),
      availability: 86,
    },
  ];

  const accessibilityLabel = useMemo(() => {
    if (scrubIndex === undefined) return undefined;
    const event = availabilityEvents[scrubIndex];
    const formattedDate = event.date.toLocaleDateString('en-US', {
      weekday: 'short',
      month: 'short',
      day: 'numeric',
      year: 'numeric',
    });
    const status =
      event.availability >= 90 ? 'Good' : event.availability >= 85 ? 'Warning' : 'Critical';
    return `${formattedDate}: Availability ${event.availability}% - Status: ${status}`;
  }, [scrubIndex, availabilityEvents]);

  const ChartDefs = memo(({ yellowThresholdPercentage = 85, greenThresholdPercentage = 90 }) => {
    const { drawingArea, height, series, getYScale, getYAxis } = useCartesianChartContext();
    const yScale = getYScale();
    const yAxis = getYAxis();

    if (!series || !drawingArea || !yScale) return null;

    const rangeBounds = yAxis?.domain;
    const rangeMin = rangeBounds?.min ?? 0;
    const rangeMax = rangeBounds?.max ?? 100;

    // Calculate the Y positions in the chart coordinate system
    const yellowThresholdY = yScale(yellowThresholdPercentage) ?? 0;
    const greenThresholdY = yScale(greenThresholdPercentage) ?? 0;
    const minY = yScale(rangeMax) ?? 0; // Top of chart (max value)
    const maxY = yScale(rangeMin) ?? drawingArea.height; // Bottom of chart (min value)

    // Calculate percentages based on actual chart positions
    const yellowThreshold = ((yellowThresholdY - minY) / (maxY - minY)) * 100;
    const greenThreshold = ((greenThresholdY - minY) / (maxY - minY)) * 100;

    return (
      <defs>
        <linearGradient
          gradientUnits="userSpaceOnUse"
          id="availabilityGradient"
          x1="0%"
          x2="0%"
          y1={minY}
          y2={maxY}
        >
          <stop offset="0%" stopColor="var(--color-fgPositive)" />
          <stop offset={`${greenThreshold}%`} stopColor="var(--color-fgPositive)" />
          <stop offset={`${greenThreshold}%`} stopColor="var(--color-fgWarning)" />
          <stop offset={`${yellowThreshold}%`} stopColor="var(--color-fgWarning)" />
          <stop offset={`${yellowThreshold}%`} stopColor="var(--color-fgNegative)" />
          <stop offset="100%" stopColor="var(--color-fgNegative)" />
        </linearGradient>
      </defs>
    );
  });

  return (
    <CartesianChart
      enableScrubbing
      onScrubberPositionChange={setScrubIndex}
      height={{ base: 150, tablet: 200, desktop: 250 }}
      series={[
        {
          id: 'availability',
          data: availabilityEvents.map((event) => event.availability),
          color: 'url(#availabilityGradient)',
        },
      ]}
      xAxis={{
        data: availabilityEvents.map((event) => event.date.getTime()),
      }}
      yAxis={{
        domain: ({ min, max }) => ({ min: Math.max(min - 2, 0), max: Math.min(max + 2, 100) }),
      }}
      padding={{ left: 2, right: 2 }}
      accessibilityLabel={accessibilityLabel}
    >
      <ChartDefs />
      <XAxis
        showGrid
        showLine
        showTickMarks
        tickLabelFormatter={(value) => new Date(value).toLocaleDateString()}
      />
      <YAxis
        showGrid
        showLine
        showTickMarks
        position="left"
        tickLabelFormatter={(value) => `${value}%`}
      />
      <Line
        curve="stepAfter"
        renderPoints={() => ({
          fill: 'var(--color-bg)',
          stroke: 'url(#availabilityGradient)',
          strokeWidth: 2,
        })}
        seriesId="availability"
      />
      <Scrubber hideOverlay />
    </CartesianChart>
  );
}
```

#### Asset Price Widget

You can coordinate LineChart with custom styles to create a custom card that shows the latest price and percent change.

```jsx live
function BitcoinChartWithScrubberBeacon() {
  const { isPhone } = useBreakpoints();
  const prices = [...btcCandles].reverse().map((candle) => parseFloat(candle.close));
  const latestPrice = prices[prices.length - 1];

  const formatPrice = (price: number) => {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD',
    }).format(price);
  };

  const formatPercentChange = (price: number) => {
    return new Intl.NumberFormat('en-US', {
      style: 'percent',
      minimumFractionDigits: 2,
      maximumFractionDigits: 2,
    }).format(price);
  };

  const percentChange = (latestPrice - prices[0]) / prices[0];

  return (
    <VStack
      style={{
        background:
          'linear-gradient(0deg, rgba(0, 0, 0, 0.80) 0%, rgba(0, 0, 0, 0.80) 100%), #ED702F',
      }}
      borderRadius={300}
      gap={2}
      padding={2}
      paddingBottom={0}
      overflow="hidden"
    >
      <HStack gap={2} alignItems="center">
        <RemoteImage source={assets.btc.imageUrl} size="xxl" shape="circle" aria-hidden />
        {!isPhone && (
          <VStack gap={0.25} flexGrow={1}>
            <Text font="title1" style={{ color: 'white' }} aria-hidden>
              BTC
            </Text>
            <Text font="label1" color="fgMuted">
              Bitcoin
            </Text>
          </VStack>
        )}
        <VStack gap={0.25} alignItems="flex-end" flexGrow={isPhone ? 1 : undefined}>
          <Text font="title1" style={{ color: 'white' }}>
            {formatPrice(latestPrice)}
          </Text>
          <Text font="label1" color="fgPositive" accessibilityLabel={`Up ${formatPercentChange(percentChange)}`}>
            +{formatPercentChange(percentChange)}
          </Text>
        </VStack>
      </HStack>
      <div
        style={{
          marginLeft: 'calc(-1 * var(--space-2))',
          marginRight: 'calc(-1 * var(--space-2))',
        }}
      >
        <LineChart
          inset={{ left: 0, right: 18, bottom: 0, top: 0 }}
          series={[
            {
              id: 'btcPrice',
              data: prices,
              color: assets.btc.color,
            },
          ]}
          showArea
          width="100%"
          height={92}
        >
          <Scrubber idlePulse styles={{ beacon: { stroke: 'white' } }} />
        </LineChart>
      </div>
    </VStack>
  );
};
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `AreaComponent` | `AreaComponent` | No | `-` | Custom component to render line area fill. |
| `LineComponent` | `LineComponent` | No | `-` | Component to render the line. Takes precedence over the type prop if provided. |
| `accentHeight` | `string \| number` | No | `-` | - |
| `accumulate` | `none \| sum` | No | `-` | - |
| `additive` | `sum \| replace` | No | `-` | - |
| `alignContent` | `ResponsiveProp<center \| end \| normal \| start \| flex-start \| flex-end \| space-between \| space-around \| space-evenly \| stretch \| baseline \| first baseline \| last baseline>` | No | `-` | - |
| `alignItems` | `ResponsiveProp<center \| end \| normal \| start \| flex-start \| flex-end \| stretch \| baseline \| first baseline \| last baseline \| self-start \| self-end>` | No | `-` | - |
| `alignSelf` | `ResponsiveProp<center \| end \| normal \| auto \| start \| flex-start \| flex-end \| stretch \| baseline \| first baseline \| last baseline \| self-start \| self-end>` | No | `-` | - |
| `alignmentBaseline` | `alphabetic \| hanging \| ideographic \| mathematical \| inherit \| auto \| baseline \| before-edge \| text-before-edge \| middle \| central \| after-edge \| text-after-edge` | No | `-` | - |
| `allowReorder` | `no \| yes` | No | `-` | - |
| `alphabetic` | `string \| number` | No | `-` | - |
| `amplitude` | `string \| number` | No | `-` | - |
| `animate` | `boolean` | No | `true` | Whether to animate the chart. |
| `arabicForm` | `initial \| medial \| terminal \| isolated` | No | `-` | - |
| `areaType` | `dotted \| solid \| gradient` | No | `'gradient'` | The type of area fill to add to the line. |
| `as` | `svg` | No | `-` | - |
| `ascent` | `string \| number` | No | `-` | - |
| `aspectRatio` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `attributeName` | `string` | No | `-` | - |
| `attributeType` | `string` | No | `-` | - |
| `autoReverse` | `false \| true \| true \| false` | No | `-` | - |
| `azimuth` | `string \| number` | No | `-` | - |
| `background` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | - |
| `baseFrequency` | `string \| number` | No | `-` | - |
| `baseProfile` | `string \| number` | No | `-` | - |
| `baselineShift` | `string \| number` | No | `-` | - |
| `bbox` | `string \| number` | No | `-` | - |
| `begin` | `string \| number` | No | `-` | - |
| `bias` | `string \| number` | No | `-` | - |
| `borderBottomLeftRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
| `borderBottomRightRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
| `borderBottomWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
| `borderColor` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | - |
| `borderEndWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
| `borderRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
| `borderStartWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
| `borderTopLeftRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
| `borderTopRightRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
| `borderTopWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
| `borderWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
| `bordered` | `boolean` | No | `-` | Add a border around all sides of the box. |
| `borderedBottom` | `boolean` | No | `-` | Add a border to the bottom side of the box. |
| `borderedEnd` | `boolean` | No | `-` | Add a border to the trailing side of the box. |
| `borderedHorizontal` | `boolean` | No | `-` | Add a border to the leading and trailing sides of the box. |
| `borderedStart` | `boolean` | No | `-` | Add a border to the leading side of the box. |
| `borderedTop` | `boolean` | No | `-` | Add a border to the top side of the box. |
| `borderedVertical` | `boolean` | No | `-` | Add a border to the top and bottom sides of the box. |
| `bottom` | `ResponsiveProp<Bottom<string \| number>>` | No | `-` | - |
| `by` | `string \| number` | No | `-` | - |
| `calcMode` | `string \| number` | No | `-` | - |
| `capHeight` | `string \| number` | No | `-` | - |
| `className` | `string` | No | `-` | - |
| `clip` | `string \| number` | No | `-` | - |
| `clipPath` | `string` | No | `-` | - |
| `clipPathUnits` | `string \| number` | No | `-` | - |
| `clipRule` | `string \| number` | No | `-` | - |
| `color` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | - |
| `colorInterpolation` | `string \| number` | No | `-` | - |
| `colorInterpolationFilters` | `inherit \| auto \| sRGB \| linearRGB` | No | `-` | - |
| `colorProfile` | `string \| number` | No | `-` | - |
| `colorRendering` | `string \| number` | No | `-` | - |
| `columnGap` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `contentScriptType` | `string \| number` | No | `-` | - |
| `contentStyleType` | `string \| number` | No | `-` | - |
| `crossOrigin` | ` \| anonymous \| use-credentials` | No | `-` | - |
| `cursor` | `string \| number` | No | `-` | - |
| `curve` | `bump \| catmullRom \| linear \| linearClosed \| monotone \| natural \| step \| stepBefore \| stepAfter` | No | `'linear'` | The curve interpolation method to use for the line. |
| `cx` | `string \| number` | No | `-` | - |
| `cy` | `string \| number` | No | `-` | - |
| `d` | `string` | No | `-` | - |
| `dangerouslySetBackground` | `string` | No | `-` | - |
| `decelerate` | `string \| number` | No | `-` | - |
| `descent` | `string \| number` | No | `-` | - |
| `diffuseConstant` | `string \| number` | No | `-` | - |
| `direction` | `string \| number` | No | `-` | - |
| `display` | `ResponsiveProp<grid \| revert \| none \| block \| inline \| inline-block \| flex \| inline-flex \| inline-grid \| contents \| flow-root \| list-item>` | No | `-` | - |
| `divisor` | `string \| number` | No | `-` | - |
| `dominantBaseline` | `string \| number` | No | `-` | - |
| `dur` | `string \| number` | No | `-` | - |
| `dx` | `string \| number` | No | `-` | - |
| `dy` | `string \| number` | No | `-` | - |
| `edgeMode` | `string \| number` | No | `-` | - |
| `elevation` | `0 \| 1 \| 2` | No | `-` | - |
| `enableBackground` | `string \| number` | No | `-` | - |
| `enableScrubbing` | `boolean` | No | `-` | Enables scrubbing interactions. When true, allows scrubbing and makes scrubber components interactive. |
| `end` | `string \| number` | No | `-` | - |
| `exponent` | `string \| number` | No | `-` | - |
| `externalResourcesRequired` | `false \| true \| true \| false` | No | `-` | - |
| `fill` | `string` | No | `-` | - |
| `fillOpacity` | `string \| number` | No | `-` | - |
| `fillRule` | `inherit \| nonzero \| evenodd` | No | `-` | - |
| `filter` | `string` | No | `-` | - |
| `filterRes` | `string \| number` | No | `-` | - |
| `filterUnits` | `string \| number` | No | `-` | - |
| `flexBasis` | `ResponsiveProp<FlexBasis<string \| number>>` | No | `-` | - |
| `flexDirection` | `ResponsiveProp<row \| row-reverse \| column \| column-reverse>` | No | `-` | - |
| `flexGrow` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset` | No | `-` | - |
| `flexShrink` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset` | No | `-` | - |
| `flexWrap` | `ResponsiveProp<nowrap \| wrap \| wrap-reverse>` | No | `-` | - |
| `floodColor` | `string \| number` | No | `-` | - |
| `floodOpacity` | `string \| number` | No | `-` | - |
| `focusable` | `auto \| Booleanish` | No | `-` | - |
| `font` | `ResponsiveProp<FontFamily \| inherit>` | No | `-` | - |
| `fontFamily` | `ResponsiveProp<FontFamily \| inherit>` | No | `-` | - |
| `fontSize` | `ResponsiveProp<FontSize \| inherit>` | No | `-` | - |
| `fontSizeAdjust` | `string \| number` | No | `-` | - |
| `fontStretch` | `string \| number` | No | `-` | - |
| `fontStyle` | `string \| number` | No | `-` | - |
| `fontVariant` | `string \| number` | No | `-` | - |
| `fontWeight` | `ResponsiveProp<FontWeight \| inherit>` | No | `-` | - |
| `format` | `string \| number` | No | `-` | - |
| `fr` | `string \| number` | No | `-` | - |
| `from` | `string \| number` | No | `-` | - |
| `fx` | `string \| number` | No | `-` | - |
| `fy` | `string \| number` | No | `-` | - |
| `g1` | `string \| number` | No | `-` | - |
| `g2` | `string \| number` | No | `-` | - |
| `gap` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `glyphName` | `string \| number` | No | `-` | - |
| `glyphOrientationHorizontal` | `string \| number` | No | `-` | - |
| `glyphOrientationVertical` | `string \| number` | No | `-` | - |
| `glyphRef` | `string \| number` | No | `-` | - |
| `gradientTransform` | `string` | No | `-` | - |
| `gradientUnits` | `string` | No | `-` | - |
| `grid` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| none` | No | `-` | - |
| `gridArea` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `gridAutoColumns` | `ResponsiveProp<GridAutoColumns<string \| number>>` | No | `-` | - |
| `gridAutoFlow` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| row \| column \| dense` | No | `-` | - |
| `gridAutoRows` | `ResponsiveProp<GridAutoRows<string \| number>>` | No | `-` | - |
| `gridColumn` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `gridColumnEnd` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `gridColumnStart` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `gridRow` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `gridRowEnd` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `gridRowStart` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `gridTemplate` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| none` | No | `-` | - |
| `gridTemplateAreas` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| none` | No | `-` | - |
| `gridTemplateColumns` | `ResponsiveProp<GridTemplateColumns<string \| number>>` | No | `-` | - |
| `gridTemplateRows` | `ResponsiveProp<GridTemplateRows<string \| number>>` | No | `-` | - |
| `hanging` | `string \| number` | No | `-` | - |
| `height` | `ResponsiveProp<Height<string \| number>>` | No | `-` | - |
| `horizAdvX` | `string \| number` | No | `-` | - |
| `horizOriginX` | `string \| number` | No | `-` | - |
| `href` | `string` | No | `-` | - |
| `id` | `string` | No | `-` | - |
| `ideographic` | `string \| number` | No | `-` | - |
| `imageRendering` | `string \| number` | No | `-` | - |
| `in` | `string` | No | `-` | - |
| `in2` | `string \| number` | No | `-` | - |
| `inset` | `number \| Partial<ChartInset>` | No | `-` | Inset around the entire chart (outside the axes). |
| `intercept` | `string \| number` | No | `-` | - |
| `justifyContent` | `ResponsiveProp<left \| right \| center \| end \| normal \| start \| flex-start \| flex-end \| space-between \| space-around \| space-evenly \| stretch>` | No | `-` | - |
| `k` | `string \| number` | No | `-` | - |
| `k1` | `string \| number` | No | `-` | - |
| `k2` | `string \| number` | No | `-` | - |
| `k3` | `string \| number` | No | `-` | - |
| `k4` | `string \| number` | No | `-` | - |
| `kernelMatrix` | `string \| number` | No | `-` | - |
| `kernelUnitLength` | `string \| number` | No | `-` | - |
| `kerning` | `string \| number` | No | `-` | - |
| `key` | `Key \| null` | No | `-` | - |
| `keyPoints` | `string \| number` | No | `-` | - |
| `keySplines` | `string \| number` | No | `-` | - |
| `keyTimes` | `string \| number` | No | `-` | - |
| `lang` | `string` | No | `-` | - |
| `left` | `ResponsiveProp<Left<string \| number>>` | No | `-` | - |
| `lengthAdjust` | `string \| number` | No | `-` | - |
| `letterSpacing` | `string \| number` | No | `-` | - |
| `lightingColor` | `string \| number` | No | `-` | - |
| `limitingConeAngle` | `string \| number` | No | `-` | - |
| `lineHeight` | `ResponsiveProp<LineHeight \| inherit>` | No | `-` | - |
| `local` | `string \| number` | No | `-` | - |
| `margin` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
| `marginBottom` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
| `marginEnd` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
| `marginStart` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
| `marginTop` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
| `marginX` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
| `marginY` | `ResponsiveProp<0 \| -5 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1 \| -1.5 \| -2 \| -3 \| -4 \| -6 \| -7 \| -8 \| -9>` | No | `-` | - |
| `markerEnd` | `string` | No | `-` | - |
| `markerHeight` | `string \| number` | No | `-` | - |
| `markerMid` | `string` | No | `-` | - |
| `markerStart` | `string` | No | `-` | - |
| `markerUnits` | `string \| number` | No | `-` | - |
| `markerWidth` | `string \| number` | No | `-` | - |
| `mask` | `string` | No | `-` | - |
| `maskContentUnits` | `string \| number` | No | `-` | - |
| `maskUnits` | `string \| number` | No | `-` | - |
| `mathematical` | `string \| number` | No | `-` | - |
| `max` | `string \| number` | No | `-` | - |
| `maxHeight` | `ResponsiveProp<MaxHeight<string \| number>>` | No | `-` | - |
| `maxWidth` | `ResponsiveProp<MaxWidth<string \| number>>` | No | `-` | - |
| `media` | `string` | No | `-` | - |
| `method` | `string` | No | `-` | - |
| `min` | `string \| number` | No | `-` | - |
| `minHeight` | `ResponsiveProp<MinHeight<string \| number>>` | No | `-` | - |
| `minWidth` | `ResponsiveProp<MinWidth<string \| number>>` | No | `-` | - |
| `mode` | `string \| number` | No | `-` | - |
| `name` | `string` | No | `-` | - |
| `numOctaves` | `string \| number` | No | `-` | - |
| `offset` | `string \| number` | No | `-` | - |
| `onChange` | `FormEventHandler<SVGSVGElement>` | No | `-` | - |
| `onPointClick` | `((event: MouseEvent<Element, MouseEvent>, point: { x: number; y: number; dataX: number; dataY: number; }) => void)` | No | `-` | Handler for when a dot is clicked. Automatically makes dots appear pressable when provided. |
| `onScrubberPositionChange` | `((index: number) => void) \| undefined` | No | `-` | Callback fired when the scrubber position changes. Receives the dataIndex of the scrubber or undefined when not scrubbing. |
| `opacity` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset` | No | `-` | - |
| `operator` | `string \| number` | No | `-` | - |
| `order` | `string \| number` | No | `-` | - |
| `orient` | `string \| number` | No | `-` | - |
| `orientation` | `string \| number` | No | `-` | - |
| `origin` | `string \| number` | No | `-` | - |
| `overflow` | `ResponsiveProp<clip \| auto \| visible \| hidden \| scroll>` | No | `-` | - |
| `overlinePosition` | `string \| number` | No | `-` | - |
| `overlineThickness` | `string \| number` | No | `-` | - |
| `padding` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `paddingBottom` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `paddingEnd` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `paddingStart` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `paddingTop` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `paddingX` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `paddingY` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `paintOrder` | `string \| number` | No | `-` | - |
| `panose1` | `string \| number` | No | `-` | - |
| `path` | `string` | No | `-` | - |
| `pathLength` | `string \| number` | No | `-` | - |
| `patternContentUnits` | `string` | No | `-` | - |
| `patternTransform` | `string \| number` | No | `-` | - |
| `patternUnits` | `string` | No | `-` | - |
| `pin` | `top \| bottom \| left \| right \| all` | No | `-` | Direction in which to absolutely pin the box. |
| `pointerEvents` | `string \| number` | No | `-` | - |
| `points` | `string` | No | `-` | - |
| `pointsAtX` | `string \| number` | No | `-` | - |
| `pointsAtY` | `string \| number` | No | `-` | - |
| `pointsAtZ` | `string \| number` | No | `-` | - |
| `position` | `ResponsiveProp<static \| relative \| absolute \| fixed \| sticky>` | No | `-` | - |
| `preserveAlpha` | `false \| true \| true \| false` | No | `-` | - |
| `preserveAspectRatio` | `string` | No | `-` | - |
| `primitiveUnits` | `string \| number` | No | `-` | - |
| `r` | `string \| number` | No | `-` | - |
| `radius` | `string \| number` | No | `-` | - |
| `ref` | `((instance: SVGSVGElement \| null) => void) \| RefObject<SVGSVGElement> \| null` | No | `-` | - |
| `refX` | `string \| number` | No | `-` | - |
| `refY` | `string \| number` | No | `-` | - |
| `renderPoints` | `((params: RenderPointsParams) => boolean \| PointConfig \| null) \| undefined` | No | `-` | Callback function to determine how to render points at each data point in the series. Called for every entry in the data array. |
| `renderingIntent` | `string \| number` | No | `-` | - |
| `repeatCount` | `string \| number` | No | `-` | - |
| `repeatDur` | `string \| number` | No | `-` | - |
| `requiredExtensions` | `string \| number` | No | `-` | - |
| `requiredFeatures` | `string \| number` | No | `-` | - |
| `restart` | `string \| number` | No | `-` | - |
| `result` | `string` | No | `-` | - |
| `right` | `ResponsiveProp<Right<string \| number>>` | No | `-` | - |
| `role` | `grid \| article \| button \| dialog \| figure \| form \| img \| link \| main \| menu \| menuitem \| option \| search \| table \| switch \| string & {} \| none \| row \| alert \| alertdialog \| application \| banner \| cell \| checkbox \| columnheader \| combobox \| complementary \| contentinfo \| definition \| directory \| document \| feed \| gridcell \| group \| heading \| list \| listbox \| listitem \| log \| marquee \| math \| menubar \| menuitemcheckbox \| menuitemradio \| navigation \| note \| presentation \| progressbar \| radio \| radiogroup \| region \| rowgroup \| rowheader \| scrollbar \| searchbox \| separator \| slider \| spinbutton \| status \| tab \| tablist \| tabpanel \| term \| textbox \| timer \| toolbar \| tooltip \| tree \| treegrid \| treeitem` | No | `-` | - |
| `rotate` | `string \| number` | No | `-` | - |
| `rowGap` | `0 \| 5 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1 \| 1.5 \| 2 \| 3 \| 4 \| 6 \| 7 \| 8 \| 9` | No | `-` | - |
| `rx` | `string \| number` | No | `-` | - |
| `ry` | `string \| number` | No | `-` | - |
| `scale` | `string \| number` | No | `-` | - |
| `seed` | `string \| number` | No | `-` | - |
| `series` | `LineSeries[]` | No | `-` | Configuration objects that define how to visualize the data. Each series supports Line component props for individual customization. |
| `shapeRendering` | `string \| number` | No | `-` | - |
| `showArea` | `boolean` | No | `-` | Show area fill under the line. |
| `showXAxis` | `boolean` | No | `-` | Whether to show the X axis. |
| `showYAxis` | `boolean` | No | `-` | Whether to show the Y axis. |
| `slope` | `string \| number` | No | `-` | - |
| `spacing` | `string \| number` | No | `-` | - |
| `specularConstant` | `string \| number` | No | `-` | - |
| `specularExponent` | `string \| number` | No | `-` | - |
| `speed` | `string \| number` | No | `-` | - |
| `spreadMethod` | `string` | No | `-` | - |
| `startOffset` | `string \| number` | No | `-` | - |
| `stdDeviation` | `string \| number` | No | `-` | - |
| `stemh` | `string \| number` | No | `-` | - |
| `stemv` | `string \| number` | No | `-` | - |
| `stitchTiles` | `string \| number` | No | `-` | - |
| `stopColor` | `string` | No | `-` | - |
| `stopOpacity` | `string \| number` | No | `-` | - |
| `strikethroughPosition` | `string \| number` | No | `-` | - |
| `strikethroughThickness` | `string \| number` | No | `-` | - |
| `string` | `string \| number` | No | `-` | - |
| `stroke` | `string` | No | `-` | - |
| `strokeDasharray` | `string \| number` | No | `-` | - |
| `strokeDashoffset` | `string \| number` | No | `-` | - |
| `strokeLinecap` | `inherit \| butt \| round \| square` | No | `-` | - |
| `strokeLinejoin` | `inherit \| round \| miter \| bevel` | No | `-` | - |
| `strokeMiterlimit` | `string \| number` | No | `-` | - |
| `strokeOpacity` | `string \| number` | No | `-` | - |
| `strokeWidth` | `number` | No | `-` | - |
| `style` | `CSSProperties` | No | `-` | - |
| `suppressHydrationWarning` | `boolean` | No | `-` | - |
| `surfaceScale` | `string \| number` | No | `-` | - |
| `systemLanguage` | `string \| number` | No | `-` | - |
| `tabIndex` | `number` | No | `-` | - |
| `tableValues` | `string \| number` | No | `-` | - |
| `target` | `string` | No | `-` | - |
| `targetX` | `string \| number` | No | `-` | - |
| `targetY` | `string \| number` | No | `-` | - |
| `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID |
| `textAlign` | `ResponsiveProp<center \| end \| start \| justify>` | No | `-` | - |
| `textAnchor` | `string` | No | `-` | - |
| `textDecoration` | `ResponsiveProp<none \| underline \| overline \| line-through \| underline overline \| underline double>` | No | `-` | - |
| `textLength` | `string \| number` | No | `-` | - |
| `textRendering` | `string \| number` | No | `-` | - |
| `textTransform` | `ResponsiveProp<capitalize \| lowercase \| none \| uppercase>` | No | `-` | - |
| `to` | `string \| number` | No | `-` | - |
| `top` | `ResponsiveProp<Top<string \| number>>` | No | `-` | - |
| `transform` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| none` | No | `-` | - |
| `type` | `dotted \| solid \| gradient` | No | `'solid'` | The type of line to render. |
| `u1` | `string \| number` | No | `-` | - |
| `u2` | `string \| number` | No | `-` | - |
| `underlinePosition` | `string \| number` | No | `-` | - |
| `underlineThickness` | `string \| number` | No | `-` | - |
| `unicode` | `string \| number` | No | `-` | - |
| `unicodeBidi` | `string \| number` | No | `-` | - |
| `unicodeRange` | `string \| number` | No | `-` | - |
| `unitsPerEm` | `string \| number` | No | `-` | - |
| `userSelect` | `ResponsiveProp<text \| none \| auto \| all>` | No | `-` | - |
| `vAlphabetic` | `string \| number` | No | `-` | - |
| `vHanging` | `string \| number` | No | `-` | - |
| `vIdeographic` | `string \| number` | No | `-` | - |
| `vMathematical` | `string \| number` | No | `-` | - |
| `values` | `string` | No | `-` | - |
| `vectorEffect` | `string \| number` | No | `-` | - |
| `version` | `string` | No | `-` | - |
| `vertAdvY` | `string \| number` | No | `-` | - |
| `vertOriginX` | `string \| number` | No | `-` | - |
| `vertOriginY` | `string \| number` | No | `-` | - |
| `viewBox` | `string` | No | `-` | - |
| `viewTarget` | `string \| number` | No | `-` | - |
| `visibility` | `ResponsiveProp<visible \| hidden>` | No | `-` | - |
| `width` | `ResponsiveProp<Width<string \| number>>` | No | `-` | - |
| `widths` | `string \| number` | No | `-` | - |
| `wordSpacing` | `string \| number` | No | `-` | - |
| `writingMode` | `string \| number` | No | `-` | - |
| `x` | `string \| number` | No | `-` | - |
| `x1` | `string \| number` | No | `-` | - |
| `x2` | `string \| number` | No | `-` | - |
| `xAxis` | `(Partial<AxisConfigProps> & AxisBaseProps & { className?: string; classNames?: { root?: string \| undefined; tickLabel?: string \| undefined; gridLine?: string \| undefined; line?: string \| undefined; tickMark?: string \| undefined; } \| undefined; style?: CSSProperties \| undefined; styles?: { root?: CSSProperties \| undefined; tickLabel?: CSSProperties \| undefined; gridLine?: CSSProperties \| undefined; line?: CSSProperties \| undefined; tickMark?: CSSProperties \| undefined; } \| undefined; } & { position?: top \| bottom \| undefined; height?: number \| undefined; }) \| undefined` | No | `-` | - |
| `xChannelSelector` | `string` | No | `-` | - |
| `xHeight` | `string \| number` | No | `-` | - |
| `xlinkActuate` | `string` | No | `-` | - |
| `xlinkArcrole` | `string` | No | `-` | - |
| `xlinkHref` | `string` | No | `-` | - |
| `xlinkRole` | `string` | No | `-` | - |
| `xlinkShow` | `string` | No | `-` | - |
| `xlinkTitle` | `string` | No | `-` | - |
| `xlinkType` | `string` | No | `-` | - |
| `xmlBase` | `string` | No | `-` | - |
| `xmlLang` | `string` | No | `-` | - |
| `xmlSpace` | `string` | No | `-` | - |
| `xmlns` | `string` | No | `-` | - |
| `xmlnsXlink` | `string` | No | `-` | - |
| `y` | `string \| number` | No | `-` | - |
| `y1` | `string \| number` | No | `-` | - |
| `y2` | `string \| number` | No | `-` | - |
| `yAxis` | `(Partial<AxisConfigProps> & AxisBaseProps & { className?: string; classNames?: { root?: string \| undefined; tickLabel?: string \| undefined; gridLine?: string \| undefined; line?: string \| undefined; tickMark?: string \| undefined; } \| undefined; style?: CSSProperties \| undefined; styles?: { root?: CSSProperties \| undefined; tickLabel?: CSSProperties \| undefined; gridLine?: CSSProperties \| undefined; line?: CSSProperties \| undefined; tickMark?: CSSProperties \| undefined; } \| undefined; } & { axisId?: string \| undefined; position?: left \| right \| undefined; width?: number \| undefined; }) \| undefined` | No | `-` | - |
| `yChannelSelector` | `string` | No | `-` | - |
| `z` | `string \| number` | No | `-` | - |
| `zIndex` | `-moz-initial \| inherit \| initial \| revert \| revert-layer \| unset \| auto` | No | `-` | - |
| `zoomAndPan` | `string` | No | `-` | - |


