Skip to content

Sankey и Chord ​

Потоковые серии: рёбра fromField → toField с весом sizeField.

Sankey ​

Узлы раскладываются по колонкам топологической глубины, толщина связей пропорциональна потоку.

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

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'User journey' },
    series: [{ type: 'sankey', fromField: 'from', toField: 'to', sizeField: 'value' }],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { from: 'Traffic', to: 'Organic', value: 620 },
    { from: 'Traffic', to: 'Ads', value: 380 },
    { from: 'Organic', to: 'Sign-up', value: 240 },
    { from: 'Ads', to: 'Sign-up', value: 190 },
    { from: 'Organic', to: 'Bounce', value: 380 },
    { from: 'Ads', to: 'Bounce', value: 190 },
    { from: 'Sign-up', to: 'Subscription', value: 160 },
    { from: 'Sign-up', to: 'Freemium', value: 270 },
  ];
}

Подписи и настройка узлов ​

Подпись узла — его имя и то, что через него течёт, нарисованные одним блоком: label.category — имя, label.value — число, у каждого свои шрифт, цвет и формат; ровно та же форма, что у подписи сектора круговой. Имя печатается само по себе, пока value.enabled не попросит и число; layout ставит половины в одну строку вместо двух, а сам label несёт шрифт, к которому обе половины откатываются. Рядом с ними — node.width / node.spacing и linkOpacity:

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

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Budget flow' },
    subtitle: { text: 'the name and the total of a node, each with its own font' },
    series: [
      {
        type: 'sankey',
        fromField: 'from',
        toField: 'to',
        sizeField: 'amount',
        node: { width: 14, spacing: 24 },
        linkOpacity: 0.5,
        label: {
          fontSize: 12,
          // the name and the number are one label, styled apart
          category: { fontWeight: 'bold' },
          value: { enabled: true, format: ',.0f', fontSize: 11, color: '#8892a4' },
        },
      },
    ],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { from: 'Salary', to: 'Budget', amount: 220 },
    { from: 'Freelance', to: 'Budget', amount: 60 },
    { from: 'Budget', to: 'Rent', amount: 90 },
    { from: 'Budget', to: 'Food', amount: 70 },
    { from: 'Budget', to: 'Transport', amount: 30 },
    { from: 'Budget', to: 'Savings', amount: 90 },
    { from: 'Savings', to: 'Investments', amount: 60 },
    { from: 'Savings', to: 'Emergency fund', amount: 30 },
  ];
}

Много узлов в колонке ​

Масштаб «значение → пиксели» задаёт та колонка, у которой раньше кончается место, а не самая тяжёлая: промежутки между узлами — фиксированная плата, и колонке из двенадцати узлов нужно сначала оплатить одиннадцать промежутков, а уж потом значения получат высоту. Если и одни промежутки не влезают, они сжимаются ниже node.spacing — каждому узлу остаётся хотя бы полоска, а колонка целиком остаётся внутри графика.

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

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Revenue by year and month' },
    subtitle: { text: 'a column of twelve nodes still fits the height' },
    series: [{ type: 'sankey', fromField: 'year', toField: 'month', sizeField: 'revenue', node: { spacing: 8 } }],
  };
}
ts
const YEARS = ['2019', '2020', '2021'];
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

/** Revenue per year split across the months it landed in. */
export function getData() {
  return YEARS.flatMap((year, yearIndex) =>
    MONTHS.map((month, monthIndex) => ({
      year,
      month,
      revenue: 40 + ((yearIndex * 7 + monthIndex * 5) % 55),
    })),
  );
}

Chord ​

Узлы по кругу, ленты — взаимные потоки.

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

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'Calls between services' },
    series: [{ type: 'chord', fromField: 'from', toField: 'to', sizeField: 'calls' }],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { from: 'API', to: 'Auth', calls: 320 },
    { from: 'API', to: 'Billing', calls: 180 },
    { from: 'Web', to: 'API', calls: 540 },
    { from: 'Mobile', to: 'API', calls: 410 },
    { from: 'Billing', to: 'Auth', calls: 90 },
    { from: 'Web', to: 'Auth', calls: 130 },
  ];
}

Отступы и подписи ​

nodeSpacing — зазор между дугами (px по внутреннему радиусу), linkOpacity — плотность лент. Блок подписи тот же, что у санкея: здесь его числовая половина читается как доля кольца (value.type: 'percent'), а не как сам поток:

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

export function createOptions(): ChartOptions {
  return {
    data: getData(),
    title: { text: 'User migration between platforms' },
    subtitle: { text: 'nodeSpacing: 28, dense ribbons, a share under every name' },
    series: [
      {
        type: 'chord',
        fromField: 'from',
        toField: 'to',
        sizeField: 'users',
        nodeSpacing: 28,
        linkOpacity: 0.55,
        label: {
          fontSize: 12,
          // the value half reads as a share of the ring, on a line of its own
          value: { enabled: true, type: 'percent', fontSize: 10, color: '#8892a4' },
        },
      },
    ],
    legend: { enabled: false },
  };
}
ts
export function getData() {
  return [
    { from: 'Web', to: 'iOS', users: 18 },
    { from: 'Web', to: 'Android', users: 22 },
    { from: 'iOS', to: 'Web', users: 9 },
    { from: 'Android', to: 'Web', users: 12 },
    { from: 'iOS', to: 'Android', users: 6 },
    { from: 'Android', to: 'iOS', users: 7 },
    { from: 'Web', to: 'Desktop', users: 10 },
    { from: 'Desktop', to: 'Web', users: 5 },
  ];
}

Опции ​

Общие опции всех серий (name, showInLegend, tooltip.renderer, …) — в разделе Общие опции серий.

ОпцияСерииОписание
fromFieldоберёбра графа потоков
toFieldоберёбра графа потоков
sizeFieldоберёбра графа потоков
fillsобецвета узлов по кругу палитры
nodeStylerобестиль узла по name (fill, label.color); в params ещё depth (sankey), total, share; связи берут цвет источника

Полный список опций ​

ОпцияТипПо умолчаниюОписание
linkOpacityобе0.35прозрачность лент потоков
nodeSpacingchord12зазор между дугами узлов, px
label.enabledbooleantrueподписи узлов
label.formatter({ name, total, share }) => string—вся подпись сразу; сильнее category/value
label.fontSizePixels11шрифт, к которому откатываются обе половины
label.fontWeightstring | numbernormalнасыщенность
label.fontFamilystringшрифт темыгарнитура
label.colorColorValueforegroundцвет
label.layout'stacked' | 'inline''stacked'половины в две строки или в одну
label.separatorstring' · 'между половинами подписи в одну строку
label.categoryenabled, шрифт, format, formatterвкл.имя узла
label.valueenabled, type, шрифт, format, formatterвыкл.то, что течёт через узел
label.value.type'value' | 'percent''value'сам поток или его доля от целого
label.minShareFraction0доля, начиная с которой узел стоит подписи
label.avoidOverlapbooleanfalseснимать подпись, которой не хватило места
node.widthPixels14ширина узла sankey
node.spacingPixels14вертикальный зазор узлов sankey

Целое, от которого считается доля, — то, среди чего стоит узел: своя колонка у санкея, всё кольцо у хорды. minShare считает от того же целого, поэтому 0.02 оставляет без подписей мелочь переполненной колонки.

Подсказка и имя величины ​

Узел — не строка данных, а имя и то, во что оно складывается, поэтому tooltip.renderer получает NodeTooltipRendererParams: { datum?, label, value, share, color }. datum — строка, из которой прочитан узел; узел потока сложен из нескольких строк, и своей у него нет.

js
tooltip: { renderer: ({ label, value, share }) => `${label}: ${value} (${Math.round(share * 100)}%)` },

Без рендерера строка подсказки называется по ключу поля, откуда взята величина, — это имя колонки, а не имя меры. name у серии говорит, как её называть:

js
series: [{ type: 'treemap', sizeField: 'revenue', name: 'Выручка' }],