# SparklineInteractive

**📖 Live documentation:** https://cds.coinbase.com/components/charts/SparklineInteractive/

The SparklineInteractive is used to display a Sparkline that has multiple time periods

## Import

```tsx
import { SparklineInteractive } from '@coinbase/cds-web/visualizations/sparkline'
```

## Examples

### Default usage

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Fill Type

The fill will be added by default with a gradient style. You can set `fillType="dotted"` to get a dotted gradient fill.

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        fill
        data={sparklineInteractiveData}
        defaultPeriod="day"
        fillType="dotted"
        formatDate={formatDate}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Compact

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        compact
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Hide period selector

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        hidePeriodSelector
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Scaling Factor

The scaling factor is usually used when you want to show less variance in the chart. An example of this is a stable coin that doesn't change price by more than a few cents.

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        periods={periods}
        strokeColor="#F7931A"
        yAxisScalingFactor={0.1}
      />
    </Box>
  );
};
```

### With header

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  const formatPrice = (num) => num.toLocaleString('en-US', { maximumFractionDigits: 2 });

  const generateSubHead = useCallback((point, period) => {
    const firstPoint = sparklineInteractiveData[period][0];
    const increase = point.value > firstPoint.value;
    return {
      percent: `${formatPrice(Math.abs((point.value - firstPoint.value) / firstPoint.value) * 100)}%`,
      sign: increase ? 'upwardTrend' : 'downwardTrend',
      variant: increase ? 'positive' : 'negative',
      priceChange: `$${formatPrice(Math.abs(point.value - firstPoint.value))}`,
    };
  }, []);

  const headerRef = useRef(null);
  const [currentPeriod, setCurrentPeriod] = useState('day');
  const data = sparklineInteractiveData[currentPeriod];
  const lastPoint = data[data.length - 1];

  const handleScrub = useCallback(
    ({ point, period }) => {
      headerRef.current?.update({
        title: `$${point.value.toLocaleString('en-US')}`,
        subHead: generateSubHead(point, period),
      });
    },
    [generateSubHead],
  );

  const handleScrubEnd = useCallback(() => {
    headerRef.current?.update({
      title: `$${formatPrice(lastPoint.value)}`,
      subHead: generateSubHead(lastPoint, currentPeriod),
    });
  }, [lastPoint, currentPeriod, generateSubHead]);

  const handlePeriodChanged = useCallback(
    (period) => {
      setCurrentPeriod(period);
      const newData = sparklineInteractiveData[period];
      const newLastPoint = newData[newData.length - 1];
      headerRef.current?.update({
        title: `$${formatPrice(newLastPoint.value)}`,
        subHead: generateSubHead(newLastPoint, period),
      });
    },
    [generateSubHead],
  );

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        fill
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        headerNode={
          <SparklineInteractiveHeader
            ref={headerRef}
            defaultLabel="Bitcoin Price"
            defaultSubHead={generateSubHead(lastPoint, currentPeriod)}
            defaultTitle={`$${formatPrice(lastPoint.value)}`}
          />
        }
        onPeriodChanged={handlePeriodChanged}
        onScrub={handleScrub}
        onScrubEnd={handleScrubEnd}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Custom hover data

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        fill
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        hoverData={sparklineInteractiveHoverData}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Period selector placement

`periodSelectorPlacement` can be used to place the period selector in different positions (`above` or `below`).

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        periodSelectorPlacement="below"
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Custom styles

You can also provide custom styles, such as to remove any horizontal padding from the header.

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  const formatPrice = (num) => num.toLocaleString('en-US', { maximumFractionDigits: 2 });

  const generateSubHead = useCallback((point, period) => {
    const firstPoint = sparklineInteractiveData[period][0];
    const increase = point.value > firstPoint.value;
    return {
      percent: `${formatPrice(Math.abs((point.value - firstPoint.value) / firstPoint.value) * 100)}%`,
      sign: increase ? 'upwardTrend' : 'downwardTrend',
      variant: increase ? 'positive' : 'negative',
      priceChange: `$${formatPrice(Math.abs(point.value - firstPoint.value))}`,
    };
  }, []);

  const headerRef = useRef(null);
  const [currentPeriod, setCurrentPeriod] = useState('day');
  const data = sparklineInteractiveData[currentPeriod];
  const lastPoint = data[data.length - 1];

  const handleScrub = useCallback(
    ({ point, period }) => {
      headerRef.current?.update({
        title: `$${point.value.toLocaleString('en-US')}`,
        subHead: generateSubHead(point, period),
      });
    },
    [generateSubHead],
  );

  const handleScrubEnd = useCallback(() => {
    headerRef.current?.update({
      title: `$${formatPrice(lastPoint.value)}`,
      subHead: generateSubHead(lastPoint, currentPeriod),
    });
  }, [lastPoint, currentPeriod, generateSubHead]);

  const handlePeriodChanged = useCallback(
    (period) => {
      setCurrentPeriod(period);
      const newData = sparklineInteractiveData[period];
      const newLastPoint = newData[newData.length - 1];
      headerRef.current?.update({
        title: `$${formatPrice(newLastPoint.value)}`,
        subHead: generateSubHead(newLastPoint, period),
      });
    },
    [generateSubHead],
  );

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        fill
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        headerNode={
          <SparklineInteractiveHeader
            ref={headerRef}
            defaultLabel="Bitcoin Price"
            defaultSubHead={generateSubHead(lastPoint, currentPeriod)}
            defaultTitle={`$${formatPrice(lastPoint.value)}`}
          />
        }
        onPeriodChanged={handlePeriodChanged}
        onScrub={handleScrub}
        onScrubEnd={handleScrubEnd}
        periods={periods}
        strokeColor="#F7931A"
        styles={{ header: { paddingLeft: 0, paddingRight: 0 } }}
      />
    </Box>
  );
};
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `defaultPeriod` | `string` | Yes | `-` | default period value that the chart will use |
| `formatDate` | `ChartFormatDate<Period>` | Yes | `-` | function used to format the date that is shown in the bottom of the chart as the user scrubs |
| `periods` | `{ label: string; value: Period; }[]` | Yes | `-` | A list of periods that the chart will use. label is what is shown in the bottom of the chart and the value is the key. |
| `strokeColor` | `string` | Yes | `-` | Color of the line* |
| `className` | `string` | No | `-` | Custom class name for the root element. |
| `classNames` | `{ header?: string; root?: string \| undefined; } \| undefined` | No | `-` | Custom class names for the component. |
| `compact` | `boolean` | No | `false` | Show the chart in compact height |
| `data` | `Record<Period, ChartData>` | No | `-` | Chart data bucketed by Period. Period is a string key |
| `disableScrubbing` | `boolean` | No | `false` | Disables the scrub user interaction from the chart |
| `fallback` | `null \| string \| number \| bigint \| false \| true \| ReactElement<unknown, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| Promise<AwaitedReactNode>` | No | `-` | Fallback shown in the chart when data is not available. This is usually a loading state. |
| `fallbackType` | `positive \| negative` | No | `-` | If you use the default fallback then this specifies if the fallback line is decreasing or increasing |
| `fill` | `boolean` | No | `true` | Adds an area fill to the Sparkline |
| `fillType` | `dotted \| gradient` | No | `'gradient'` | Type of fill to use for the area |
| `formatHoverDate` | `((date: Date, period: Period) => string)` | No | `-` | Formats the date above the chart as you scrub. Omit this if you dont want to show the date as the user scrubs |
| `formatHoverPrice` | `((price: number) => string)` | No | `-` | Formats the price above the chart as you scrub. Omit this if you dont want to show the price as the user scrubs |
| `headerNode` | `null \| string \| number \| bigint \| false \| true \| ReactElement<unknown, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| Promise<AwaitedReactNode>` | No | `-` | Adds a header node above the chart. It will be placed next to the period selector on web. |
| `headerTestID` | `string` | No | `-` | Test ID for the header |
| `hidePeriodSelector` | `boolean` | No | `false` | Hides the period selector at the bottom of the chart |
| `hoverData` | `Record<Period, ChartTimeseries[]>` | No | `-` | Optional data to show on hover/scrub instead of the original sparkline. This allows multiple timeseries lines.  Period => timeseries list |
| `onPeriodChanged` | `((period: Period) => void)` | No | `-` | Callback when the user selects a new period. |
| `onScrub` | `((params: ChartScrubParams<Period>) => void)` | No | `-` | Callback used when the user is scrubbing. This will be called for every data point change. |
| `onScrubEnd` | `(() => void)` | No | `-` | Callback when a user finishes scrubbing |
| `onScrubStart` | `(() => void)` | No | `-` | Callback when the user starts scrubbing |
| `periodSelectorPlacement` | `above \| below` | No | `-` | Optional placement prop that position the period selector component above or below the chart |
| `style` | `CSSProperties` | No | `-` | Custom styles for the root element. |
| `styles` | `{ header?: CSSProperties; root?: CSSProperties \| undefined; } \| undefined` | No | `-` | Custom styles for the component. |
| `timePeriodGutter` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | Optional gutter to add to the Period selector. This is useful if you choose to use the full screen width for the chart |
| `yAxisScalingFactor` | `number` | No | `-` | Scales the sparkline to show more or less variance. Use a number less than 1 for less variance and a number greater than 1 for more variance. If you use a number greater than 1 it may clip the boundaries of the sparkline. |


