# 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-mobile-visualization'
```

## Examples

### Basic Example

```jsx
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={150}
      series={[
        {
          id: 'prices',
          data: data,
        },
      ]}
      curve="monotone"
      showYAxis
      showArea
      yAxis={{
        showGrid: true,
      }}
      accessibilityLabel={accessibilityLabel}
    >
      <Scrubber />
    </LineChart>
  );
}
```

### Simple

```jsx
<LineChart
  height={150}
  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
function CompactLineChart() {
  const theme = useTheme();
  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 }) => {
    return (
      <ListCell
        detail={formatPrice(parseFloat(prices[0]))}
        intermediary={
          <CompactChart data={data} showArea={showArea} color={color} referenceY={referenceY} />
        }
        media={<CellMedia source={assets.btc.imageUrl} title="BTC" type="image" />}
        onClick={() => console.log('clicked')}
        subdetail={subdetail}
        title={isPhone ? undefined : assets.btc.name}
        variant={variant}
        style={{ padding: 0 }}
      />
    );
  });

  return (
    <VStack gap={2}>
      <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={theme.color.fgPositive}
        referenceY={positiveFloor}
        subdetail="+0.25%"
        variant="positive"
      />
      <ChartCell
        data={negativeData}
        showArea
        color={theme.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
function GainLossChart() {
  const theme = useTheme();
  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(${theme.color.gray15})`;
      const positiveColor = theme.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={150}
      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
function MultipleSeriesChart() {
  const theme = useTheme();
  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={150}
      series={[
        {
          id: 'prices',
          data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
          label: 'Prices',
          color: theme.color.accentBoldBlue,
        },
        {
          id: 'volume',
          data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14, 16, 14, 16, 10],
          label: 'Volume',
          color: theme.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
function PointsChart() {
  const theme = useTheme();
  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={150}
      series={[
        {
          id: 'prices',
          data: data,
        },
      ]}
    >
      <Area seriesId="prices" curve="monotone" fill={`rgb(${theme.color.blue5})`} />
      <Line
        seriesId="prices"
        renderPoints={({ dataX, dataY, ...props }) =>
          keyMarketShiftIndices.includes(dataX)
            ? {
                ...props,
                strokeWidth: 2,
                stroke: theme.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
function EmptyStateChart() {
  const theme = useTheme();
  return (
    <LineChart
      series={[
        {
          id: 'line',
          color: `rgb(${theme.color.gray50})`,
          data: [1, 1],
          showArea: true,
        },
      ]}
      yAxis={{ domain: { min: -1, max: 3 } }}
      height={150}
    />
  );
}
```

### Line Styles

```jsx
<LineChart
  height={150}
  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
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={150}
      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
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={150}
      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
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={150}
      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
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={150}
        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
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={150}
        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
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={150}
        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
function AvailabilityChart() {
  const theme = useTheme();
  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={theme.color.fgPositive} />
          <stop offset={`${greenThreshold}%`} stopColor={theme.color.fgPositive} />
          <stop offset={`${greenThreshold}%`} stopColor={theme.color.fgWarning} />
          <stop offset={`${yellowThreshold}%`} stopColor={theme.color.fgWarning} />
          <stop offset={`${yellowThreshold}%`} stopColor={theme.color.fgNegative} />
          <stop offset="100%" stopColor={theme.color.fgNegative} />
        </LinearGradient>
      </Defs>
    );
  });

  return (
    <CartesianChart
      enableScrubbing
      onScrubberPositionChange={setScrubIndex}
      height={150}
      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: theme.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
function BitcoinChartWithScrubberBeacon() {
  const theme = useTheme();
  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 />
        <VStack gap={0.25} alignItems="flex-end" flexGrow={1}>
          <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 * ${theme.space[2]})`,
          marginRight: `calc(-1 * ${theme.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. |
| `alignContent` | `flex-start \| flex-end \| center \| stretch \| space-between \| space-around \| space-evenly` | No | `-` | - |
| `alignItems` | `flex-start \| flex-end \| center \| stretch \| baseline` | No | `-` | - |
| `alignSelf` | `auto \| FlexAlignType` | No | `-` | - |
| `allowOverflowGestures` | `boolean` | No | `-` | Allows continuous gestures on the chart to continue outside the bounds of the chart element. |
| `animate` | `boolean` | No | `true` | Whether to animate the chart. |
| `animated` | `boolean` | No | `-` | - |
| `areaType` | `solid \| dotted \| gradient` | No | `'gradient'` | The type of area fill to add to the line. |
| `aspectRatio` | `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 | `-` | - |
| `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` | `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 | `-` | - |
| `columnGap` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `curve` | `bump \| catmullRom \| linear \| linearClosed \| monotone \| natural \| step \| stepBefore \| stepAfter` | No | `'linear'` | The curve interpolation method to use for the line. |
| `dangerouslySetBackground` | `string` | No | `-` | - |
| `display` | `flex \| none` | No | `-` | - |
| `elevation` | `0 \| 1 \| 2` | No | `-` | Determines box shadow styles. Parent should have overflow set to visible to ensure styles are not clipped. |
| `enableScrubbing` | `boolean` | No | `-` | Enables scrubbing interactions. When true, allows scrubbing and makes scrubber components interactive. |
| `flexBasis` | `string \| number` | No | `-` | - |
| `flexDirection` | `row \| column \| row-reverse \| column-reverse` | No | `-` | - |
| `flexGrow` | `number` | No | `-` | - |
| `flexShrink` | `number` | No | `-` | - |
| `flexWrap` | `wrap \| nowrap \| wrap-reverse` | No | `-` | - |
| `font` | `inherit \| FontFamily` | No | `-` | - |
| `fontFamily` | `inherit \| FontFamily` | No | `-` | - |
| `fontSize` | `inherit \| FontSize` | No | `-` | - |
| `fontWeight` | `inherit \| FontWeight` | No | `-` | - |
| `gap` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `height` | `string \| number` | No | `-` | Chart height. If not provided, will use the containers measured height. |
| `inset` | `number \| Partial<ChartInset>` | No | `-` | Inset around the entire chart (outside the axes). |
| `justifyContent` | `flex-start \| flex-end \| center \| space-between \| space-around \| space-evenly` | No | `-` | - |
| `key` | `Key \| null` | No | `-` | - |
| `left` | `string \| number` | No | `-` | - |
| `lineHeight` | `inherit \| LineHeight` | No | `-` | - |
| `margin` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
| `marginBottom` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
| `marginEnd` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
| `marginStart` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
| `marginTop` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
| `marginX` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
| `marginY` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
| `maxHeight` | `string \| number` | No | `-` | - |
| `maxWidth` | `string \| number` | No | `-` | - |
| `minHeight` | `string \| number` | No | `-` | - |
| `minWidth` | `string \| number` | No | `-` | - |
| `onPointerCancel` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerCancelCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerDown` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerDownCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerEnter` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerEnterCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerLeave` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerLeaveCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerMove` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerMoveCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerUp` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerUpCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `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` | `number \| AnimatedNode` | No | `-` | - |
| `overflow` | `visible \| hidden \| scroll` | No | `-` | - |
| `padding` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingBottom` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingEnd` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingStart` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingTop` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingX` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `paddingY` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `pin` | `top \| bottom \| left \| right \| all` | No | `-` | Direction in which to absolutely pin the box. |
| `position` | `absolute \| relative \| static \| fixed \| sticky` | No | `-` | - |
| `ref` | `((instance: View \| null) => void) \| RefObject<View> \| null` | 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. |
| `right` | `string \| number` | No | `-` | - |
| `rowGap` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
| `series` | `LineSeries[]` | No | `-` | Configuration objects that define how to visualize the data. Each series supports Line component props for individual customization. |
| `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. |
| `strokeWidth` | `number` | No | `-` | - |
| `style` | `false \| RegisteredStyle<ViewStyle> \| Value \| AnimatedInterpolation<string \| number> \| WithAnimatedObject<ViewStyle> \| WithAnimatedArray<Falsy \| ViewStyle \| RegisteredStyle<ViewStyle> \| RecursiveArray<Falsy \| ViewStyle \| RegisteredStyle<ViewStyle>> \| readonly (Falsy \| ViewStyle \| RegisteredStyle<ViewStyle>)[]> \| null` | No | `-` | - |
| `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Used to locate this view in end-to-end tests. |
| `textAlign` | `auto \| left \| right \| center \| justify` | No | `-` | - |
| `textDecorationLine` | `none \| underline \| line-through \| underline line-through` | No | `-` | - |
| `textDecorationStyle` | `solid \| dotted \| dashed \| double` | No | `-` | - |
| `textTransform` | `none \| capitalize \| uppercase \| lowercase` | No | `-` | - |
| `top` | `string \| number` | No | `-` | - |
| `transform` | `string \| (({ scaleX: AnimatableNumericValue; } & { scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ scaleY: AnimatableNumericValue; } & { scaleX?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ translateX: AnimatableNumericValue \| ${number}%; } & { scaleX?: undefined; scaleY?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ translateY: AnimatableNumericValue \| ${number}%; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ perspective: AnimatableNumericValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotate: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateX: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateY: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateZ: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ scale: AnimatableNumericValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ skewX: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ skewY: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; matrix?: undefined; }) \| ({ matrix: AnimatableNumericValue[]; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; }))[]` | No | `-` | - |
| `type` | `solid \| dotted \| gradient` | No | `'solid'` | The type of line to render. |
| `userSelect` | `auto \| none \| text \| contain \| all` | No | `-` | - |
| `width` | `string \| number` | No | `-` | Chart width. If not provided, will use the containers measured width. |
| `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 | `-` | - |
| `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 | `-` | - |
| `zIndex` | `number` | No | `-` | - |


