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

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

D3 рассматривает данные как типизированные структуры JavaScript, но исходные источники почти всегда предоставляют строки: CSV, TSV, JSON с текстовыми полями, API-ответы с нестрогой типизацией. Поэтому ключевая задача загрузки — не просто чтение, а приведение типов и нормализация структуры.


Форматы загрузки и базовое поведение парсинга

В D3 используется модуль загрузки данных (d3-fetch), где основными функциями выступают:

  • d3.csv
  • d3.tsv
  • d3.json
  • d3.text
  • d3.xml

Каждая функция возвращает Promise, а обработка данных происходит в цепочке .then().

CSV как основной источник типизационных проблем

CSV-файлы изначально не содержат типов. Например:

year,price,count,date
2020,12.5,10,2020-01-01

После загрузки:

  • year → строка "2020"
  • price → строка "12.5"
  • count → строка "10"
  • date → строка "2020-01-01"

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


Автоматическое приведение типов: d3.autoType

D3 предоставляет встроенный механизм автоматического определения типов — d3.autoType.

Он применяется как функция преобразования строк в:

  • числа
  • даты ISO
  • булевы значения
  • null (для пустых значений)

Пример использования:

d3.csv("data.csv", d3.autoType).then(data => {
  console.log(data);
});

Поведение d3.autoType

Алгоритм анализа значений:

  • "42"42
  • "3.14"3.14
  • "true"true
  • "false"false
  • "2020-01-01"Date
  • ""null

Однако автоматизм имеет ограничения:

  • нестандартные даты не распознаются
  • числовые строки с форматированием ("1,000") не обрабатываются
  • локализованные форматы игнорируются
  • строки с единицами измерения остаются строками

Ручная типизация через row accessor

Наиболее надёжный способ контроля типов — функция преобразования строки при загрузке.

d3.csv("data.csv", function(row) {
  return {
    year: +row.year,
    price: parseFloat(row.price),
    count: parseInt(row.count, 10),
    date: new Date(row.date)
  };
}).then(data => {
  console.log(data);
});

Преимущества ручного преобразования

  • полный контроль над типами
  • возможность валидации
  • обработка ошибок на этапе загрузки
  • поддержка сложных форматов

Обработка числовых значений

Приведение через унарный плюс

value: +row.value

Особенности:

  • быстрее parseFloat
  • возвращает NaN при ошибке
  • не обрабатывает строки с мусорными символами

parseInt и parseFloat

parseInt("10px", 10) // 10
parseFloat("10.5kg") // 10.5

Проблема: частичное чтение строки может скрывать ошибки данных.


Обработка дат и временных данных

Стандартный Date()

new Date("2020-01-01")

Работает только с ISO-форматом и близкими к нему строками.


d3.timeParse для нестандартных форматов

Когда данные приходят в виде:

01/2020
2020-01

используется d3.timeParse:

const parseDate = d3.timeParse("%m/%Y");

d3.csv("data.csv", row => ({
  date: parseDate(row.date),
  value: +row.value
}));

Форматирование основано на спецификаторах:

  • %Y — год
  • %m — месяц
  • %d — день
  • %H — час
  • %M — минута

Локализация чисел и d3-format

В европейских форматах числа могут выглядеть так:

1.234,56

Стандартный JavaScript не интерпретирует такие значения корректно.

D3 решает проблему через локализованные парсеры:

const locale = d3.formatLocale({
  decimal: ",",
  thousands: ".",
  grouping: [3],
  currency: ["", ""]
});

const parseNumber = locale.numberParse(",");

Применение:

value: parseNumber(row.value)

null, undefined и пропущенные значения

D3 нормализует отсутствие данных, но поведение зависит от метода загрузки:

  • пустая строка → null (при autoType)
  • отсутствующее поле → undefined
  • "NA", "-" → остаются строками без кастомной обработки

Практический подход:

value: row.value === "NA" ? null : +row.value

Валидация данных на этапе загрузки

Типизация часто совмещается с проверкой:

d3.csv("data.csv", row => {
  const value = +row.value;

  if (!Number.isFinite(value)) {
    return null;
  }

  return {
    id: row.id,
    value
  };
}).then(data => {
  data = data.filter(d => d !== null);
});

Структурная нормализация данных

CSV и JSON API часто приходят в несовместимых формах.

Пример нормализации

Исходные данные:

{
  "id": "A1",
  "metrics": {
    "value": "10",
    "time": "2020-01-01"
  }
}

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

d3.json("data.json").then(data => {
  return data.map(d => ({
    id: d.id,
    value: +d.metrics.value,
    time: new Date(d.metrics.time)
  }));
});

Потоки преобразования: цепочка загрузки и подготовки

Типичный pipeline:

  1. загрузка (d3.csv, d3.json)
  2. парсинг строк
  3. приведение типов
  4. фильтрация
  5. агрегация

Пример:

d3.csv("data.csv", d3.autoType)
  .then(data => data.filter(d => d.value > 0))
  .then(data => d3.group(data, d => d.category))
  .then(grouped => {
    console.log(grouped);
  });

Ошибки автоматической типизации

1. Ложная интерпретация строк как даты

"2020-13-01" → invalid Date

Результат: Invalid Date без исключения.


2. Потеря точности при числах

Строки:

"00123"

Могут быть интерпретированы как число 123, что критично для идентификаторов.


3. Неявные NaN

+"" // 0
+"abc" // NaN

NaN легко распространяется по вычислениям и ломает масштабы.


Типизация идентификаторов

Идентификаторы часто ошибочно преобразуются в числа.

Правильная стратегия:

  • всегда оставлять id строкой
  • избегать autoType для ключевых полей
d3.csv("data.csv", row => ({
  id: row.id,
  value: +row.value
}));

Оптимизация на больших данных

При больших CSV-файлах важна минимизация операций:

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

Пример оптимизированного подхода:

d3.csv("large.csv", row => ({
  x: +row.x,
  y: +row.y
}));

Комбинирование парсинга и вычисляемых полей

Часто данные сразу дополняются производными значениями:

d3.csv("data.csv", row => {
  const value = +row.value;

  return {
    value,
    logValue: Math.log(value),
    category: value > 10 ? "high" : "low"
  };
});

Такой подход снижает нагрузку на этап визуализации и переносит вычисления в фазу подготовки данных.


Управление типами как часть архитектуры визуализации

Корректная типизация данных влияет на:

  • масштабирование (d3.scaleLinear, d3.scaleTime)
  • оси (d3.axisBottom)
  • геометрию (scatter plots, line charts)
  • агрегации (d3.rollup, d3.group)

Ошибки типов на входе приводят к каскадным сбоям визуальных компонентов, поскольку D3 не выполняет строгую типовую проверку на уровне графических примитивов.