> ## Documentation Index
> Fetch the complete documentation index at: https://docs.graphy.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Scatter & bubble

export const GraphEmbed = ({src, story, aspectRatio, borderRadius = '14px', args}) => {
  const STORYBOOK_BASE_URL = typeof window !== 'undefined' && window.location.hostname === 'localhost' ? 'http://localhost:6006' : 'https://storybook-sdk.vercel.app';
  function buildArgsString(args) {
    if (!args) return '';
    const encodeValue = value => {
      if (value === null) return '!null';
      if (value === undefined) return '!undefined';
      if (typeof value === 'boolean') return `!${value}`;
      if (typeof value === 'number') return String(value);
      return encodeURIComponent(String(value));
    };
    const properties = Object.entries(args).map(([key, value]) => `${key}:${encodeValue(value)}`).join(';');
    return `&args=${properties}`;
  }
  const resolvedSrc = story ? `${STORYBOOK_BASE_URL}/iframe.html?id=${story}&viewMode=story&embed=1&globals=mode:readonly${buildArgsString(args)}` : src;
  const resolvedAspectRatio = aspectRatio ?? (story ? '16 / 9' : '10 / 6');
  return <iframe src={resolvedSrc} loading="lazy" allowfullscreen="true" style={{
    width: '100%',
    aspectRatio: resolvedAspectRatio,
    border: 'none',
    borderRadius,
    colorScheme: 'light'
  }} />;
};

A scatter plot draws a `geom.point()` per observation against two continuous axes — the tool for correlation and distribution. Add a `size` mapping and it becomes a bubble chart, encoding a third variable.

## Scatter

Map two numeric columns to `x` and `y`, and declare continuous scales:

<GraphEmbed story="chart-types-point-graph--simple" />

```tsx theme={null}
import { useMemo } from 'react';
import {
  GraphProvider,
  GraphRenderer,
  createSpec,
  pipe,
  geom,
  scale,
} from '@graphysdk/react';

const data = {
  columns: [
    { key: 'weight', label: 'Weight' },
    { key: 'height', label: 'Height' },
  ],
  rows: [
    { weight: 60, height: 160 },
    { weight: 65, height: 165 },
    { weight: 70, height: 175 },
    { weight: 75, height: 170 },
    { weight: 80, height: 180 },
    { weight: 85, height: 178 },
    { weight: 90, height: 185 },
  ],
};

export function WeightVsHeight() {
  const spec = useMemo(
    () =>
      pipe(
        createSpec({ x: 'weight', y: 'height' }),
        geom.point(),
        scale.x.continuous(),
        scale.y.continuous()
      ),
    []
  );

  return (
    <GraphProvider spec={spec} data={data}>
      <GraphRenderer />
    </GraphProvider>
  );
}
```

## Coloring by category

Map a category to `color` to distinguish groups of points:

<GraphEmbed story="chart-types-point-graph--color-grouped" />

```tsx theme={null}
const spec = pipe(
  createSpec({ x: 'weight', y: 'height', color: 'gender' }),
  geom.point(),
  scale.x.continuous(),
  scale.y.continuous(),
  scale.color.palette()
);
```

## Bubble

Map a third numeric column to `size` and add a size scale. The default size scale uses a square-root transform, so bubble **area** is proportional to the value:

<GraphEmbed story="chart-types-point-graph--size-and-color" />

```tsx theme={null}
const spec = pipe(
  createSpec({
    x: 'gdp',
    y: 'lifeExpectancy',
    size: 'population',
    color: 'continent',
  }),
  geom.point(),
  scale.x.continuous(),
  scale.y.continuous(),
  scale.size.continuous(),
  scale.color.palette()
);
```

Each mapped aesthetic needs its own scale: `size` alone would leave every bubble in the built-in mark color, so `scale.color.palette()` earns its place beside `scale.size.continuous()`.

## Missing values

`geom.point()` has no `missingValues` param — an observation whose `x` or `y` is `null` simply isn't drawn, and the rest of the cloud is unaffected.

## Painting the markers

`geom.point()` takes no params at all. Everything about a marker's appearance comes from the [stylesheet](/sdk-next/config/styling), through the `style.geom.point` target — `color`, `alpha`, `saturation`, `size`, `borderColor` and `borderWidth`:

```tsx theme={null}
import { createSpec, pipe, geom, scale, styles, style } from '@graphysdk/react';

const spec = pipe(
  createSpec({ x: 'weight', y: 'height' }),
  geom.point(),
  scale.x.continuous(),
  scale.y.continuous(),
  styles({ defaults: [style.geom.point({ size: 6, alpha: 0.5 })] })
);
```

Markers draw at `size: 8` with a 1px white border by default. Lowering `alpha` is the standard move for a dense cloud, where overlapping markers otherwise merge into a solid mass.

One cascade rule matters here. A `defaults` entry applies only where no mapped aesthetic decided the value, so on a bubble chart `style.geom.point({ size: 6 })` is ignored — the `size` mapping already spoke. Put the entry in `overrides` to win against the mapping:

```tsx theme={null}
styles({ overrides: [style.geom.point({ size: 6 })] });
```

## Related

* [Styling](/sdk-next/config/styling) — marker paint, size and the cascade
* [Scales](/sdk-next/concepts/scales) — continuous and size scales
* [Coordinate systems](/sdk-next/concepts/coordinate-systems) — `coord.flip()` swaps the two axes
* [Statistics](/sdk-next/advanced/statistics) — add a `smooth` trendline over the points


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.