Bump chart и slope chart относятся к классу визуализаций, предназначенных для отображения изменения рангов или значений между дискретными состояниями (временными точками, категориями, версиями данных). Их ключевая задача — показать не абсолютные величины, а динамику относительного положения объектов.
Обе диаграммы активно используются в аналитике соревнований, продуктовых метрик, рейтингов, образовательных данных, спортивной статистики и любых сценариях, где важна эволюция позиций.
Slope chart отображает изменение значения между двумя точками во времени или между двумя состояниями.
Базовая структура:
Ключевая особенность — визуализация наклона линии, который отражает рост или падение значения.
Формат данных обычно простой:
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")
Bump chart показывает изменение рангов объектов во времени. В отличие от slope 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. Толщина линий
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);
Slope chart эффективен при:
Bump chart эффективен при:
Slope chart:
Bump chart:
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);
});
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 часто используются вместе:
В аналитических панелях D3.js эти графики дополняют друг друга, формируя связку: разовый эффект → динамика процесса