Bubble Chart

What is a Bubble Chart?

ApexCharts bubble charts plot three variables simultaneously: an x position, a y position, and a z value that controls the rendered size of each bubble. This makes a bubble chart the natural choice when you need to communicate a third quantitative dimension on a two-axis plane, for example market share (x) plotted against year-over-year growth (y) with revenue encoded as bubble size (z). Without the z axis you would reach for a scatter chart instead.

When to Use a Bubble Chart

Use a bubble chart when all three of the following are true:

  • You have two numeric axes where position carries meaning (unlike categories, where a bar chart is usually clearer).
  • A third numeric quantity is associated with each point and that quantity is worth comparing across points.
  • The relative size difference between the smallest and largest z value is large enough to be readable as a visual difference in bubble diameter.

If your third dimension is categorical rather than numeric, encode it as color (multiple series) instead of bubble size. If you have no meaningful third variable, use a scatter chart, which renders uniform markers and avoids implying a size dimension that does not exist.

Data Format

Each data point in a bubble series requires three numbers: x, y, and z. ApexCharts accepts two equivalent syntaxes.

Nested array syntax

The shorthand is a nested array [x, y, z]:

series: [
  {
    name: 'Product A',
    data: [
      [16.4, 5.4, 20],
      [21.7, 2.0, 35],
      [25.4, 3.0, 15],
      [19.0, 7.8, 60],
      [10.9, 1.2, 10],
    ]
  }
]

Object syntax

The equivalent object form is { x, y, z }. Use this when you want to attach a fillColor or other per-point overrides:

series: [
  {
    name: 'Product A',
    data: [
      { x: 16.4, y: 5.4, z: 20 },
      { x: 21.7, y: 2.0, z: 35 },
      { x: 25.4, y: 3.0, z: 15 },
    ]
  }
]

The z value is not a pixel radius. ApexCharts scales it relative to the other z values in the chart (see the z-scaling section below).

Basic Bubble Chart

The only required setting beyond series is chart.type: 'bubble'. The example below renders two product lines on a shared plane:

const options = {
  chart: {
    type: 'bubble',
    height: 380,
  },
  series: [
    {
      name: 'Product A',
      data: [
        [16.4, 5.4, 20],
        [21.7, 2.0, 35],
        [25.4, 3.0, 15],
        [19.0, 7.8, 60],
        [10.9, 1.2, 10],
      ],
    },
    {
      name: 'Product B',
      data: [
        [36.4, 13.4, 55],
        [12.3, 5.2, 42],
        [44.2, 5.8, 18],
        [27.5, 11.3, 30],
        [50.1, 8.6, 70],
      ],
    },
  ],
  xaxis: {
    tickAmount: 8,
    labels: {
      formatter: (val) => parseFloat(val).toFixed(1),
    },
  },
  yaxis: {
    max: 20,
  },
};

const chart = new ApexCharts(document.querySelector('#chart'), options);
chart.render();

Each series appears in a distinct color from the chart palette, and the legend labels each series by name. The tooltip shows all three values for the hovered bubble by default.

Z-Scaling: minBubbleRadius and maxBubbleRadius

The z values you supply are relative, not absolute pixel sizes. ApexCharts finds the minimum and maximum z across all series, then maps that range onto a pixel radius range. The smallest z value renders at minBubbleRadius pixels; the largest renders at maxBubbleRadius pixels; everything in between scales linearly.

plotOptions: {
  bubble: {
    minBubbleRadius: 5,   // pixels, smallest bubble diameter / 2
    maxBubbleRadius: 50,  // pixels, largest bubble diameter / 2
  },
}

The default values let ApexCharts choose sensible radii automatically based on the chart dimensions. Set explicit values when:

  • Your smallest bubbles are becoming invisible because the z range is very wide. Raising minBubbleRadius gives every bubble a floor size.
  • Your largest bubbles are overlapping so aggressively that they obscure nearby points. Lowering maxBubbleRadius compresses the size range.

Disabling z-scaling

Set zScaling: false to render all bubbles at the same size, which converts the chart into a de-facto scatter chart. This is occasionally useful when z values span extreme ranges and the size difference would make small bubbles unreadable, though in that case you should usually encode z as color or tooltip text instead.

plotOptions: {
  bubble: {
    zScaling: false,
  },
}

Multi-Series Bubble Chart

Each entry in the series array becomes a separate group of bubbles rendered in its own color. Multi-series charts are useful for comparing two populations or product lines across the same axes. The legend allows toggling each series independently.

const options = {
  chart: {
    type: 'bubble',
    height: 380,
  },
  series: [
    {
      name: 'EMEA',
      data: [
        [52.8, 14.0, 25],
        [60.4, 18.3, 55],
        [71.2, 10.7, 15],
        [48.0, 22.6, 40],
      ],
    },
    {
      name: 'APAC',
      data: [
        [30.1, 8.5, 60],
        [45.7, 5.0, 20],
        [55.3, 12.1, 35],
        [62.0, 16.4, 45],
      ],
    },
    {
      name: 'Americas',
      data: [
        [20.5, 3.0, 10],
        [33.8, 9.2, 50],
        [41.1, 7.6, 30],
        [58.9, 20.0, 65],
      ],
    },
  ],
  xaxis: {
    title: { text: 'Market Share (%)' },
  },
  yaxis: {
    title: { text: 'YoY Growth (%)' },
  },
  plotOptions: {
    bubble: {
      minBubbleRadius: 6,
      maxBubbleRadius: 40,
    },
  },
};

const chart = new ApexCharts(document.querySelector('#chart'), options);
chart.render();

Bubble Chart with a Datetime X-Axis

When x values are timestamps, set xaxis.type to 'datetime'. ApexCharts formats the axis labels and tooltip automatically. The x values must be Unix timestamps in milliseconds (the value returned by Date.getTime() or Date.now()).

const options = {
  chart: {
    type: 'bubble',
    height: 380,
  },
  series: [
    {
      name: 'Revenue',
      data: [
        [new Date('2024-01-15').getTime(), 420, 30],
        [new Date('2024-02-20').getTime(), 530, 55],
        [new Date('2024-03-10').getTime(), 610, 20],
        [new Date('2024-04-05').getTime(), 480, 70],
        [new Date('2024-05-22').getTime(), 720, 45],
        [new Date('2024-06-18').getTime(), 810, 60],
      ],
    },
  ],
  xaxis: {
    type: 'datetime',
  },
  yaxis: {
    title: { text: 'Units Sold' },
  },
  plotOptions: {
    bubble: {
      minBubbleRadius: 8,
      maxBubbleRadius: 45,
    },
  },
  tooltip: {
    x: {
      format: 'MMM dd, yyyy',
    },
  },
};

const chart = new ApexCharts(document.querySelector('#chart'), options);
chart.render();

The z value encodes a third variable (here, deal size or campaign spend) while the x/y plane shows the time and sales volume relationship. Using tooltip.x.format gives readable dates in the hover tooltip rather than raw timestamps.

Customizing the Tooltip

The default tooltip shows the x, y, and z values. You can rename the z label or fully override the tooltip with a custom formatter:

tooltip: {
  z: {
    title: 'Revenue: $',
  },
}

For complete control, use tooltip.custom:

tooltip: {
  custom: ({ seriesIndex, dataPointIndex, w }) => {
    const point = w.config.series[seriesIndex].data[dataPointIndex];
    const [x, y, z] = point;
    return (
      '<div class="apexcharts-tooltip-box">' +
        '<span>Share: ' + x + '%</span>' +
        '<span>Growth: ' + y + '%</span>' +
        '<span>Revenue: $' + z + 'M</span>' +
      '</div>'
    );
  },
}

Bubble Chart vs Scatter Chart

BubbleScatter
Data dimensionsx, y, zx, y
Marker sizeProportional to zUniform
Use whenThree variables to showTwo variables to show

If all your z values are identical, a bubble chart degenerates into a scatter chart with equal-sized circles. Use a scatter chart directly in that case: it is simpler to configure and makes the intent clearer to readers.

Tree-Shaking

Bubble is a tree-shakeable chart type. When bundling with a modern bundler and using the modular entry points, import only the bubble renderer:

import ApexCharts from 'apexcharts/bubble'

This keeps the bubble renderer in your bundle while omitting chart types your app does not use. Note that apexcharts/line also registers bubble (along with line, area, scatter, and rangeArea), so if you already import the line entry point you do not need a separate bubble import.

Further Reading