Bump chart и slope chart

Общая идея ранговых визуализаций

Bump chart и slope chart относятся к классу визуализаций, предназначенных для отображения изменения рангов или значений между дискретными состояниями (временными точками, категориями, версиями данных). Их ключевая задача — показать не абсолютные величины, а динамику относительного положения объектов.

Обе диаграммы активно используются в аналитике соревнований, продуктовых метрик, рейтингов, образовательных данных, спортивной статистики и любых сценариях, где важна эволюция позиций.


Slope chart

Смысл и структура

Slope chart отображает изменение значения между двумя точками во времени или между двумя состояниями.

Базовая структура:

  • две оси X (левое и правое состояние)
  • набор точек для каждого объекта
  • линия, соединяющая значения одного объекта

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


Когда используется slope chart

  • сравнение «до / после»
  • анализ изменений KPI между двумя периодами
  • сравнение рейтингов между двумя версиями системы
  • оценка изменений в опросах

Подготовка данных

Формат данных обычно простой:

const data = [
  { name: "Product A", before: 30, after: 80 },
  { name: "Product B", before: 90, after: 60 },
  { name: "Product C", before: 50, after: 50 }
];

Построение шкал

const width = 600;
const height = 400;

const x = d3.scalePoint()
  .domain(["before", "after"])
  .range([50, width - 50]);

const y = d3.scaleLinear()
  .domain([0, 100])
  .range([height - 50, 50]);

Рисование линий

const svg = d3.select("svg")
  .attr("width", width)
  .attr("height", height);

svg.selectAll("line.slope")
  .data(data)
  .enter()
  .append("line")
  .attr("class", "slope")
  .attr("x1", d => x("before"))
  .attr("y1", d => y(d.before))
  .attr("x2", d => x("after"))
  .attr("y2", d => y(d.after))
  .attr("stroke", "steelblue")
  .attr("stroke-width", 2);

Добавление точек

svg.selectAll("circle.before")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", d => x("before"))
  .attr("cy", d => y(d.before))
  .attr("r", 4);

svg.selectAll("circle.after")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", d => x("after"))
  .attr("cy", d => y(d.after))
  .attr("r", 4);

Улучшение читаемости

Slope chart быстро становится перегруженным при большом количестве линий. Основные техники оптимизации:

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

Пример окраски по изменению:

.attr("stroke", d => d.after > d.before ? "green" : "red")

Ограничения slope chart

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

Bump chart

Смысл и структура

Bump chart показывает изменение рангов объектов во времени. В отличие от slope chart, здесь отображается не значение, а позиция в рейтинге.

Каждая линия представляет объект, а каждая точка — его ранг в конкретный момент.


Когда используется bump chart

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

Подготовка данных

Данные обычно представляют собой таблицу:

const data = [
  { name: "Team A", values: [1, 3, 2, 1] },
  { name: "Team B", values: [2, 1, 3, 2] },
  { name: "Team C", values: [3, 2, 1, 3] }
];

Каждое значение — это ранг в конкретный период.


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

Важно заранее убедиться, что ранги корректны:

const periods = [0, 1, 2, 3];

const x = d3.scalePoint()
  .domain(periods)
  .range([50, width - 50]);

const y = d3.scaleLinear()
  .domain([1, 3])
  .range([50, height - 50]);

Построение линии

Для bump chart используется генератор линий:

const line = d3.line()
  .x((d, i) => x(i))
  .y(d => y(d));

Отрисовка графика

svg.selectAll("path.bump")
  .data(data)
  .enter()
  .append("path")
  .attr("class", "bump")
  .attr("d", d => line(d.values))
  .attr("fill", "none")
  .attr("stroke", "black")
  .attr("stroke-width", 2);

Добавление точек ранга

data.forEach(d => {
  svg.selectAll(null)
    .data(d.values)
    .enter()
    .append("circle")
    .attr("cx", (v, i) => x(i))
    .attr("cy", v => y(v))
    .attr("r", 3);
});

Работа с ранжированием

Ключевой этап bump chart — вычисление рангов из исходных значений.

Пример:

function computeRanks(matrix) {
  return matrix.map((period) => {
    return period
      .map((d, i) => ({ index: i, value: d }))
      .sort((a, b) => b.value - a.value)
      .map((d, rank) => ({ index: d.index, rank: rank + 1 }))
      .sort((a, b) => a.index - b.index)
      .map(d => d.rank);
  });
}

Визуальная интерпретация

  • линия вверх → ухудшение позиции (если 1 — лучший ранг)
  • линия вниз → улучшение позиции
  • пересечения линий → смена лидера

Улучшение читаемости bump chart

Основные техники:

1. Толщина линий

  • лидеры выделяются более толстой линией

2. Цветовая кодировка

.attr("stroke", (d, i) => d.values[d.values.length - 1] === 1 ? "gold" : "gray")

3. Подписи на концах

svg.selectAll("text.label")
  .data(data)
  .enter()
  .append("text")
  .attr("x", width - 40)
  .attr("y", d => y(d.values[d.values.length - 1]))
  .text(d => d.name);

Частые ошибки при построении bump chart

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

Сравнение bump chart и slope chart

Структурные различия

  • slope chart работает с двумя точками
  • bump chart работает с множеством периодов

Семантические различия

  • slope chart показывает изменение значения
  • bump chart показывает изменение позиции

Область применения

Slope chart эффективен при:

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

Bump chart эффективен при:

  • анализе динамики рейтингов
  • отслеживании конкуренции во времени

Ограничения

Slope chart:

  • теряет смысл при более чем двух точках

Bump chart:

  • становится перегруженным при большом числе объектов
  • требует аккуратной обработки пересечений

Интерактивные расширения в D3.js

Подсветка линии при наведении

svg.selectAll("path.bump")
  .on("mouseover", function () {
    d3.selectAll("path.bump").attr("opacity", 0.2);
    d3.select(this).attr("opacity", 1);
  })
  .on("mouseout", function () {
    d3.selectAll("path.bump").attr("opacity", 1);
  });

Tooltip с данными

const tooltip = d3.select("body")
  .append("div")
  .style("position", "absolute")
  .style("visibility", "hidden");

svg.selectAll("circle")
  .on("mouseover", (event, d) => {
    tooltip.style("visibility", "visible")
      .text(`Rank: ${d}`);
  })
  .on("mousemove", (event) => {
    tooltip.style("top", event.pageY + "px")
      .style("left", event.pageX + "px");
  })
  .on("mouseout", () => tooltip.style("visibility", "hidden"));

Анимация переходов

svg.selectAll("path.bump")
  .transition()
  .duration(1000)
  .attrTween("d", function (d) {
    const interpolate = d3.line()
      .x((_, i) => x(i))
      .y((v, i) => y(v));
    return interpolate(d.values);
  });

Практическое применение в аналитике

Bump chart и slope chart часто используются вместе:

  • slope chart для быстрого сравнения «до/после»
  • bump chart для анализа эволюции структуры

В аналитических панелях D3.js эти графики дополняют друг друга, формируя связку: разовый эффект → динамика процесса