Canvas And React: A Simple Guide To Data Charts

Written by maksymmostovyi | Published 2024/01/01
Tech Story Tags: front-end-development | javascript | react | canvas | bar-chart | pie-chart | data-visualization | creating-data-charts

TLDRThe power of canvas. Make your data visualizations flexible and performant. via the TL;DR App

In the realm of web development, the HTML5 Canvas API stands as a pivotal tool for rendering graphics and creating animations directly in the browser. It offers a vast playground for developers and designers to bring their visual ideas to life. Unlike other graphic methods that rely on SVG or other DOM elements, Canvas works through a bitmap approach, enabling the direct drawing of shapes, images, and even animations.

Why Choose Canvas for Your React App?

Integrating Canvas into a React application opens up a world of possibilities. Here are the core advantages:

  • Pixel-Level Control: Canvas gives you the freedom to manipulate individual pixels, allowing for intricate and detailed graphical representations.

  • Performance: For data-heavy visualizations or complex animations, Canvas outperforms its counterparts like SVG, especially when dealing with a large number of objects.

  • Flexibility and Creativity: From charts, and graphs to interactive games, Canvas's versatility makes it a perfect choice for a broad range of applications.

  • Integration with React: Canvas blends seamlessly with React’s component-based architecture, enabling the creation of highly interactive and dynamic user interfaces.

Let’s build a pie and bar chart in the React app, just using Canvas.

For that, we need to create a simple React app. I did it using Vite. Then we need to create components that will represent charts and integrate them into the main app js component.

Here is what the project structure will look like.

Let's jump to the code of each chart. I left an explanation for each move in the code comments.

Bar chart

import React, { useRef, useEffect } from 'react';

export const CanvasBarChart = ({ data, labels }) => {
    // useRef to reference the canvas element
    const canvasRef = useRef(null);

    useEffect(() => {
        // Get the canvas element and its drawing context
        const canvas = canvasRef.current;
        const ctx = canvas.getContext('2d');

        // Clear the canvas to start fresh each render
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // Settings for the bar chart
        const padding = 50; // Space around the chart to not draw on the edge of the canvas
        const barWidth = 40; // The width of each bar
        const gap = 15; // The gap between each bar
        const yAxisLabelPadding = 10; // Space between the Y-axis and the labels
        let x = padding * 1.5; // Initial X position, set to padding times 1.5 to offset the first bar

        // Calculate the maximum value for the Y-axis
        const maxValue = Math.max(...data);

        // Drawing the Y-axis
        ctx.beginPath();
        ctx.moveTo(padding, padding); // Start at the upper-left corner with padding
        ctx.lineTo(padding, canvas.height - padding); // Draw down to the bottom-left corner with padding
        ctx.strokeStyle = '#000'; // Set the color of the axis line
        ctx.stroke(); // Execute the drawing of the line

        // Drawing the X-axis
        ctx.beginPath();
        ctx.moveTo(padding, canvas.height - padding); // Start at the bottom-left corner with padding
        ctx.lineTo(canvas.width - padding, canvas.height - padding); // Draw to the bottom-right corner with padding
        ctx.stroke(); // Execute the drawing of the line

        // Drawing bars and X-axis labels
        data.forEach((value, index) => {
            // Set the fill color for the bar
            ctx.fillStyle = '#3498db';
            // Draw the bar
            const barHeight = (value / maxValue) * (canvas.height - padding * 2); // Calculate bar height based on value
            ctx.fillRect(x, canvas.height - padding - barHeight, barWidth, barHeight); // Draw the bar

            // Set the fill color for the text (X-axis labels)
            ctx.fillStyle = '#000';
            ctx.font = '14px Arial';
            // Draw the X-axis label below the bar
            ctx.fillText(labels[index], x + (barWidth / 2) - (ctx.measureText(labels[index]).width / 2), canvas.height - padding + 20);

            x += barWidth + gap; // Move the x position for the next bar
        });

        // Drawing Y-axis labels
        const numberOfYLabels = 5; // For example, 5 labels on the Y-axis
        for (let i = 0; i <= numberOfYLabels; i++) {
            const yValue = (maxValue / numberOfYLabels) * i; // Calculate the value for the label
            const yPosition = canvas.height - padding - (yValue / maxValue) * (canvas.height - padding * 2); // Calculate the Y position

            // Draw the Y-axis label
            ctx.fillStyle = '#000';
            ctx.font = '14px Arial';
            ctx.fillText(yValue.toFixed(0), padding - yAxisLabelPadding - ctx.measureText(yValue.toFixed(0)).width, yPosition + 4); // Center text vertically
        }

    }, [data, labels]); // Dependencies of useEffect: the component will re-render when data or labels change

    // Return the canvas element with a ref attached
    return <canvas ref={canvasRef} width={500} height={500} />;
};

Pie chart

import React, { useRef, useEffect } from 'react';

export const CanvasPieChart = ({ data, colors, labels }) => {
    const canvasRef = useRef(null);

    useEffect(() => {
        // Get the canvas context
        const canvas = canvasRef.current;
        const ctx = canvas.getContext('2d');
        // Clear the entire canvas to start fresh
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // Setup for the pie chart
        let total = data.reduce((sum, value) => sum + value, 0);
        let startAngle = 0;
        const radius = canvas.height / 4; // Pie radius is 1/4 the height of the canvas
        const centerX = radius + 20; // Center the pie on the left part of the canvas
        const centerY = radius + 20;

        // Draw the pie slices
        data.forEach((value, index) => {
            // Calculate the end angle of the slice
            let sliceAngle = (value / total) * 2 * Math.PI;
            // Set the color for the current slice
            ctx.fillStyle = colors[index];
            // Start a new path for the slice
            ctx.beginPath();
            // Move to the center of the pie
            ctx.moveTo(centerX, centerY);
            // Draw the arc for the slice
            ctx.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
            // Close the path and fill the slice
            ctx.closePath();
            ctx.fill();
            // Update the start angle for the next slice
            startAngle += sliceAngle;
        });

        // Setup for the legend
        let legendY = 20; // Start position for the legend on the canvas
        const legendX = 2 * radius + 40; // Position the legend to the right of the pie

        // Draw the legend
        labels.forEach((label, index) => {
            const legendColor = colors[index];
            // Draw the color box for the legend
            ctx.fillStyle = legendColor;
            ctx.fillRect(legendX, legendY, 20, 20); // Draw the legend color box
            // Set the text style for the legend
            ctx.fillStyle = '#000';
            ctx.font = '16px Arial';
            // Draw the label text beside the color box
            ctx.fillText(label, legendX + 25, legendY + 15);
            // Update the Y position for the next legend item
            legendY += 30;
        });
    }, [data, colors, labels]); // Redraw when data, colors, or labels change

    return <canvas ref={canvasRef} width={600} height={600} />;
};

App js

import { CanvasBarChart } from "./components/CanvasBarChart/CanvasBarChart.jsx";
import { CanvasPieChart } from "./components/CanvasPieChart/CanvasPieChart.jsx";
import './App.css'

function App() {
    const barData = [100, 200, 150, 250, 300]; // Sample data
    const barLabels = ['Jan', 'Feb', 'Mar', 'Apr', 'May']; // Sample labels

    const pieData = [300, 150, 100, 200]; // Sample data for pie chart
    const pieColors = ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0']; // Sample colors for pie chart
    const pieLabels = ['Category A', 'Category B', 'Category C', 'Category D']; // Example labels

    return (
        <div className="App">
            <div className="chart">
                <CanvasBarChart data={barData} labels={barLabels} />
            </div>
            <div className="chart">
                <CanvasPieChart data={pieData} colors={pieColors} labels={pieLabels} />
            </div>
        </div>
    );
}

export default App

And here are the charts look like:

Pie

Bar

As we wrap up this journey through the vibrant world of Canvas in React, it's clear that the power and flexibility of Canvas make it an unparalleled choice for data visualization in web applications. Whether you're a seasoned developer or a curious newcomer, the world of Canvas in React is an adventure worth embarking on.

And to all my dear readers, remember, every pixel on your Canvas is a universe of possibilities – so keep drawing, keep experimenting, and most importantly, have fun! Your creativity is the only limit in this pixel-perfect world. If you want to play around with the code mentioned in the article, feel free to get it from my github. Cheers!


Written by maksymmostovyi | Software engineer with expertise in JavaScript, Angular, and React. One of my key skills is Data Visualisation.
Published by HackerNoon on 2024/01/01