DEV Community

Cover image for How to Create a Sparkline Component in React
Nataliya Stepanova
Nataliya Stepanova

Posted on

How to Create a Sparkline Component in React

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

Simple sparkline

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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(' ');
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

Next, I use a ref to mark the <div> element:

<div ref={containerRef} class="w-full h-full">
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Here is a simple usage example:

<div className="w-[480px] h-[160px]">
  <Sparkline data={...} />
</div>
Enter fullscreen mode Exit fullscreen mode

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)