Radar
Категории распределяются по кругу (angleField), значения — по радиусу (radiusField). radar-line — контур, radar-area — контур с заливкой. Сетка — полигональная («паутина»). При наведении маркер вершины плавно увеличивается, остальные серии приглушаются — анимация как у круговых.
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Product assessment' },
series: [
{ type: 'radar-area', angleField: 'skill', radiusField: 'target', name: 'Target' },
{
type: 'radar-line',
angleField: 'skill',
radiusField: 'current',
name: 'Current',
tooltip: {
renderer: ({ label, value, seriesName, color }) => ({
heading: label,
rows: [{ label: seriesName, value: `${value} / 10`, color }],
}),
},
},
],
};
}export function getData() {
return [
{ skill: 'Speed', current: 7, target: 9 },
{ skill: 'Quality', current: 8, target: 9 },
{ skill: 'Reliability', current: 6, target: 8 },
{ skill: 'Support', current: 5, target: 7 },
{ skill: 'Price', current: 8, target: 8 },
{ skill: 'Documentation', current: 4, target: 8 },
];
}Radar-area
Заливка профилей с прозрачностью — удобно для сравнения двух контуров:
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Radar-area: profile comparison' },
series: [
{ type: 'radar-area', angleField: 'metric', radiusField: 'team', name: 'Us', fillOpacity: 0.3 },
{ type: 'radar-area', angleField: 'metric', radiusField: 'market', name: 'Market', fillOpacity: 0.3 },
],
};
}export function getData() {
return [
{ metric: 'Speed', team: 8, market: 6 },
{ metric: 'Quality', team: 7, market: 8 },
{ metric: 'Price', team: 5, market: 7 },
{ metric: 'Support', team: 9, market: 5 },
{ metric: 'Features', team: 6, market: 8 },
{ metric: 'UX', team: 8, market: 6 },
];
}Длинные названия категорий
Размер паутины подбирается под подписи, а не наоборот: радиус уменьшается — а если тесно только с одной стороны, ещё и сдвигается центр — пока все названия категорий не окажутся внутри области графика. Ничего не обрезается по краю, при любых пропорциях контейнера.
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Long category names' },
subtitle: { text: 'the grid gives way to the labels instead of cutting them off' },
series: [{ type: 'radar-area', angleField: 'metric', radiusField: 'score', name: 'Score', fillOpacity: 0.3 }],
legend: { enabled: false },
};
}export function getData() {
return [
{ metric: 'Documentation', score: 8 },
{ metric: 'Integrations', score: 6 },
{ metric: 'Reliability', score: 9 },
{ metric: 'Onboarding', score: 5 },
{ metric: 'Performance', score: 7 },
{ metric: 'Support hours', score: 6 },
];
}Много категорий
Когда спицы встают плотно, подписи начинают налезать друг на друга — и те, что задели бы соседа, снимаются. Плотность по кругу неравномерна: у боков подписи стоят вплотную (там спицы ближе всего по вертикали), а сверху и снизу текст расходится в стороны. Поэтому вместо прореживания фиксированным шагом, который пришлось бы брать по самому тесному месту, каждая подпись остаётся, если ей хватает места: по бокам густо, сверху реже.
Спицы и кольца рисуются все: сетка остаётся целой, уступают только подписи. Значения на кольцах уступают названиям категорий — так они не накладываются друг на друга на 12 часах.
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Signups by week' },
subtitle: { text: 'every spoke is drawn, only the labels with room to spare' },
series: [{ type: 'radar-area', angleField: 'week', radiusField: 'signups', name: 'Signups', fillOpacity: 0.25 }],
legend: { enabled: false },
};
}/** Weekly signups over a year: a seasonal swell with a summer dip. */
export function getData() {
return Array.from({ length: 52 }, (_, index) => {
const week = index + 1;
const seasonal = 60 + 28 * Math.sin((index / 52) * Math.PI * 2 - 0.6);
const ripple = 6 * Math.sin(index / 3);
return { week: `W${String(week).padStart(2, '0')}`, signups: Math.round(seasonal + ripple) };
});
}Паутина и линии осей
Линии вокруг паутины и линии внутри неё делают разную работу — и настраиваются по-разному. Обод (axes.angle.line) и вертикаль, вдоль которой читаются значения (axes.radius.line), — это линии осей: сплошные, цветом оси, у каждой свои цвет, толщина и штрих. Спицы (axes.angle.gridLine) и кольца (axes.radius.gridLine) — фон: пунктиром и приглушённо, как сетка на картезианском графике, и по тому же токену темы. Кольцо, которое легло бы на обод, уступает ему: одна окружность — один штрих.
Шкала значений подписана от центра наружу, вместе с центром: там она и начинается, а паутина без числа в середине — паутина, от которой нечего отсчитать первое кольцо.
axes: {
angle: { line: { stroke: '#64748b', width: 1.5 }, gridLine: { opacity: 0.45 } },
radius: { max: 10, ringCount: 5, line: { stroke: '#94a3b8', width: 1.5 } },
},import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Cupping profile' },
subtitle: { text: 'the rim is an outline, the web behind it is chrome' },
series: [
{ type: 'radar-area', angleField: 'trait', radiusField: 'roast', name: 'Espresso roast', fillOpacity: 0.2 },
{ type: 'radar-line', angleField: 'trait', radiusField: 'filter', name: 'Filter roast' },
],
axes: {
angle: {
// the outline of the web: its own stroke, its own width, solid
line: { stroke: '#64748b', width: 1.5 },
// the spokes are grid: dashed by the theme, and here a shade fainter
gridLine: { opacity: 0.45 },
},
radius: {
max: 10,
ringCount: 5,
// the vertical the ring values are read along — an outline of its own
line: { stroke: '#94a3b8', width: 1.5 },
label: { fontWeight: 'bold' },
},
},
};
}export function getData() {
return [
{ trait: 'Aroma', roast: 8, filter: 6 },
{ trait: 'Acidity', roast: 4, filter: 9 },
{ trait: 'Body', roast: 9, filter: 5 },
{ trait: 'Sweetness', roast: 7, filter: 8 },
{ trait: 'Bitterness', roast: 8, filter: 3 },
{ trait: 'Aftertaste', roast: 6, filter: 7 },
];
}Опции
Общие опции всех серий (name, showInLegend, tooltip.renderer, …) — в разделе Общие опции серий.
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
angleField | string | — | ключи данных |
radiusField | string | — | ключи данных |
name | string | radiusField | имя серии |
stroke | ColorValue | палитра | контур |
strokeWidth | Pixels | 2 | контур |
fillOpacity (radar-area) | Fraction | 0.25 | прозрачность заливки |
tooltip.renderer | ({ datum, label, value, seriesName, color }) => … | — | кастомное содержимое тултипа |
marker.enabled | boolean | true | маркеры вершин |
marker.shape | MarkerShape | circle | форма |
marker.size | Pixels | 6 | размер |
Подписи значений
label.enabled печатает величину на каждой вершине. placement по умолчанию outward — подпись отодвигается от центра вдоль своей спицы, где паутина пуста, куда бы вершина ни склонилась; top, bottom, left, right и inside ведут себя так же, как у линии.
import { getData } from './data';
import type { ChartOptions } from 'grafit-charts';
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Value labels on the vertices' },
series: [
{ type: 'radar-area', angleField: 'skill', radiusField: 'target', name: 'Target' },
{
type: 'radar-line',
angleField: 'skill',
radiusField: 'current',
name: 'Current',
label: { enabled: true, fontWeight: 'bold' },
},
],
};
}export function getData() {
return [
{ skill: 'Speed', current: 7, target: 9 },
{ skill: 'Quality', current: 8, target: 9 },
{ skill: 'Reliability', current: 6, target: 8 },
{ skill: 'Support', current: 5, target: 7 },
{ skill: 'Price', current: 8, target: 8 },
{ skill: 'Documentation', current: 4, target: 8 },
];
}| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
label.enabled | boolean | false | показать подписи |
label.placement | 'outward' | 'top' | 'bottom' | 'left' | 'right' | 'inside' | 'outward' | положение подписи |
label.formatter | ({ value, datum }) => string | значение | содержимое подписи |
label.fontSize | Pixels | 11 | размер шрифта |
label.fontWeight | string | number | normal | насыщенность |
label.fontFamily | string | шрифт темы | гарнитура |
label.color | ColorValue | foreground; inside — автоконтраст | цвет текста |
Кольца, спицы и числа рядом с ними принадлежат осям, а не серии — см. Полярные оси.
У радара обычно по мере на серию, поэтому tooltip.mode: 'shared' читается здесь так же, как на линейном графике: спица под курсором отвечает сразу за все серии, каждая подсвечена на своей вершине, и ни одна не приглушена. См. Тултип.