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!