Particle Charts: Animated Canvas Charts Made of Particles

Category: Chart & Graph , Javascript , Recommended | September 3, 2026
Authorbwilliford
Last UpdateSeptember 3, 2026
LicenseMIT
Views0 views
Particle Charts: Animated Canvas Charts Made of Particles

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 is line. column, scatter, and spider are aliases.
  • data (ChartData): Chart data in one of the accepted input structures.
  • theme (dark | light): Chart chrome theme. Default is dark.
  • background (string): Canvas background fill. Default is transparent.
  • padding (number | array | object | null): Plot padding. Default is automatic measurement.
  • responsive (boolean): Recalculates the layout after container size changes. Default is true.
  • maxDpr (number): Caps the HiDPI backing-store ratio. Default is 2.
  • pauseWhenHidden (boolean): Stops the render loop when the tab hides or the chart leaves the viewport. Default is true.
  • animate (boolean): Controls the entrance animation. Default is true.
  • duration (number): Entrance duration in milliseconds. Default is 1100.
  • stagger (number): Spreads entrance timing across particles from 0 to 1. Default is 0.5.
  • showAxis (boolean): Shows the chart axes. Default is true.
  • showGrid (boolean): Shows grid lines. Default is true.
  • showLegend (boolean): Shows the legend when applicable. Default is true.
  • showTooltip (boolean): Shows hover tooltips and crosshairs. Default is true.
  • showValues (boolean): Prints values beside applicable marks. Default is false.

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 is 0.8.
  • particle.sizeJitter (number): Random size variation from 0 to 1. Default is 0.
  • particle.density (number): Multiplier for the calculated particle budget. Default is 15.
  • particle.max (number): Hard particle-count ceiling. Default is 50000.
  • particle.bloom (number): Additive glow strength from 0 to 1. Default is 0.8.
  • particle.bloomRadius (number): Bloom blur radius in CSS pixels. Default is 14.
  • particle.opacity (number): Global particle alpha. Default is 0.7.
  • particle.jitter (number): Idle drift amplitude in pixels. Default is 1.
  • particle.jitterSpeed (number): Idle drift speed. Default is 1.
  • particle.speed (number): Spring stiffness toward each particle destination. Default is 0.085.
  • particle.damping (number): Motion damping. Default is 0.78.
  • particle.shape (soft | dot | square): Particle rendering style. Default is soft.

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 is 11.
  • axis.ticks (number): Approximate y-axis tick count. Default is 5.
  • axis.grid (boolean): Shows y-axis grid lines. Default is true.
  • axis.xGrid (boolean): Shows x-axis grid lines. Default is false.
  • axis.xLabels (boolean): Shows x-axis labels. Default is true.
  • axis.yLabels (boolean): Shows y-axis labels. Default is true.
  • axis.beginAtZero (boolean): Includes zero in the value domain. Default is true.
  • 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 is true.
  • legend.markerSize (number): Legend marker size. Default is 8.
  • legend.fontSize (number): Legend text size. Default is 12.
  • 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 is smooth.
  • line.width (number): Particle stroke width. Default is 3.2.
  • line.area (boolean): Fills the region beneath the line. Default is false.
  • line.areaAmount (number): Particle share assigned to the area fill. Default is 0.55.
  • line.areaFade (number): Concentrates fill particles near the line. Default is 0.9.
  • line.points (boolean): Shows particle clusters at data points. Default is true.
  • line.pointRadius (number): Point-cluster radius. Default is 4.5.

Bar Options

  • bar.padding (number): Gap between categories. Default is 0.3.
  • bar.groupPadding (number): Gap between bars inside a group. Default is 0.16.
  • bar.stacked (boolean): Stacks multiple series. Default is false.
  • bar.horizontal (boolean): Draws horizontal bars. Default is false.
  • bar.fade (number): Thins particle density toward the growing end. Default is 0.45.
  • bar.radius (number): Rounded cap radius. Default is 4.

Bubble Options

  • bubble.minRadius (number): Smallest bubble radius. Default is 5.
  • bubble.maxRadius (number): Largest bubble radius. Default is 30.
  • bubble.edgeFade (number): Feathers the bubble rim. Default is 0.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 is false.

Radar Options

  • radar.levels (number): Number of web rings. Default is 4.
  • radar.shape (polygon | circle): Web-ring shape. Default is polygon.
  • radar.width (number): Particle outline width. Default is 2.6.
  • radar.fill (boolean): Fills the radar polygon. Default is true.
  • radar.fillAmount (number): Particle share assigned to the fill. Default is 0.55.
  • radar.fillFade (number): Thins the fill toward the center. Default is 0.55.
  • radar.points (boolean): Shows vertex particle clusters. Default is true.
  • radar.pointRadius (number): Vertex-cluster radius. Default is 4.
  • 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 to 0.
  • pie.startAngle (number): First slice angle in degrees. Default is -90.
  • pie.padAngle (number): Slice gap in degrees. Default is 1.2.
  • pie.radius (number): Chart radius relative to available plot space. Default is 0.95.
  • pie.edgeFade (number): Feathers the outer edge. Default is 0.25.
  • pie.labels (percent | value | label | none): Label content when values are shown. Default is percent.
  • pie.center (auto | total | none | string): Controls donut center content.

Flat Option Aliases

  • particleColorparticle.color.
  • particleSizeparticle.size.
  • particleDensityparticle.density.
  • particleCountparticle.max.
  • particleBloomparticle.bloom.
  • particleOpacityparticle.opacity.
  • particleJitterparticle.jitter.
  • particleSpeedparticle.speed.
  • particleShapeparticle.shape.
  • colorsparticle.color.
  • curveline.curve.
  • fillArealine.area.
  • lineWidthline.width.
  • showPointsline.points.
  • stackedbar.stacked.
  • horizontalbar.horizontal.
  • barPaddingbar.padding.
  • minRadiusbubble.minRadius.
  • maxRadiusbubble.maxRadius.
  • levelsradar.levels.
  • webShaperadar.shape.
  • innerRadiuspie.innerRadius.
  • startAnglepie.startAngle.
  • padAnglepie.padAngle.
  • legendPositionlegend.position.
  • legendAlignlegend.align.
  • minaxis.min.
  • maxaxis.max.
  • beginAtZeroaxis.beginAtZero.
  • valueFormataxis.format.
  • formataxis.format.
  • ticksaxis.ticks.
  • xTitleaxis.xTitle.
  • yTitleaxis.yTitle.
  • fontFamilyaxis.fontFamily.
  • textColoraxis.textColor.
  • axisColoraxis.color.
  • gridColoraxis.gridColor.
  • crosshairColoraxis.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

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.

You Might Be Interested In:


Leave a Reply