Форматы данных для Waffle

Waffle chart в Nivo опирается на дискретное распределение значений, которое преобразуется в сетку одинаковых ячеек. Каждая ячейка представляет фиксированную долю от общего значения, а входные данные задают относительные веса категорий.

Ключевая особенность формата заключается в том, что исходные данные не требуют нормализации: библиотека самостоятельно вычисляет пропорции на основе суммы значений.

Основной формат представляет собой массив объектов:

const data = [
  { id: 'A', label: 'Category A', value: 40 },
  { id: 'B', label: 'Category B', value: 25 },
  { id: 'C', label: 'Category C', value: 15 },
  { id: 'D', label: 'Category D', value: 20 }
]

Обязательные поля

id

  • Уникальный идентификатор категории
  • Используется для внутренней обработки и связывания цветов

value

  • Числовое значение, определяющее долю категории
  • Может быть целым или дробным числом
  • Отрицательные значения игнорируются или приводят к некорректному распределению

Рекомендуемые поля

label

  • Человекочитаемое название категории
  • Используется в легенде и подсказках

Принцип преобразования данных в сетку

Waffle chart преобразует одномерный массив значений в двумерную матрицу фиксированного размера.

Параметры:

  • total — общее количество “ячеек” в сетке
  • rows — количество строк
  • columns — количество столбцов

Пример конфигурации:

const total = 100
const rows = 10
const columns = 10

Каждая категория получает количество ячеек пропорционально своему значению.

Формула распределения:

  • сумма всех value = S
  • доля категории = value / S
  • количество ячеек = доля × total

Нормализация и округление значений

При преобразовании значений в ячейки возникает необходимость округления.

Пример проблемы округления

const data = [
  { id: 'A', value: 33 },
  { id: 'B', value: 33 },
  { id: 'C', value: 34 }
]

При total = 100 каждая категория должна получить 33.33 / 33.33 / 33.34 ячеек, что невозможно без округления.

Используемые стратегии

  1. floor-распределение

    • все значения округляются вниз
    • оставшиеся ячейки распределяются по наибольшим остаткам
  2. proportional fill

    • сначала вычисляется точное распределение
    • затем корректируется до совпадения с total

Расширенный формат данных

Помимо базового массива объектов, Waffle поддерживает расширенные структуры с дополнительными параметрами визуализации.

const data = [
  {
    id: 'react',
    label: 'React',
    value: 50,
    color: '#61dafb'
  },
  {
    id: 'vue',
    label: 'Vue',
    value: 30,
    color: '#42b883'
  },
  {
    id: 'svelte',
    label: 'Svelte',
    value: 20,
    color: '#ff3e00'
  }
]

Дополнительные поля

color

  • фиксирует цвет категории
  • имеет приоритет над цветовой схемой theme

data

  • вложенные метаданные
  • используется для tooltip и кастомных рендеров
{
  id: 'A',
  value: 40,
  data: {
    description: 'Frontend usage',
    region: 'Europe'
  }
}

Использование кастомных цветов

Если поле color не задано, используется цветовая схема Nivo.

Варианты поведения:

  • scheme-based coloring (например, nivo palette)
  • sequential coloring
  • categorical mapping по id

Пример:

<ResponsiveWaffle
  data={data}
  colors={{ scheme: 'set3' }}
/>

При наличии color в данных:

{
  id: 'A',
  value: 40,
  color: 'hsl(210, 70%, 50%)'
}

цветовая схема игнорируется для данного элемента.


Формат данных для stacked Waffle

В некоторых конфигурациях используется концепция слоёв (layers), где каждая категория может иметь подкатегории.

const data = [
  {
    id: 'frontend',
    value: 70,
    sub: [
      { id: 'react', value: 40 },
      { id: 'vue', value: 30 }
    ]
  },
  {
    id: 'backend',
    value: 30,
    sub: [
      { id: 'node', value: 20 },
      { id: 'go', value: 10 }
    ]
  }
]

В таком формате:

  • верхний уровень определяет крупные сегменты
  • вложенный уровень используется для детализации
  • визуализация может переключаться между уровнями агрегации

Поддержка динамических данных

Waffle chart чувствителен к изменениям входного массива.

Изменение структуры данных может происходить:

  • при обновлении состояния приложения
  • при фильтрации категорий
  • при изменении временного диапазона

Пример динамического обновления:

const [data, setData] = useState(initialData)

useEffect(() => {
  const updated = initialData.map(d => ({
    ...d,
    value: d.value * Math.random()
  }))
  setData(updated)
}, [])

При изменении данных происходит полная перерасчёт сетки.


Особенности работы с нулевыми и пустыми значениями

value = 0

Категория исключается из визуального распределения, но может сохраняться в легенде при соответствующей настройке.

{ id: 'A', value: 0 }

отсутствующие значения

{ id: 'B' }
  • трактуется как некорректный элемент
  • может быть заменён значением 0 или выброшен из массива

Влияние total на интерпретацию данных

Параметр total не изменяет входные данные, но влияет на плотность визуализации.

Пример:

total = 100
  • более детализированная сетка
total = 25
  • крупные блоки, грубая детализация

При этом распределение пропорций остаётся неизменным.


Совместимость форматов данных с легендой и tooltip

Legend и tooltip используют те же поля id, label, value.

Пример структуры tooltip:

{
  id: 'react',
  label: 'React',
  value: 50,
  formattedValue: '50%'
}

Если label отсутствует, используется id.


Преобразование внешних данных в формат Waffle

Часто данные поступают из API в несогласованной форме:

[
  { "name": "A", "count": 10 },
  { "name": "B", "count": 20 }
]

Преобразование:

const formatted = apiData.map(item => ({
  id: item.name,
  label: item.name,
  value: item.count
}))

Типичные ошибки в структуре данных

Несоответствие типов

{ id: 'A', value: '40' } // строка вместо числа

Последствия:

  • некорректное распределение
  • ошибки округления

Дублирующиеся id

{ id: 'A', value: 10 }
{ id: 'A', value: 20 }

Последствия:

  • перезапись значений
  • конфликт цветов

Отрицательные значения

{ id: 'A', value: -10 }
  • игнорируются или приводят к некорректной нормализации

Использование функций генерации данных

Для тестирования часто применяются генераторы:

const generateData = (n) =>
  Array.from({ length: n }).map((_, i) => ({
    id: `cat-${i}`,
    label: `Category ${i}`,
    value: Math.floor(Math.random() * 100)
  }))

Такие данные позволяют оценивать поведение распределения при изменяющихся значениях.


Подготовка данных для больших наборов категорий

При большом количестве категорий (>20) возникает проблема читаемости.

Рекомендуемые подходы:

  • агрегация малых значений в “Others”
  • сортировка по value перед передачей
  • фильтрация нулевых категорий
const processed = data
  .filter(d => d.value > 0)
  .sort((a, b) => b.value - a.value)

Связь формата данных с интерактивностью

Формат данных напрямую влияет на:

  • hover-обработчики
  • кликабельность ячеек
  • выделение категорий

Каждая ячейка наследует:

  • id категории
  • value категории
  • цвет категории

что позволяет строить интерактивную модель поверх статической сетки.