---
title: Scatter + Bubble
description: Two-axis correlation and three-axis bubble visualization with realistic LTV/CAC data.
visibility: guest
draft: false
related:
  - /components/charts-chartjs
  - /components/charts-apex/scatter-bubble
---

# Scatter + Bubble

Scatter charts plot pairs of numeric values to expose correlation. Bubble charts add a third axis (radius) — used for "X vs Y vs Magnitude" visualizations like LTV vs CAC vs customer count, or query duration vs row count vs index hit rate.

## When to use

When you want to spot correlation or clusters in two-axis data. For ordered time-series prefer [line charts](./line.md); for categorical totals prefer [bar charts](./bar.md).

## Basic Example — LTV vs CAC by cohort

:::preview{title="Customer LTV vs CAC across cohorts (€)"}
<x-wirekit-chart
    type="scatter"
    :labels="[]"
    :datasets="[
        [
            'label' => 'Cohort sample',
            'data' => [
                ['x' => 80,  'y' => 240],
                ['x' => 120, 'y' => 480],
                ['x' => 95,  'y' => 360],
                ['x' => 140, 'y' => 510],
                ['x' => 200, 'y' => 720],
                ['x' => 165, 'y' => 580],
                ['x' => 230, 'y' => 880],
                ['x' => 110, 'y' => 320],
            ],
            'backgroundColor' => 'var(--color-wk-accent)',
        ],
    ]"
    aria-label="Customer LTV vs CAC scatter across cohorts"
/>
:::

## Bubble — query duration vs rows vs index hit rate

:::preview{title="Query plan analysis: duration (ms) × row count × index hit %"}
<x-wirekit-chart
    type="bubble"
    :labels="[]"
    :datasets="[
        [
            'label' => 'Sampled queries',
            'data' => [
                ['x' => 12,  'y' => 1450, 'r' => 8],
                ['x' => 8,   'y' => 320,  'r' => 4],
                ['x' => 240, 'y' => 89000, 'r' => 22],
                ['x' => 56,  'y' => 12000, 'r' => 12],
                ['x' => 98,  'y' => 24000, 'r' => 14],
            ],
        ],
    ]"
    aria-label="Query duration vs row count vs index hit rate"
/>
:::

The third dimension `r` is the bubble radius — typical convention: larger bubble = more important / more common / higher metric. WCAG 1.1.1 (Non-text content): every bubble chart should ship with a `<table class="sr-only">` companion describing each data point.

## Real-world recipe — Convert Eloquent rows into bubble data

```blade
@php
    $points = \App\Models\Query::recentSamples(50)
        ->map(fn ($q) => [
            'x' => $q->duration_ms,
            'y' => $q->rows_examined,
            'r' => max(2, min(30, (int) ($q->index_hit_pct / 4))),
        ])
        ->all();
@endphp

<x-wirekit-chart
    type="bubble"
    :datasets="[['label' => 'Query plan samples', 'data' => $points]]"
    height="320px"
/>
```

The `max(2, min(30, ...))` clamp keeps bubbles in a visually-readable range; very small bubbles disappear under the line stroke, very large bubbles overlap each other unreadably.

## See Also

- [Line charts](./line.md) for ordered time-series
- [ApexCharts scatter + bubble demos](../charts-apex/scatter-bubble.md) including 3D-effect bubble variants

## Design Tokens

See [Chart.js adapter — Design Tokens](/components/charts-chartjs#design-tokens) for the color, typography, and grid tokens shared across every Chart.js demo.
