This boilerplate includes powerful interactive charts powered by Plotly.js with 12+ chart types and WebGL support for high-performance rendering.
- Overview
- Plotly.js Features
- Built-in Chart Examples
- Basic Usage
- Chart Types
- Interactivity
- Styling and Theming
- Performance Optimization
- Best Practices
Plotly.js is a high-level, declarative charting library built on top of D3.js and stack.gl. It provides:
- 12+ chart types: Line, bar, pie, scatter, 3D, heatmap, and more
- Interactive by default: Zoom, pan, hover tooltips, and legends
- WebGL rendering: Hardware-accelerated graphics for large datasets
- Responsive: Automatically adapts to container size
- Export capabilities: Download charts as PNG, SVG, or JSON
- Mobile-friendly: Touch gestures for zoom and pan
- Rich visualizations: Professional-grade charts out of the box
- Minimal code: Declarative API with sensible defaults
- Performance: WebGL support for millions of data points
- Interactivity: Built-in zoom, pan, hover, and click handlers
- Flexibility: Extensive customization options
- Type-safe: Full TypeScript support with
@types/plotly.js
The boilerplate includes examples of:
- Single/multi-line charts
- Stacked area charts
- Time series data
- Trend lines
- Grouped bar charts
- Stacked bar charts
- Horizontal bars
- Waterfall charts
- Pie charts with labels
- Donut charts (pie with hole)
- Sunburst charts (hierarchical pie)
- 2D scatter plots
- Bubble charts (size-encoded)
- Color-coded scatter plots
- Box plots
- Violin plots
- Histograms
- Distribution curves
- Heatmaps with colorscales
- Annotated heatmaps
- Gantt-style timelines
- 3D scatter plots
- 3D surface plots
- Mesh plots
- Full rotation and zoom
- Bar + line overlay
- Multiple y-axes
- Dual chart types
The boilerplate includes 30+ chart examples at /charts:
- User Growth Over Time - Multi-line chart with 3 series (new, active, churned users)
- Revenue Breakdown - Stacked area chart showing revenue categories
- Monthly Active Users by Region - Grouped bar chart comparing 4 regions
- Feature Usage by Plan - Horizontal stacked bar chart
- Tenant Plan Distribution - Donut chart showing plan tiers
- User Role Hierarchy - Sunburst chart with org structure
- Session Duration vs Actions - Color-coded scatter plot
- Tenant Size vs Storage - Bubble chart with size encoding
- Response Time Distribution - Box plot across 5 services
- Session Length Distribution - Histogram with bins
- Activity Heatmap - 7x24 grid showing event density
- Tenant Onboarding Timeline - Gantt-style bar chart
- User Engagement Clustering - 3D scatter plot (WebGL)
- Revenue Surface - 3D surface plot with rotation
- Revenue vs User Growth - Bar + line with dual y-axes
- MRR Movement - Waterfall chart
Already included in the boilerplate:
{
"dependencies": {
"plotly.js": "^3.3.1",
"react-plotly.js": "^2.6.0"
},
"devDependencies": {
"@types/react-plotly.js": "^2.6.4"
}
}import Plot from 'react-plotly.js';
function SimpleLineChart() {
const data = [
{
x: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
y: [10, 15, 13, 17, 22],
type: 'scatter',
mode: 'lines+markers',
marker: { color: 'blue' },
name: 'Sales',
},
];
const layout = {
title: 'Monthly Sales',
xaxis: { title: 'Month' },
yaxis: { title: 'Revenue ($K)' },
};
return <Plot data={data} layout={layout} />;
}function SimpleBarChart() {
const data = [
{
x: ['Product A', 'Product B', 'Product C'],
y: [20, 35, 15],
type: 'bar',
marker: { color: 'green' },
},
];
const layout = {
title: 'Product Sales',
yaxis: { title: 'Units Sold' },
};
return <Plot data={data} layout={layout} />;
}const data = [
{
x: [1, 2, 3, 4, 5],
y: [1, 4, 9, 16, 25],
type: 'scatter',
mode: 'lines',
line: { color: 'blue', width: 2 },
},
];const data = [
{
x: ['Jan', 'Feb', 'Mar'],
y: [10, 15, 13],
fill: 'tonexty',
type: 'scatter',
name: 'Series 1',
},
{
x: ['Jan', 'Feb', 'Mar'],
y: [5, 8, 6],
fill: 'tonexty',
type: 'scatter',
name: 'Series 2',
},
];const data = [
{
x: ['Q1', 'Q2', 'Q3', 'Q4'],
y: [20, 30, 25, 35],
type: 'bar',
name: '2023',
},
{
x: ['Q1', 'Q2', 'Q3', 'Q4'],
y: [25, 35, 30, 40],
type: 'bar',
name: '2024',
},
];
const layout = {
barmode: 'group',
};const data = [
{
values: [35, 25, 20, 15, 5],
labels: ['Free', 'Pro', 'Enterprise', 'Trial', 'Other'],
type: 'pie',
hole: 0.4, // Makes it a donut chart (0 = pie, 0.4 = donut)
},
];const data = [
{
x: [1, 2, 3, 4, 5],
y: [1, 4, 2, 3, 5],
mode: 'markers',
type: 'scatter',
marker: {
size: 12,
color: ['red', 'blue', 'green', 'orange', 'purple'],
},
},
];const data = [
{
x: [1, 2, 3, 4],
y: [10, 11, 12, 13],
mode: 'markers',
marker: {
size: [40, 60, 80, 100], // Bubble sizes
color: [10, 20, 30, 40], // Color scale
colorscale: 'Viridis',
showscale: true,
},
type: 'scatter',
},
];const data = [
{
y: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
type: 'box',
name: 'Dataset 1',
},
{
y: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
type: 'box',
name: 'Dataset 2',
},
];const data = [
{
x: [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5],
type: 'histogram',
marker: { color: 'blue' },
},
];const data = [
{
z: [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
],
x: ['A', 'B', 'C'],
y: ['X', 'Y', 'Z'],
type: 'heatmap',
colorscale: 'Viridis',
},
];const data = [
{
x: [1, 2, 3, 4, 5],
y: [1, 2, 3, 4, 5],
z: [1, 4, 9, 16, 25],
mode: 'markers',
type: 'scatter3d',
marker: {
size: 12,
color: [1, 2, 3, 4, 5],
colorscale: 'Viridis',
},
},
];
const layout = {
scene: {
xaxis: { title: 'X Axis' },
yaxis: { title: 'Y Axis' },
zaxis: { title: 'Z Axis' },
},
};const data = [
{
z: [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
],
type: 'surface',
colorscale: 'Portland',
},
];const data = [
{
x: ['Start', 'New', 'Expansion', 'Churn', 'End'],
y: [100, 50, 30, -20, 160],
type: 'waterfall',
connector: {
line: { color: 'rgb(63, 63, 63)' },
},
},
];Plotly charts are interactive by default:
- Zoom: Click and drag to zoom into regions
- Pan: Shift + drag to pan
- Hover: Hover over data points to see tooltips
- Legend: Click legend items to show/hide series
- Reset: Double-click to reset zoom
- Download: Camera icon to export as PNG
function InteractiveChart() {
const handleClick = (event) => {
const point = event.points[0];
console.log('Clicked:', point.x, point.y);
};
return (
<Plot
data={data}
layout={layout}
onClick={handleClick}
/>
);
}const data = [
{
x: [1, 2, 3],
y: [10, 20, 30],
type: 'scatter',
mode: 'markers',
hovertemplate: '<b>X</b>: %{x}<br><b>Y</b>: %{y}<br><extra></extra>',
},
];const config = {
displayModeBar: false, // Hide toolbar
staticPlot: true, // Make chart non-interactive
scrollZoom: false, // Disable scroll zoom
};
<Plot data={data} layout={layout} config={config} />const data = [
{
x: ['A', 'B', 'C'],
y: [10, 20, 30],
type: 'bar',
marker: {
color: ['#3b82f6', '#10b981', '#f59e0b'], // Custom colors
},
},
];function DarkModeChart() {
const layout = {
title: 'Dark Mode Chart',
paper_bgcolor: '#1f2937', // Background
plot_bgcolor: '#111827', // Plot area
font: { color: '#f9fafb' }, // Text color
xaxis: {
gridcolor: '#374151',
zerolinecolor: '#374151',
},
yaxis: {
gridcolor: '#374151',
zerolinecolor: '#374151',
},
};
return <Plot data={data} layout={layout} />;
}const layout = {
autosize: true,
margin: { t: 50, r: 50, b: 50, l: 50 },
};
const config = {
responsive: true,
};
<Plot
data={data}
layout={layout}
config={config}
style={{ width: '100%', height: '400px' }}
/>const layout = {
font: {
family: 'Inter, sans-serif',
size: 14,
color: '#374151',
},
title: {
font: {
family: 'Inter, sans-serif',
size: 20,
weight: 600,
},
},
};For charts with 1000+ points, use WebGL mode:
const data = [
{
x: largeArrayX, // 10,000+ points
y: largeArrayY,
type: 'scattergl', // WebGL mode
mode: 'markers',
marker: { size: 3 },
},
];WebGL-enabled types:
scattergl- 2D scatter (WebGL)scatter3d- 3D scatter (WebGL by default)surface- 3D surface (WebGL by default)mesh3d- 3D mesh (WebGL by default)
Reduce data points for smoother rendering:
// Before: 10,000 points
const fullData = [...]; // 10k points
// After: Downsample to 1,000 points
const downsampledData = fullData.filter((_, i) => i % 10 === 0);Load charts only when visible:
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
function ChartsPage() {
return (
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart />
</Suspense>
);
}Prevent unnecessary re-renders:
import { useMemo } from 'react';
function OptimizedChart({ rawData }) {
const data = useMemo(() => {
return [
{
x: rawData.map((d) => d.x),
y: rawData.map((d) => d.y),
type: 'scatter',
},
];
}, [rawData]);
return <Plot data={data} layout={layout} />;
}For real-time data, debounce updates:
import { useEffect, useState } from 'react';
import { debounce } from 'lodash';
function RealTimeChart({ liveData }) {
const [chartData, setChartData] = useState(liveData);
useEffect(() => {
const updateChart = debounce((newData) => {
setChartData(newData);
}, 500);
updateChart(liveData);
}, [liveData]);
return <Plot data={chartData} layout={layout} />;
}const layout = {
title: {
text: 'Monthly Active Users by Region',
font: { size: 20 },
},
xaxis: {
title: 'Month',
},
yaxis: {
title: 'Active Users',
},
};| Data Type | Recommended Chart |
|---|---|
| Trends over time | Line chart |
| Comparisons | Bar chart |
| Proportions | Pie/Donut chart |
| Relationships | Scatter plot |
| Distributions | Histogram, Box plot |
| Hierarchies | Sunburst, Treemap |
| Geographic | Choropleth map |
- Consistent colors: Same data = same color across charts
- Accessible palettes: Use colorblind-friendly palettes
- Semantic colors: Green = positive, Red = negative
- Limit colors: Max 5-7 distinct colors per chart
Add annotations for important events:
const layout = {
annotations: [
{
x: 'Feb',
y: 150,
text: 'Product Launch',
showarrow: true,
arrowhead: 2,
ax: 0,
ay: -40,
},
],
};const layout = {
autosize: true,
margin: { t: 50, r: 50, b: 50, l: 50 },
};
<Plot
data={data}
layout={layout}
useResizeHandler={true}
style={{ width: '100%', height: '100%' }}
/>function SafeChart({ data }) {
if (!data || data.length === 0) {
return <div>No data available</div>;
}
return <Plot data={data} layout={layout} />;
}Enable export functionality:
const config = {
toImageButtonOptions: {
format: 'png', // 'png', 'svg', 'jpeg', 'webp'
filename: 'chart',
height: 600,
width: 1000,
scale: 2, // Higher scale = higher resolution
},
};
<Plot data={data} layout={layout} config={config} />const data = [
{
x: ['Jan', 'Feb', 'Mar', 'Apr'],
y: [100, 120, 140, 160],
type: 'bar',
name: 'Revenue',
yaxis: 'y',
},
{
x: ['Jan', 'Feb', 'Mar', 'Apr'],
y: [1000, 1100, 1200, 1300],
type: 'scatter',
mode: 'lines+markers',
name: 'Users',
yaxis: 'y2',
},
];
const layout = {
title: 'Revenue vs Users',
yaxis: {
title: 'Revenue ($K)',
},
yaxis2: {
title: 'Users',
overlaying: 'y',
side: 'right',
},
};const frames = [
{
name: 'frame1',
data: [{ x: [1, 2, 3], y: [1, 2, 3] }],
},
{
name: 'frame2',
data: [{ x: [1, 2, 3], y: [2, 3, 4] }],
},
];
const layout = {
updatemenus: [
{
buttons: [
{
args: [null, { frame: { duration: 500 } }],
label: 'Play',
method: 'animate',
},
],
},
],
};
<Plot data={data} layout={layout} frames={frames} />const customColorscale = [
[0, '#3b82f6'], // Blue
[0.5, '#ffffff'], // White
[1, '#ef4444'], // Red
];
const data = [
{
z: [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
type: 'heatmap',
colorscale: customColorscale,
},
];Cause: Missing dimensions.
Solution: Set explicit width/height:
<Plot
data={data}
layout={layout}
style={{ width: '100%', height: '400px' }}
/>Cause: Too many data points.
Solution: Use WebGL mode (scattergl) or downsample data.
Cause: Missing useResizeHandler.
Solution:
<Plot
data={data}
layout={{ ...layout, autosize: true }}
useResizeHandler={true}
style={{ width: '100%' }}
/>Cause: Missing type definitions.
Solution: Install types:
pnpm add -D @types/react-plotly.js @types/plotly.js