# Next.js Bubble Chart

A Next.js bubble chart for the App Router: an ApexCharts client component in AdminKit PRO that plots x, y and size per point, in light and dark mode.

> This component is only available in **AdminKit PRO**. [Learn more](/pricing/#nextjs).

A bubble chart compares items on three measures at once. Each bubble sits at its x and y values, and a third value sets its size. [AdminKit for Next.js](/nextjs/) draws it with `ApexChart`, a client component around [ApexCharts](https://apexcharts.com/) ([Chart setup](/nextjs/docs/charts/overview/#apexcharts)).

In the App Router, your page is a server component. It renders on the server and cannot pass a function to a client component. The chart's options are a function, so put the chart in a client component file, one that starts with `"use client"`, and render that from your page.

Until the browser has drawn the chart, the wrapper shows a skeleton. The static HTML of the page contains that skeleton too.

## Example

```html
<div id="apexcharts-bubble"></div>

<script>
function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function generateData(count) {
  var data = [];
  for (var i = 0; i < count; i++) {
    data.push([random(1, 750), random(10, 60), random(15, 75)]);
  }
  return data;
}

var options = {
  series: ["Bubble1", "Bubble2", "Bubble3", "Bubble4"].map(function (name) {
    return { name: name, data: generateData(20) };
  }),
  chart: {
    height: 350,
    type: "bubble",
  },
  dataLabels: {
    enabled: false
  },
  fill: {
    opacity: 0.8
  },
  title: {
    text: "Simple Bubble Chart"
  },
  xaxis: {
    tickAmount: 12,
    type: "category",
  },
  yaxis: {
    max: 70
  }
};

adminkit.apexcharts("#apexcharts-bubble", function () {
  return options;
});
</script>
```
```tsx code title="src/components/bubble-chart.tsx"
"use client";

import { ApexChart } from "@/components/plugins/apexcharts";

const random = (min: number, max: number) =>
  Math.floor(Math.random() * (max - min + 1)) + min;

// [x, y, size], generated once when the module loads
const series = ["Bubble1", "Bubble2", "Bubble3", "Bubble4"].map((name) => ({
  name,
  data: Array.from({ length: 20 }, () => [
    random(1, 750),
    random(10, 60),
    random(15, 75),
  ]),
}));

export function BubbleChart() {
  return (
    <div className="position-relative" style={{ minHeight: 365 }}>
      <ApexChart
        options={() => ({
          series,
          chart: { height: 350, type: "bubble" },
          dataLabels: { enabled: false },
          fill: { opacity: 0.8 },
          title: { text: "Simple Bubble Chart" },
          xaxis: { tickAmount: 12, type: "category" },
          yaxis: { max: 70 },
        })}
      />
    </div>
  );
}
```

```tsx title="src/app/(dashboard)/customers/page.tsx"
import { BubbleChart } from "@/components/bubble-chart";
import { pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/customers");

export default function CustomersPage() {
  return (
    <div className="card">
      <div className="card-body">
        <BubbleChart />
      </div>
    </div>
  );
}
```

`pageMetadata()` takes the title and description of `/customers` from `src/data/pages.json`, so add the route there.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `(theme: Theme) => ApexOptions` | | The ApexCharts options, with `series` and `chart.type`. Called again with the new colors when the color mode changes |

## Random data and hydration

The module generates the random data once, outside `options`. The wrapper calls `options` again when the color mode changes, and data made inside it would move every bubble.

The server render and the browser get different random numbers. That still causes no hydration mismatch, where the server HTML differs from what the browser renders when it takes over the page. The server only renders the skeleton, and the browser draws the chart.

Real data does not have this concern. Pass it from the page as props, as arrays of numbers, or fetch it in the client component.

## Data

Each point is `[x, y, z]`, and `z` sets the bubble size relative to the other bubbles. The series take the primary, success, warning and danger colors of the wrapper. `fill.opacity: 0.8` lets overlapping bubbles show through.

## Accessibility

- The values only show in tooltips on hover, and bubble sizes are hard to compare. Give the figures that matter in a table too.
- Name the chart: other props of `ApexChart` go to its `<div>`, such as `role="figure"` and `aria-label`.
- The legend lists the series names next to their colors.

## Related

- [React bubble chart](/react/docs/charts/bubble-chart/): the same chart in AdminKit for React (Vite)
- [Bootstrap 5 bubble chart](/bootstrap/docs/charts/bubble-chart/): the ApexCharts example of the HTML version
- [Chart setup](/nextjs/docs/charts/overview/): the `ApexChart`, `ChartJs` and `VectorMap` client components
- [Next.js scatter chart](/nextjs/docs/charts/scatter-chart/): points on two axes, without the size
- [ApexCharts demo](https://nextjs-demo.adminkit.io/charts-apexcharts): the line, area, bar, column, pie, heatmap, mixed and candlestick charts of the demo

---

Canonical HTML page: https://adminkit.io/nextjs/docs/charts/bubble-chart/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
