# 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-visualization' ``` ## 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 ( ); }; ``` ### 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 ( ); }; ``` ### 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 ( ); }; ``` ### 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 ( ); }; ``` ### 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 ( ); }; ``` ### 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 ( } onPeriodChanged={handlePeriodChanged} onScrub={handleScrub} onScrubEnd={handleScrubEnd} periods={periods} strokeColor="#F7931A" /> ); }; ``` ### 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 ( ); }; ``` ### 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 ( ); }; ``` ### 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 ( } onPeriodChanged={handlePeriodChanged} onScrub={handleScrub} onScrubEnd={handleScrubEnd} periods={periods} strokeColor="#F7931A" styles={{ header: { paddingLeft: 0, paddingRight: 0 } }} /> ); }; ``` ## Props | Prop | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `defaultPeriod` | `string` | Yes | `-` | default period value that the chart will use | | `formatDate` | `ChartFormatDate` | 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` | No | `-` | Chart data bucketed by Period. Period is a string key | | `disableScrubbing` | `boolean` | No | `false` | Disables the scrub user interaction from the chart | | `fallback` | `ReactNode` | 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` | `ReactNode` | 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` | 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) => 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` | `Space` | 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. |