
Particle Charts is a zero-dependency JavaScript charting library that renders data as animated particle fields on an HTML canvas.
It can handle line, area, bar, bubble, radar, pie, and donut charts from plain arrays, records, multi-series data, or x/y points.
The distinctive part appears when the data changes. Existing particles move into their new positions, which turns a normal chart update into a morph between datasets and chart shapes.
Features
- Line, area, bar, bubble, radar, pie, and donut rendering.
- Particle morphs between data updates and chart shapes.
- Grouped, stacked, and horizontal bar layouts.
- Multi-series data and continuous numeric axes.
- Bubble sizing from a third data dimension.
- Responsive Canvas2D rendering with HiDPI handling.
- Tooltips, crosshairs, axes, legends, and value labels.
- Dark and light chart themes.
- Screen-reader data tables and keyboard legend controls.
- CDN, ES module, CommonJS, and TypeScript distribution.
See It In Action
How To Use It
Installation
Load the browser build directly in your HTML page:
<script src="https://cdn.jsdelivr.net/npm/particle-charts@1/dist/particle-charts.min.js"></script>
Or install and import it via NPM:
npm install particle-charts
import { ParticleChart } from 'particle-charts';
CommonJS is available through the package entry:
const { ParticleChart } = require('particle-charts');
Basic Usage
Create a chart container.
<div id="salesChart" style="height: 320px"></div>
Create a new ParticleChart instance, specify the chart type and prepare your data. All available chart types:
* `line` for smooth, linear, or stepped lines.
* `area` for a particle-filled line chart.
* `bar` for grouped, stacked, or horizontal bars.
* `bubble` for x/y points with optional size values.
* `radar` for multi-axis polygon charts.
* `pie` for part-to-whole data.
* `donut` for a pie chart with a center opening.
<script>
const chart = new ParticleCharts.ParticleChart('#salesChart', {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
values: [38, 52, 47, 69]
}
});
</script>
Data Formats
A plain array is enough for a basic single-series chart:
const data = [8, 13, 11, 20, 18, 27];
Labels and values can use several structures:
const labeledPoints = [
{ label: 'Jan', value: 8 },
{ label: 'Feb', value: 13 },
{ label: 'Mar', value: 11 }
];
const labeledValues = {
labels: ['Jan', 'Feb', 'Mar'],
values: [8, 13, 11]
};
const trafficSources = {
Organic: 54,
Referral: 28,
Paid: 18
};
Multi-series charts share labels:
const data = {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{
name: 'Revenue',
data: [44, 57, 61, 76],
color: '#3987e5'
},
{
name: 'Costs',
data: [31, 36, 43, 49]
}
]
};
Line and bubble charts also accept numeric x/y coordinates:
const data = {
series: [
{
name: 'Latency',
data: [
{ x: 0, y: 18 },
{ x: 5, y: 24 },
{ x: 12, y: 21 }
]
}
]
};
Bubble charts read a third value for mark size:
const data = {
series: [
{
name: 'Markets',
data: [
{ x: 12, y: 34, r: 16 },
{ x: 27, y: 49, r: 38 },
{ x: 41, y: 28, r: 25 }
]
}
]
};
Chart Helpers
Module builds expose shorthand functions for each primary chart type:
import { donut } from 'particle-charts';
const chart = donut('#trafficChart', {
Direct: 4820,
Search: 3140,
Referral: 1270
});
Update Data
Call update() when the dataset changes. Existing particles receive new destinations and move into the new chart geometry.
chart.update({
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
values: [22, 31, 29, 44]
});
The second argument changes configuration during the update:
chart.update(
{
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
values: [22, 31, 29, 44]
},
{
type: 'area'
}
);
Light And Dark Themes
Switch the chart chrome with theme:
chart.setOptions({
theme: 'light',
particleBloom: 0.15,
particleOpacity: 0.9
});
A page that follows the operating system preference can update the chart when the color scheme changes:
const media = matchMedia('(prefers-color-scheme: light)');
function syncChartTheme() {
chart.setOptions({
theme: media.matches ? 'light' : 'dark'
});
}
media.addEventListener('change', syncChartTheme);
syncChartTheme();
All Configuration Options
Core Options
type(string): Chart type. Default isline.column,scatter, andspiderare aliases.data(ChartData): Chart data in one of the accepted input structures.theme(dark | light): Chart chrome theme. Default isdark.background(string): Canvas background fill. Default istransparent.padding(number | array | object | null): Plot padding. Default is automatic measurement.responsive(boolean): Recalculates the layout after container size changes. Default istrue.maxDpr(number): Caps the HiDPI backing-store ratio. Default is2.pauseWhenHidden(boolean): Stops the render loop when the tab hides or the chart leaves the viewport. Default istrue.animate(boolean): Controls the entrance animation. Default istrue.duration(number): Entrance duration in milliseconds. Default is1100.stagger(number): Spreads entrance timing across particles from0to1. Default is0.5.showAxis(boolean): Shows the chart axes. Default istrue.showGrid(boolean): Shows grid lines. Default istrue.showLegend(boolean): Shows the legend when applicable. Default istrue.showTooltip(boolean): Shows hover tooltips and crosshairs. Default istrue.showValues(boolean): Prints values beside applicable marks. Default isfalse.
Particle Options
particle.color(string | string[] | function | null): Particle colors. Default uses the built-in palette.particle.size(number): Base particle radius in CSS pixels. Default is0.8.particle.sizeJitter(number): Random size variation from0to1. Default is0.particle.density(number): Multiplier for the calculated particle budget. Default is15.particle.max(number): Hard particle-count ceiling. Default is50000.particle.bloom(number): Additive glow strength from0to1. Default is0.8.particle.bloomRadius(number): Bloom blur radius in CSS pixels. Default is14.particle.opacity(number): Global particle alpha. Default is0.7.particle.jitter(number): Idle drift amplitude in pixels. Default is1.particle.jitterSpeed(number): Idle drift speed. Default is1.particle.speed(number): Spring stiffness toward each particle destination. Default is0.085.particle.damping(number): Motion damping. Default is0.78.particle.shape(soft | dot | square): Particle rendering style. Default issoft.
Axis Options
axis.color(string): Axis line color.axis.gridColor(string): Grid line color.axis.textColor(string): Tick and axis-title color.axis.crosshairColor(string): Hover crosshair color.axis.fontFamily(string): Font used for chart labels.axis.fontSize(number): Axis label size. Default is11.axis.ticks(number): Approximate y-axis tick count. Default is5.axis.grid(boolean): Shows y-axis grid lines. Default istrue.axis.xGrid(boolean): Shows x-axis grid lines. Default isfalse.axis.xLabels(boolean): Shows x-axis labels. Default istrue.axis.yLabels(boolean): Shows y-axis labels. Default istrue.axis.beginAtZero(boolean): Includes zero in the value domain. Default istrue.axis.min(number | null): Fixed minimum value. Default is automatic.axis.max(number | null): Fixed maximum value. Default is automatic.axis.format(function | null): Formats value-axis labels and tooltip values.axis.xTitle(string): X-axis title. Default is empty.axis.yTitle(string): Y-axis title. Default is empty.
Bubble charts change axis.beginAtZero to false and axis.xGrid to true unless those values are set explicitly.
Legend Options
legend.position(top | bottom | left | right): Legend position.legend.align(start | center | end): Legend alignment.legend.interactive(boolean): Lets legend controls mute and restore a series. Default istrue.legend.markerSize(number): Legend marker size. Default is8.legend.fontSize(number): Legend text size. Default is12.legend.color(string): Legend text color.
Line, area, bar, bubble, and radar charts place the legend at the bottom and center it by default. Pie and donut charts retain the top/start defaults.
Tooltip Options
tooltip.format(function | null): Returns custom tooltip HTML.tooltip.background(string): Tooltip background.tooltip.color(string): Tooltip text color.tooltip.borderColor(string): Tooltip border color.
Line And Area Options
line.curve(smooth | linear | step): Line interpolation. Default issmooth.line.width(number): Particle stroke width. Default is3.2.line.area(boolean): Fills the region beneath the line. Default isfalse.line.areaAmount(number): Particle share assigned to the area fill. Default is0.55.line.areaFade(number): Concentrates fill particles near the line. Default is0.9.line.points(boolean): Shows particle clusters at data points. Default istrue.line.pointRadius(number): Point-cluster radius. Default is4.5.
Bar Options
bar.padding(number): Gap between categories. Default is0.3.bar.groupPadding(number): Gap between bars inside a group. Default is0.16.bar.stacked(boolean): Stacks multiple series. Default isfalse.bar.horizontal(boolean): Draws horizontal bars. Default isfalse.bar.fade(number): Thins particle density toward the growing end. Default is0.45.bar.radius(number): Rounded cap radius. Default is4.
Bubble Options
bubble.minRadius(number): Smallest bubble radius. Default is5.bubble.maxRadius(number): Largest bubble radius. Default is30.bubble.edgeFade(number): Feathers the bubble rim. Default is0.35.bubble.minValue(number | null): Fixed lower end of the size domain.bubble.maxValue(number | null): Fixed upper end of the size domain.bubble.outline(boolean): Draws a ring behind every bubble. Default isfalse.
Radar Options
radar.levels(number): Number of web rings. Default is4.radar.shape(polygon | circle): Web-ring shape. Default ispolygon.radar.width(number): Particle outline width. Default is2.6.radar.fill(boolean): Fills the radar polygon. Default istrue.radar.fillAmount(number): Particle share assigned to the fill. Default is0.55.radar.fillFade(number): Thins the fill toward the center. Default is0.55.radar.points(boolean): Shows vertex particle clusters. Default istrue.radar.pointRadius(number): Vertex-cluster radius. Default is4.radar.startAngle(number): First spoke angle in degrees. Default is-90.
Pie And Donut Options
pie.innerRadius(number): Hole size as a fraction of the outer radius. Pie defaults to0.pie.startAngle(number): First slice angle in degrees. Default is-90.pie.padAngle(number): Slice gap in degrees. Default is1.2.pie.radius(number): Chart radius relative to available plot space. Default is0.95.pie.edgeFade(number): Feathers the outer edge. Default is0.25.pie.labels(percent | value | label | none): Label content when values are shown. Default ispercent.pie.center(auto | total | none | string): Controls donut center content.
Flat Option Aliases
particleColor→particle.color.particleSize→particle.size.particleDensity→particle.density.particleCount→particle.max.particleBloom→particle.bloom.particleOpacity→particle.opacity.particleJitter→particle.jitter.particleSpeed→particle.speed.particleShape→particle.shape.colors→particle.color.curve→line.curve.fillArea→line.area.lineWidth→line.width.showPoints→line.points.stacked→bar.stacked.horizontal→bar.horizontal.barPadding→bar.padding.minRadius→bubble.minRadius.maxRadius→bubble.maxRadius.levels→radar.levels.webShape→radar.shape.innerRadius→pie.innerRadius.startAngle→pie.startAngle.padAngle→pie.padAngle.legendPosition→legend.position.legendAlign→legend.align.min→axis.min.max→axis.max.beginAtZero→axis.beginAtZero.valueFormat→axis.format.format→axis.format.ticks→axis.ticks.xTitle→axis.xTitle.yTitle→axis.yTitle.fontFamily→axis.fontFamily.textColor→axis.textColor.axisColor→axis.color.gridColor→axis.gridColor.crosshairColor→axis.crosshairColor.
API Methods
// Replace the data and optionally change configuration.
chart.update(nextData, options);
// Change configuration while keeping the current data.
chart.setOptions({
theme: 'light'
});
// Recalculate the layout.
chart.resize();
// Mute or restore a series or radial slice.
chart.toggleSeries('Revenue');
// Pause the render loop.
chart.stop();
// Resume the render loop.
chart.start();
// Export the current frame.
const png = chart.toDataURL();
// Export another image format or quality.
const jpeg = chart.toDataURL('image/jpeg', 0.9);
// Remove the chart, listeners, and render loop.
chart.destroy();
Alternatives
- Modern Canvas JavaScript Charting Library – MyChart.js
- Lightweight Performant Canvas Charting Library – Chartie
- GPU-Powered WebGL Canvas Chart Library for JavaScript – WebGL Chart
- Responsive & Animated Chart JavaScript Library – RGraph
FAQs
Q: Why is my Particle Charts chart blank?
A: Check the container height first. The chart needs a measurable height before it can calculate its plot area.
Q: How can I reduce the rendering workload?
A: Lower particleDensity, set an appropriate particleCount ceiling, or use particleJitter: 0 for charts that do not need continuous idle movement.







