Recently, while working on a finance project, I realized that Sparklines are a common requirement in most fintech applications at some point in their development.
So here I am, presenting a small tutorial on how to create a Sparkline component in React without any dependencies.
What is a Sparkline?
First, a small introduction.
A Sparkline is a minimalist, small-scale chart that provides a concise and clear representation of a data trend. The term was introduced in the early 2000s by data visualization expert Edward Tufte in his statistical researches.
Sparklines are often embedded in compact spaces, such as data cards or table cells, making them perfect for providing a snapshot of data without overwhelming the viewer with details.
This simplicity allows for quick insight and comparison, which is why Sparklines are favoured in the financial industry and other fields where concise data presentation is essential.
Demo
You can find the code here: Sparkline.tsx.
I have a simple boilerplate with Vite, TypeScript, React and TailwindCSS that I used for this repository.
Implementation
I want to create a component that takes an array of numbers as props and automatically adjusts to the parent's size.
The basic setup is straightforward. As mentioned, I'm using Tailwind CSS, so all the styling classes are derived from there.
import React from 'react';
type SparklineProps = {
data: number[];
}
const Sparkline: React.FC<SparklineProps> = ({ data }) => {
return (
<div class="w-full h-full">
</div>
);
};
export default Sparkline;
To avoid adding any dependencies, I'll use <svg>
and <polyline>
elements to render the chart.
The layout will include some pre-calculated values. The width
and height
define the dimensions of the SVG, as the SVG element requires explicit values. The points
attribute is a string that specifies the coordinates for the line.
<div class="w-full h-full">
<svg width={width} height={height}>
<polyline
className="fill-none stroke-current"
points={points}
strokeLinejoin="round"
/>
</svg>
</div>
Next, I'll write the basic code to calculate the points for the polyline. I've included inline comments to explain each step of the process.
// Hardcode the dimensions for now
const width = 100;
const height = 32;
// Don't render the chart for less than 2 points
if (!data || data.length < 2) return null;
// Calculate the min and max values of the data
const min = Math.min(...data);
const max = Math.max(...data);
// Construct the points string with the simple mapping
// Scales are [0, length] -> [0, width] and [min, max] -> [0, height]
const points = data.map((value, index) => {
const x = (index / (data.length - 1)) * width;
const y = ((value - min) / (max - min)) * height;
return `${x},${height - y}`;
}).join(' ');
This code already provides a working Sparkline, but it has one limitation — it's not responsive. I need my charts to adapt to their parent container's size precisely.
There are several ways to achieve this, but I opted to use ResizeObserver. By listening for size changes on my <div>
element, I can update the dimensions dynamically, making them part of the component's state.
To implement this, I first add the necessary imports:
import React, { useRef, useEffect, useState } from 'react';
Next, I use a ref to mark the <div>
element:
<div ref={containerRef} class="w-full h-full">
With everything set up, I can now write a useEffect hook to handle all the changes dynamically. And that's it!
Here is the full code for the responsive Sparkline component:
import React, { useRef, useEffect, useState } from 'react';
type SparklineProps = {
data: number[];
}
const Sparkline: React.FC<SparklineProps> = ({ data }) => {
// Reference to the container element that is changeable
const containerRef = useRef<HTMLDivElement>(null);
// Current dimensions for the chart
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
// Create effect to run when component mounts
useEffect(() => {
const container = containerRef.current;
// Do nothing if there is no element
if (!container) return;
const updateDimensions = () => {
setDimensions({
width: container.clientWidth,
height: container.clientHeight,
});
};
// Set initial dimensions
updateDimensions();
// Create the observer and subscribe to container's changes
const resizeObserver = new ResizeObserver(updateDimensions);
resizeObserver.observe(container);
return () => {
// Disconnect when the component unmounts
resizeObserver.disconnect();
};
}, []);
// Don't render the chart for less than 2 points
if (!data || data.length < 2) return null;
// Calculate the min and max values of the data
const min = Math.min(...data);
const max = Math.max(...data);
// Construct the points string with the simple mapping
// Scales are [0, length] -> [0, width] and [min, max] -> [0, height]
const { width, height } = dimensions;
const points = data.map((value, index) => {
const x = (index / (data.length - 1)) * width;
const y = ((value - min) / (max - min)) * height;
return `${x},${height - y}`;
}).join(' ');
return (
<div ref={containerRef} style={{ width: '100%', height: '100%' }}>
<svg width={width} height={height} className="overflow-visible">
<polyline
className="fill-none stroke-2 stroke-current"
points={points}
strokeLinejoin="round"
/>
</svg>
</div>
);
};
export default Sparkline;
Here is a simple usage example:
<div className="w-[480px] h-[160px]">
<Sparkline data={...} />
</div>
Conclusion
Although this component works well for most cases, it is still quite basic. Further improvements could include customizing the appearance, handling hover events to highlight values, or adding animations and tooltips.
Thank you for reading and happy coding! ☺️
Top comments (0)