Skip to content

Bubble

Scatter with a third dimension: sizeField controls the marker diameter within the size…maxSize range.

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'GDP vs Happiness Index' },
    subtitle: { text: 'bubble size — population, millions' },
    series: [
      {
        type: 'bubble',
        xField: 'gdp',
        xName: 'GDP per capita',
        yField: 'happiness',
        yName: 'Happiness',
        name: 'Countries',
        sizeField: 'population',
        sizeName: 'Population, M',
        maxSize: 36,
      },
    ],
    axes: [
      { type: 'number', position: 'bottom', title: { text: 'GDP per capita, $K' } },
      { type: 'number', position: 'left', title: { text: 'Happiness Index' }, nice: false },
    ],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { gdp: 4.2, happiness: 5.9, population: 144 },
    { gdp: 12.6, happiness: 6.1, population: 1412 },
    { gdp: 2.4, happiness: 4.3, population: 1408 },
    { gdp: 76.3, happiness: 6.9, population: 332 },
    { gdp: 48.7, happiness: 7.0, population: 84 },
    { gdp: 34.0, happiness: 6.5, population: 125 },
    { gdp: 51.0, happiness: 6.4, population: 52 },
    { gdp: 9.7, happiness: 6.3, population: 215 },
  ];
}

Labels inside bubbles

In addition to the line placements there is placement: 'inside' — text at the center of the bubble with an auto-contrast color and a halo in the marker's color:

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Bubble with Inside Labels' },
    subtitle: { text: "placement: 'inside' — auto contrast and bubble-colored halo" },
    series: [
      {
        type: 'bubble',
        xField: 'reach',
        xName: 'Reach',
        yField: 'engagement',
        yName: 'Engagement',
        sizeField: 'budget',
        sizeName: 'Budget',
        name: 'Channels',
        size: 22,
        maxSize: 58,
        fillOpacity: 0.8,
        label: {
          enabled: true,
          placement: 'inside',
          formatter: ({ datum }) => String(datum.channel),
        },
      },
    ],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { reach: 20, engagement: 65, budget: 45, channel: 'TV' },
    { reach: 45, engagement: 40, budget: 28, channel: 'Radio' },
    { reach: 60, engagement: 72, budget: 64, channel: 'Social' },
    { reach: 75, engagement: 30, budget: 18, channel: 'Print' },
    { reach: 85, engagement: 58, budget: 38, channel: 'Search' },
  ];
}

Which bubbles get a label

A bubble is a part of a whole — sizeField is its share of the total — so it takes the two options a pie sector does. label.minShare leaves the specks unlabelled, label.avoidOverlap hands out the room the big bubbles first, and label.value.type: 'percent' reads the size as that share. labelField gives the bubble a name to put beside it:

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'GDP vs Happiness Index' },
    subtitle: { text: 'labels go to the bubbles worth naming, biggest first' },
    series: [
      {
        type: 'bubble',
        xField: 'gdp',
        xName: 'GDP per capita',
        yField: 'happiness',
        yName: 'Happiness',
        labelField: 'country',
        name: 'Countries',
        sizeField: 'population',
        sizeName: 'Population, M',
        maxSize: 36,
        label: {
          enabled: true,
          minShare: 0.04,
          avoidOverlap: true,
          category: { fontWeight: 'bold' },
          value: { type: 'percent', color: '#8a8f98' },
        },
      },
    ],
    axes: [
      { type: 'number', position: 'bottom', title: { text: 'GDP per capita, $K' } },
      { type: 'number', position: 'left', title: { text: 'Happiness Index' } },
    ],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { country: 'Russia', gdp: 4.2, happiness: 5.9, population: 144 },
    { country: 'China', gdp: 12.6, happiness: 6.1, population: 1412 },
    { country: 'India', gdp: 2.4, happiness: 5.0, population: 1408 },
    { country: 'USA', gdp: 76.3, happiness: 6.9, population: 332 },
    { country: 'Germany', gdp: 48.7, happiness: 7.0, population: 84 },
    { country: 'Japan', gdp: 34.0, happiness: 6.5, population: 125 },
    { country: 'South Korea', gdp: 51.0, happiness: 6.4, population: 52 },
    { country: 'Brazil', gdp: 9.7, happiness: 6.3, population: 215 },
  ];
}

The label itself is the block described in Scatter: the name and the value, each with its own font.

Size range

size/maxSize set the diameters for the minimum and maximum sizeField values:

ts
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Bubble Size Range' },
    subtitle: { text: 'size 8 → maxSize 46, translucent fill' },
    series: [
      {
        type: 'bubble',
        xField: 'effort',
        xName: 'Effort',
        yField: 'impact',
        yName: 'Impact',
        sizeField: 'team',
        sizeName: 'Team',
        name: 'Initiatives',
        size: 8,
        maxSize: 46,
        fillOpacity: 0.55,
      },
    ],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { effort: 2, impact: 8, team: 6 },
    { effort: 4, impact: 6, team: 14 },
    { effort: 5, impact: 9, team: 22 },
    { effort: 7, impact: 4, team: 9 },
    { effort: 8, impact: 7, team: 30 },
    { effort: 3, impact: 3, team: 4 },
    { effort: 6, impact: 8, team: 18 },
    { effort: 9, impact: 9, team: 12 },
  ];
}

Options

Options common to all series (name, showInLegend, tooltip.renderer, …) are covered in Common series options.

All scatter options, plus:

OptionTypeDefaultDescription
sizeFieldstringsize value key (required)
sizeNamestringsizeFieldname in the tooltip; the row reads the size with its share of the total
labelFieldstringdata key of the bubble name
sizePixels8diameter for the minimum value
maxSizePixels28diameter for the maximum value
shapeMarkerShapecirclemarker shape
fillstylespalettefill
fillOpacitystyles0.85fill
strokestylesbackgroundmarker stroke
strokeWidthstyles1marker stroke
itemStyler(params) => MarkerItemStyleper-point styles
label.enabledbooleanfalseshow value labels
label.placement'top' | 'bottom' | 'left' | 'right' | 'inside''top'label position
label.formatter({ value, datum }) => stringthe whole label at once
label.minShareFraction0share of the total size a bubble needs before it is worth a label
label.avoidOverlapbooleanfalsedrop the labels there is no room for; the big bubbles ask first
label.fontSizePixels11label font size
label.fontWeightstring | numbernormalfont weight
label.fontFamilystringtheme fontfont family
label.colorColorValueforeground; auto-contrast when insidetext color