Skip to content

Scatter

Scatter series; both axes are numeric by default (without axes, number + number axes are created).

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

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Height and weight' },
    series: [{ type: 'scatter', xField: 'height', xName: 'Height', yField: 'weight', yName: 'Weight', name: 'People' }],
    axes: [
      { type: 'number', position: 'bottom', title: { text: 'Height, cm' }, nice: false },
      { type: 'number', position: 'left', title: { text: 'Weight, kg' } },
    ],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { height: 162, weight: 54 },
    { height: 168, weight: 61 },
    { height: 171, weight: 65 },
    { height: 175, weight: 71 },
    { height: 178, weight: 74 },
    { height: 180, weight: 82 },
    { height: 183, weight: 79 },
    { height: 186, weight: 88 },
    { height: 190, weight: 93 },
    { height: 167, weight: 58 },
    { height: 173, weight: 68 },
    { height: 181, weight: 77 },
  ];
}

Point labels

A point label is the name of the point and its value, drawn as one block — the same label a pie sector or a funnel stage gets. labelField is what gives a point a name; without it the label is the bare value, as it always was. layout puts the value behind a separator ('inline', the default) or on a line of its own, and each half carries its own font:

js
labelField: 'country',
label: { enabled: true, category: { fontWeight: 'bold' }, value: { type: 'percent' } },

The point name is a field value, so how it becomes text is a property of the series: labelName (format or formatter) spells it out for the tooltip heading and the label alike, and label.category overrides it where the label wants something shorter.

value.type: 'percent' reads the number as a share of the total — of the y values for a scatter, of sizeField for a bubble, which is what a bubble is actually a part of. label.formatter({ value, datum }) still speaks for the whole label when one text is all you want; it wins over category/value:

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

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Scatter with labels on the right' },
    series: [
      {
        type: 'scatter',
        xField: 'x',
        yField: 'y',
        name: 'Points',
        label: {
          enabled: true,
          placement: 'right',
          formatter: ({ value, datum }) => `(${datum.x}; ${value})`,
        },
      },
    ],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { x: 12, y: 30 },
    { x: 25, y: 48 },
    { x: 38, y: 22 },
    { x: 47, y: 61 },
    { x: 58, y: 41 },
    { x: 70, y: 72 },
    { x: 81, y: 55 },
  ];
}

Crowded points

label.minShare leaves the points below that share of the total unlabelled, and label.avoidOverlap drops the labels there is no room left for. A bubble hands out the room by size — the big bubbles keep their labels and the specks lose theirs; a scatter has no size to rank by, so there the earlier point wins:

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 },
  ];
}

Marker shapes

shape: circle, square, diamond, triangle, cross, plus:

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

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Marker shapes' },
    series: [
      { type: 'scatter', xField: 'x', yField: 'alpha', name: 'Group A', shape: 'circle' },
      { type: 'scatter', xField: 'x', yField: 'beta', name: 'Group B', shape: 'diamond' },
      { type: 'scatter', xField: 'x', yField: 'gamma', name: 'Group C', shape: 'triangle' },
    ],
  };
}
ts
export function getData() {
  const data: Array<Record<string, number>> = [];
  const groups = [
    { key: 'alpha', cx: 30, cy: 40 },
    { key: 'beta', cx: 60, cy: 70 },
    { key: 'gamma', cx: 75, cy: 30 },
  ];
  groups.forEach((g, gi) => {
    for (let i = 0; i < 14; i++) {
      data.push({
        x: g.cx + (((i * 13 + gi * 7) % 21) - 10),
        [g.key]: g.cy + (((i * 17 + gi * 11) % 19) - 9),
      });
    }
  });
  return data;
}

Options

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

OptionTypeDefaultDescription
xFieldstringnumeric data keys
yFieldstringnumeric data keys
labelFieldstringdata key of the point name (label and tooltip heading)
labelName.formatstringhow the point-name field becomes text: tooltip heading and label
labelName.formatter({ datum, value }) => stringthe same, when a format string cannot express it
xNamestringxFieldx value name in the tooltip
yNamestringyFieldy value name in the tooltip
shapeMarkerShape'circle'marker shape
sizePixels8marker size
fillColorValuepalettefill
fillOpacityFraction0.85fill
strokeColorValuebackgroundstroke
strokeWidthPixels1stroke
itemStyler(params) => styleper-point styles (fill/stroke/size) based on datum
label.enabledbooleanfalseshow value labels
label.placement'top' | 'bottom' | 'left' | 'right' | 'inside''top'label placement
label.formatter({ value, datum }) => stringthe whole label at once; wins over category/value
label.layout'inline' | 'stacked''inline'the value behind a separator or on its own line
label.separatorstring' · 'what separates the halves of an inline label
label.category.enabledbooleantrue when labelField is setthe point name as part of the label
label.category.formatstringformat string for the name field
label.category.formatter({ datum, label, value, share }) => …text of the name half
label.category.*FontOptionsthe label fontfont of the name
label.value.enabledbooleantruethe value as part of the label
label.value.type'value' | 'percent''value'the value itself or its share of the total
label.value.formatstringformat string (',.0f', '.1%')
label.value.formatter({ datum, label, value, share }) => …text of the value half
label.value.*FontOptionsthe label fontfont of the value
label.minShareFraction0share of the total a point needs before it is worth a label
label.avoidOverlapbooleanfalsedrop the labels there is no room for
label.fontSizePixels11label font size
label.fontWeightstring | numbernormalfont weight
label.fontFamilystringtheme fontfont family
label.colorColorValueforeground; inside — auto contrasttext color

itemStyler receives { datum, index, highlighted, fill, stroke, size } and returns partial styles — this is how you color points conditionally without separate series.

Tooltip

Both axes of a point series are measures, so the default tooltip lists the values as labelled rows: xName: x, yName: y. The heading names the point when labelField gives it a name, and the series (marker + name) otherwise. A bubble adds sizeName: size with its share of the total size — Population, M: 1412 (37%) — the way a pie sector reads its share.