Scatter
Scatter series; both axes are numeric by default (without axes, number + number axes are created).
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 },
};
}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:
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:
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 },
};
}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:
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 },
};
}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:
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' },
],
};
}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.
| Option | Type | Default | Description |
|---|---|---|---|
xField | string | — | numeric data keys |
yField | string | — | numeric data keys |
labelField | string | — | data key of the point name (label and tooltip heading) |
labelName.format | string | — | how the point-name field becomes text: tooltip heading and label |
labelName.formatter | ({ datum, value }) => string | — | the same, when a format string cannot express it |
xName | string | xField | x value name in the tooltip |
yName | string | yField | y value name in the tooltip |
shape | MarkerShape | 'circle' | marker shape |
size | Pixels | 8 | marker size |
fill | ColorValue | palette | fill |
fillOpacity | Fraction | 0.85 | fill |
stroke | ColorValue | background | stroke |
strokeWidth | Pixels | 1 | stroke |
itemStyler | (params) => style | — | per-point styles (fill/stroke/size) based on datum |
label.enabled | boolean | false | show value labels |
label.placement | 'top' | 'bottom' | 'left' | 'right' | 'inside' | 'top' | label placement |
label.formatter | ({ value, datum }) => string | — | the whole label at once; wins over category/value |
label.layout | 'inline' | 'stacked' | 'inline' | the value behind a separator or on its own line |
label.separator | string | ' · ' | what separates the halves of an inline label |
label.category.enabled | boolean | true when labelField is set | the point name as part of the label |
label.category.format | string | — | format string for the name field |
label.category.formatter | ({ datum, label, value, share }) => … | — | text of the name half |
label.category.* | FontOptions | the label font | font of the name |
label.value.enabled | boolean | true | the value as part of the label |
label.value.type | 'value' | 'percent' | 'value' | the value itself or its share of the total |
label.value.format | string | — | format string (',.0f', '.1%') |
label.value.formatter | ({ datum, label, value, share }) => … | — | text of the value half |
label.value.* | FontOptions | the label font | font of the value |
label.minShare | Fraction | 0 | share of the total a point needs before it is worth a label |
label.avoidOverlap | boolean | false | drop the labels there is no room for |
label.fontSize | Pixels | 11 | label font size |
label.fontWeight | string | number | normal | font weight |
label.fontFamily | string | theme font | font family |
label.color | ColorValue | foreground; inside — auto contrast | text 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.