Пузырьковая диаграмма в контексте визуализации данных представляет собой способ отображения многомерной информации через окружности, где каждая окружность (пузырёк) кодирует значения с помощью координат, размера и, при необходимости, цвета. В экосистеме D3.js данный тип визуализации реализуется через комбинацию масштабов, геометрических примитивов и алгоритмов упаковки или симуляции.
Пузырьковая диаграмма опирается на три ключевых измерения:
Дополнительно может использоваться четвёртое измерение — цвет или прозрачность.
Формально радиус часто вычисляется через функцию масштабирования площади:
[ r = ]
В практической реализации используется нелинейный масштаб, чаще всего
scaleSqrt, чтобы визуальное восприятие соответствовало
площади, а не линейному значению.
Структура данных для пузырьковой диаграммы обычно представляет собой массив объектов:
const data = [
{ name: "A", value: 120 },
{ name: "B", value: 60 },
{ name: "C", value: 200 },
{ name: "D", value: 90 }
];
Каждый объект содержит числовое значение, определяющее размер пузырька, и идентификатор или подпись.
В D3.js визуализация строится поверх SVG-элемента:
const width = 800;
const height = 600;
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
SVG выступает базовым холстом, на котором размещаются графические примитивы.
Для корректного отображения значений применяется масштаб:
const radiusScale = d3.scaleSqrt()
.domain([0, d3.max(data, d => d.value)])
.range([0, 50]);
Использование scaleSqrt обеспечивает пропорциональность
площадей кругов исходным данным, что критично для корректной визуальной
интерпретации.
Начальный вариант без алгоритмов упаковки:
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", (d, i) => i * 120 + 100)
.attr("cy", height / 2)
.attr("r", d => radiusScale(d.value))
.attr("fill", "steelblue")
.attr("opacity", 0.7);
В этом случае координаты задаются вручную, а радиус определяется значением данных.
Текстовые элементы синхронизируются с кругами:
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", (d, i) => i * 120 + 100)
.attr("y", height / 2)
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(d => d.name)
.style("fill", "white");
Текст размещается по центру пузырьков с использованием
text-anchor.
Для плотной упаковки окружностей применяется алгоритм
d3.pack, предназначенный для иерархических данных.
Структура данных преобразуется в иерархию:
const root = d3.hierarchy({ children: data })
.sum(d => d.value);
const pack = d3.pack()
.size([width, height])
.padding(5);
const nodes = pack(root).leaves();
Каждый узел получает координаты x, y и
радиус r.
svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.r)
.attr("fill", "tomato")
.attr("opacity", 0.75);
Этот метод обеспечивает автоматическое размещение без пересечений.
Добавление цветового измерения позволяет расширить информативность:
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(d3.schemeTableau10);
Применение цвета:
.attr("fill", d => color(d.data.name))
svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(d => d.data.name)
.style("font-size", "10px")
.style("fill", "white");
Для динамических пузырьков применяется физическая симуляция:
const simulation = d3.forceSimulation(data)
.force("x", d3.forceX(width / 2))
.force("y", d3.forceY(height / 2))
.force("collide", d3.forceCollide(d => radiusScale(d.value) + 2));
simulation.on("tick", () => {
svg.selectAll("circle")
.data(data)
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
Force layout обеспечивает естественное распределение объектов без наложений.
Для адаптивных диаграмм используется пересчёт диапазонов:
const widthScale = d3.scaleLinear()
.domain([0, data.length])
.range([50, width - 50]);
Также применяется динамическое обновление радиусов при изменении данных.
Пузырьковая диаграмма может отражать иерархию через вложенные
окружности. В этом случае применяется структура hierarchy,
где каждый уровень влияет на группировку и позиционирование.
При увеличении количества элементов возникают ограничения:
Для оптимизации используется предварительная агрегация и кластеризация данных до визуализации.
Интерактивные элементы добавляются через обработчики событий:
.on("mouseover", function(event, d) {
d3.select(this)
.attr("stroke", "black")
.attr("stroke-width", 2);
})
.on("mouseout", function() {
d3.select(this)
.attr("stroke", null);
});
Интерактивность усиливает восприятие многомерных данных через визуальную обратную связь.