Skip to content

Radar

Категории распределяются по кругу (angleField), значения — по радиусу (radiusField). radar-line — контур, radar-area — контур с заливкой. Сетка — полигональная («паутина»). При наведении маркер вершины плавно увеличивается, остальные серии приглушаются — анимация как у круговых.

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

Заливка профилей с прозрачностью — удобно для сравнения двух контуров:

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

Длинные названия категорий

Размер паутины подбирается под подписи, а не наоборот: радиус уменьшается — а если тесно только с одной стороны, ещё и сдвигается центр — пока все названия категорий не окажутся внутри области графика. Ничего не обрезается по краю, при любых пропорциях контейнера.

ts
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 },
  };
}
ts
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 часах.

ts
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 },
  };
}
ts
/** 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) — фон: пунктиром и приглушённо, как сетка на картезианском графике, и по тому же токену темы. Кольцо, которое легло бы на обод, уступает ему: одна окружность — один штрих.

Шкала значений подписана от центра наружу, вместе с центром: там она и начинается, а паутина без числа в середине — паутина, от которой нечего отсчитать первое кольцо.

js
axes: {
  angle: { line: { stroke: '#64748b', width: 1.5 }, gridLine: { opacity: 0.45 } },
  radius: { max: 10, ringCount: 5, line: { stroke: '#94a3b8', width: 1.5 } },
},
ts
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' },
      },
    },
  };
}
ts
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, …) — в разделе Общие опции серий.

ОпцияТипПо умолчаниюОписание
angleFieldstringключи данных
radiusFieldstringключи данных
namestringradiusFieldимя серии
strokeColorValueпалитраконтур
strokeWidthPixels2контур
fillOpacity (radar-area)Fraction0.25прозрачность заливки
tooltip.renderer({ datum, label, value, seriesName, color }) => …кастомное содержимое тултипа
marker.enabledbooleantrueмаркеры вершин
marker.shapeMarkerShapecircleформа
marker.sizePixels6размер

Подписи значений

label.enabled печатает величину на каждой вершине. placement по умолчанию outward — подпись отодвигается от центра вдоль своей спицы, где паутина пуста, куда бы вершина ни склонилась; top, bottom, left, right и inside ведут себя так же, как у линии.

ts
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' },
      },
    ],
  };
}
ts
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.enabledbooleanfalseпоказать подписи
label.placement'outward' | 'top' | 'bottom' | 'left' | 'right' | 'inside''outward'положение подписи
label.formatter({ value, datum }) => stringзначениесодержимое подписи
label.fontSizePixels11размер шрифта
label.fontWeightstring | numbernormalнасыщенность
label.fontFamilystringшрифт темыгарнитура
label.colorColorValueforeground; inside — автоконтрастцвет текста

Кольца, спицы и числа рядом с ними принадлежат осям, а не серии — см. Полярные оси.

У радара обычно по мере на серию, поэтому tooltip.mode: 'shared' читается здесь так же, как на линейном графике: спица под курсором отвечает сразу за все серии, каждая подсвечена на своей вершине, и ни одна не приглушена. См. Тултип.