by chrisvoncsefalvay (community) Claude, d3.js, JavaScript, React, Vue, Svelte
Claude skill для работы с d3.js — визуализация данных.
Фреймворк агентных навыков и методология разработки ПО для Claude Code — набор SKILL.md-инструкций, охватывающих полный цикл …
Конвертирует сайты документации, GitHub-репозитории и PDF в skills для Claude AI с автоматическим обнаружением конфликтов.
45+ советов по эффективному использованию Claude Code — от основ до продвинутых техник, включая кастомный статус-лайн …
Даёт Claude Code возможность напрямую общаться с вашими блокнотами Google NotebookLM: запросы к загруженным документам, ответы …
Инструкция по установке не найдена в README проекта — возможно, она описана только во внешней документации. Ссылки на репозиторий смотрите на вкладке «About».
name: d3-viz description: Создание интерактивных визуализаций данных с использованием d3.js. Используйте этот skill при создании кастомных графиков, диаграмм, сетевых схем, географических визуализаций или любой сложной SVG-визуализации данных, требующей тонкого контроля над визуальными элементами, переходами или взаимодействиями. Используйте для нестандартных визуализаций сверх стандартных библиотек чартинга — на React, Vue, Svelte, чистом JavaScript или в любой другой среде.
Этот skill предоставляет руководство по созданию сложных интерактивных визуализаций данных с использованием d3.js. D3.js (Data-Driven Documents) отлично справляется с привязкой данных к DOM-элементам и применением управляемых данными трансформаций для создания кастомных визуализаций публикационного качества с точным контролем над каждым визуальным элементом. Техники работают в любой JavaScript-среде, включая чистый JavaScript, React, Vue, Svelte и другие фреймворки.
Используйте d3.js для: - Кастомных визуализаций, требующих уникальных визуальных кодировок или layout'ов - Интерактивных исследований со сложным поведением pan, zoom или brush - Сетевых/графовых визуализаций (force-directed layout'ы, древовидные диаграммы, иерархии, chord-диаграммы) - Географических визуализаций с кастомными проекциями - Визуализаций, требующих плавных, хореографированных переходов - Графики публикационного качества с тонким контролем стилизации - Новых типов чартов, недоступных в стандартных библиотеках
Рассмотрите альтернативы для: - 3D-визуализаций — используйте вместо этого Three.js
Импортируйте d3 в начале скрипта:
import * as d3 from 'd3';
Или используйте версию через CDN (7.x):
<script src="https://d3js.org/d3.v7.min.js"></script>
Все модули (шкалы, оси, формы, переходы и т.д.) доступны через namespace d3.
Паттерн A: Прямая манипуляция DOM (рекомендуется в большинстве случаев) Используйте d3, чтобы выбирать DOM-элементы и манипулировать ими императивно. Работает в любой JavaScript-среде:
function drawChart(data) {
if (!data || data.length === 0) return;
const svg = d3.select('#chart'); // Выбор по ID, классу или DOM-элементу
// Очистить предыдущее содержимое
svg.selectAll("*").remove();
// Задать размеры
const width = 800;
const height = 400;
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
// Создать шкалы, оси и отрисовать визуализацию
// ... код d3 здесь ...
}
// Вызывать при изменении данных
drawChart(myData);
Паттерн B: Декларативный рендеринг (для фреймворков с шаблонизацией) Используйте d3 для вычислений данных (шкалы, layout'ы), но рендерите элементы через ваш фреймворк:
function getChartElements(data) {
const xScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([0, 400]);
return data.map((d, i) => ({
x: 50,
y: i * 30,
width: xScale(d.value),
height: 25
}));
}
// В React: {getChartElements(data).map((d, i) => <rect key={i} {...d} fill="steelblue" />)}
// В Vue: директива v-for над возвращённым массивом
// В чистом JS: создавайте элементы вручную из возвращённых данных
Используйте Паттерн A для сложных визуализаций с переходами, взаимодействиями или когда используете d3 по полной. Используйте Паттерн B для более простых визуализаций или когда ваш фреймворк предпочитает декларативный рендеринг.
Следуйте этой стандартной структуре в функции отрисовки:
function drawVisualization(data) {
if (!data || data.length === 0) return;
const svg = d3.select('#chart'); // Или передайте селектор/элемент
svg.selectAll("*").remove(); // Очистить предыдущий рендер
// 1. Определить размеры
const width = 800;
const height = 400;
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
// 2. Создать основную группу с отступами
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// 3. Создать шкалы
const xScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.x)])
.range([0, innerWidth]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.y)])
.range([innerHeight, 0]); // Примечание: инвертировано для SVG-координат
// 4. Создать и добавить оси
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
g.append("g")
.attr("transform", `translate(0,${innerHeight})`)
.call(xAxis);
g.append("g")
.call(yAxis);
// 5. Привязать данные и создать визуальные элементы
g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", 5)
.attr("fill", "steelblue");
}
// Вызывать при изменении данных
drawVisualization(myData);
Сделайте визуализации адаптивными к размеру контейнера:
function setupResponsiveChart(containerId, data) {
const container = document.getElementById(containerId);
const svg = d3.select(`#${containerId}`).append('svg');
function updateChart() {
const { width, height } = container.getBoundingClientRect();
svg.attr('width', width).attr('height', height);
// Перерисовать визуализацию с новыми размерами
drawChart(data, svg, width, height);
}
// Обновить при начальной загрузке
updateChart();
// Обновить при изменении размера окна
window.addEventListener('resize', updateChart);
// Вернуть функцию очистки
return () => window.removeEventListener('resize', updateChart);
}
// Использование:
// const cleanup = setupResponsiveChart('chart-container', myData);
// cleanup(); // Вызвать при размонтировании компонента или удалении элемента
Или используйте ResizeObserver для более прямого мониторинга контейнера:
function setupResponsiveChartWithObserver(svgElement, data) {
const observer = new ResizeObserver(() => {
const { width, height } = svgElement.getBoundingClientRect();
d3.select(svgElement)
.attr('width', width)
.attr('height', height);
// Перерисовать визуализацию
drawChart(data, d3.select(svgElement), width, height);
});
observer.observe(svgElement.parentElement);
return () => observer.disconnect();
}
function drawBarChart(data, svgElement) {
if (!data || data.length === 0) return;
const svg = d3.select(svgElement);
svg.selectAll("*").remove();
const width = 800;
const height = 400;
const margin = { top: 20, right: 30, bottom: 40, left: 50 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const xScale = d3.scaleBand()
.domain(data.map(d => d.category))
.range([0, innerWidth])
.padding(0.1);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([innerHeight, 0]);
g.append("g")
.attr("transform", `translate(0,${innerHeight})`)
.call(d3.axisBottom(xScale));
g.append("g")
.call(d3.axisLeft(yScale));
g.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => xScale(d.category))
.attr("y", d => yScale(d.value))
.attr("width", xScale.bandwidth())
.attr("height", d => innerHeight - yScale(d.value))
.attr("fill", "steelblue");
}
// Использование:
// drawBarChart(myData, document.getElementById('chart'));
const line = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX); // Плавная кривая
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 2)
.attr("d", line);
g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", d => sizeScale(d.size)) // Опционально: кодирование размера
.attr("fill", d => colourScale(d.category)) // Опционально: кодирование цвета
.attr("opacity", 0.7);
Chord-диаграмма показывает связи между сущностями в круговом layout'е, с лентами, представляющими потоки между ними:
function drawChordDiagram(data) {
// формат данных: массив объектов с source, target и value
// Пример: [{ source: 'A', target: 'B', value: 10 }, ...]
if (!data || data.length === 0) return;
const svg = d3.select('#chart');
svg.selectAll("*").remove();
const width = 600;
const height = 600;
const innerRadius = Math.min(width, height) * 0.3;
const outerRadius = innerRadius + 30;
// Создать матрицу из данных
const nodes = Array.from(new Set(data.flatMap(d => [d.source, d.target])));
const matrix = Array.from({ length: nodes.length }, () => Array(nodes.length).fill(0));
data.forEach(d => {
const i = nodes.indexOf(d.source);
const j = nodes.indexOf(d.target);
matrix[i][j] += d.value;
matrix[j][i] += d.value;
});
// Создать chord layout
const chord = d3.chord()
.padAngle(0.05)
.sortSubgroups(d3.descending);
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
const ribbon = d3.ribbon()
.source(d => d.source)
.target(d => d.target);
const colourScale = d3.scaleOrdinal(d3.schemeCategory10)
.domain(nodes);
const g = svg.append("g")
.attr("transform", `translate(${width / 2},${height / 2})`);
const chords = chord(matrix);
// Отрисовать ленты
g.append("g")
.attr("fill-opacity", 0.67)
.selectAll("path")
.data(chords)
.join("path")
.attr("d", ribbon)
.attr("fill", d => colourScale(nodes[d.source.index]))
.attr("stroke", d => d3.rgb(colourScale(nodes[d.source.index])).darker());
// Отрисовать группы (дуги)
const group = g.append("g")
.selectAll("g")
.data(chords.groups)
.join("g");
group.append("path")
.attr("d", arc)
.attr("fill", d => colourScale(nodes[d.index]))
.attr("stroke", d => d3.rgb(colourScale(nodes[d.index])).darker());
// Добавить подписи
group.append("text")
.each(d => { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", "0.31em")
.attr("transform", d => `rotate(${(d.angle * 180 / Math.PI) - 90})translate(${outerRadius + 30})${d.angle > Math.PI ? "rotate(180)" : ""}`)
.attr("text-anchor", d => d.angle > Math.PI ? "end" : null)
.text((d, i) => nodes[i])
.style("font-size", "12px");
}
Тепловая карта использует цвет для кодирования значений в двумерной сетке, полезна для показа паттернов по категориям:
function drawHeatmap(data) {
// формат данных: массив объектов с row, column и value
// Пример: [{ row: 'A', column: 'X', value: 10 }, ...]
if (!data || data.length === 0) return;
const svg = d3.select('#chart');
svg.selectAll("*").remove();
const width = 800;
const height = 600;
const margin = { top: 100, right: 30, bottom: 30, left: 100 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
// Получить уникальные строки и столбцы
const rows = Array.from(new Set(data.map(d => d.row)));
const columns = Array.from(new Set(data.map(d => d.column)));
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Создать шкалы
const xScale = d3.scaleBand()
.domain(columns)
.range([0, innerWidth])
.padding(0.01);
const yScale = d3.scaleBand()
.domain(rows)
.range([0, innerHeight])
.padding(0.01);
// Цветовая шкала для значений
const colourScale = d3.scaleSequential(d3.interpolateYlOrRd)
.domain([0, d3.max(data, d => d.value)]);
// Отрисовать прямоугольники
g.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => xScale(d.column))
.attr("y", d => yScale(d.row))
.attr("width", xScale.bandwidth())
.attr("height", yScale.bandwidth())
.attr("fill", d => colourScale(d.value));
// Добавить подписи оси X
svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
.selectAll("text")
.data(columns)
.join("text")
.attr("x", d => xScale(d) + xScale.bandwidth() / 2)
.attr("y", -10)
.attr("text-anchor", "middle")
.text(d => d)
.style("font-size", "12px");
// Добавить подписи оси Y
svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
.selectAll("text")
.data(rows)
.join("text")
.attr("x", -10)
.attr("y", d => yScale(d) + yScale.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(d => d)
.style("font-size", "12px");
// Добавить цветовую легенду
const legendWidth = 20;
const legendHeight = 200;
const legend = svg.append("g")
.attr("transform", `translate(${width - 60},${margin.top})`);
const legendScale = d3.scaleLinear()
.domain(colourScale.domain())
.range([legendHeight, 0]);
const legendAxis = d3.axisRight(legendScale)
.ticks(5);
// Отрисовать цветовой градиент в легенде
for (let i = 0; i < legendHeight; i++) {
legend.append("rect")
.attr("y", i)
.attr("width", legendWidth)
.attr("height", 1)
.attr("fill", colourScale(legendScale.invert(i)));
}
legend.append("g")
.attr("transform", `translate(${legendWidth},0)`)
.call(legendAxis);
}
const pie = d3.pie()
.value(d => d.value)
.sort(null);
const arc = d3.arc()
.innerRadius(0)
.outerRadius(Math.min(width, height) / 2 - 20);
const colourScale = d3.scaleOrdinal(d3.schemeCategory10);
const g = svg.append("g")
.attr("transform", `translate(${width / 2},${height / 2})`);
g.selectAll("path")
.data(pie(data))
.join("path")
.attr("d", arc)
.attr("fill", (d, i) => colourScale(i))
.attr("stroke", "white")
.attr("stroke-width", 2);
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2));
const link = g.selectAll("line")
.data(links)
.join("line")
.attr("stroke", "#999")
.attr("stroke-width", 1);
const node = g.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", 8)
.attr("fill", "steelblue")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
// Создать div для подсказки (вне SVG)
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background-color", "white")
.style("border", "1px solid #ddd")
.style("padding", "10px")
.style("border-radius", "4px")
.style("pointer-events", "none");
// Добавить к элементам
circles
.on("mouseover", function(event, d) {
d3.select(this).attr("opacity", 1);
tooltip
.style("visibility", "visible")
.html(`<strong>${d.label}</strong><br/>Value: ${d.value}`);
})
.on("mousemove", function(event) {
tooltip
.style("top", (event.pageY - 10) + "px")
.style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function() {
d3.select(this).attr("opacity", 0.7);
tooltip.style("visibility", "hidden");
});
const zoom = d3.zoom()
.scaleExtent([0.5, 10])
.on("zoom", (event) => {
g.attr("transform", event.transform);
});
svg.call(zoom);
circles
.on("click", function(event, d) {
// Обработать клик (диспатч события, обновление состояния приложения и т.д.)
console.log("Clicked:", d);
// Визуальный отклик
d3.selectAll("circle").attr("fill", "steelblue");
d3.select(this).attr("fill", "orange");
// Опционально: диспатч кастомного события для вашего фреймворка/приложения
// window.dispatchEvent(new CustomEvent('chartClick', { detail: d }));
});
Добавляйте плавные переходы к визуальным изменениям:
// Базовый переход
circles
.transition()
.duration(750)
.attr("r", 10);
// Цепочка переходов
circles
.transition()
.duration(500)
.attr("fill", "orange")
.transition()
.duration(500)
.attr("r", 15);
// Ступенчатые переходы
circles
.transition()
.delay((d, i) => i * 50)
.duration(500)
.attr("cy", d => yScale(d.value));
// Кастомный easing
circles
.transition()
.duration(1000)
.ease(d3.easeBounceOut)
.attr("r", 10);
// Линейная шкала
const xScale = d3.scaleLinear()
.domain([0, 100])
.range([0, 500]);
// Логарифмическая шкала (для экспоненциальных данных)
const logScale = d3.scaleLog()
.domain([1, 1000])
.range([0, 500]);
// Степенная шкала
const powScale = d3.scalePow()
.exponent(2)
.domain([0, 100])
.range([0, 500]);
// Временная шкала
const timeScale = d3.scaleTime()
.domain([new Date(2020, 0, 1), new Date(2024, 0, 1)])
.range([0, 500]);
// Полосовая шкала (для столбчатых диаграмм)
const bandScale = d3.scaleBand()
.domain(['A', 'B', 'C', 'D'])
.range([0, 400])
.padding(0.1);
// Точечная шкала (для категорий линий/точек)
const pointScale = d3.scalePoint()
.domain(['A', 'B', 'C', 'D'])
.range([0, 400]);
// Порядковая шкала (для цветов)
const colourScale = d3.scaleOrdinal(d3.schemeCategory10);
// Последовательная цветовая шкала
const colourScale = d3.scaleSequential(d3.interpolateBlues)
.domain([0, 100]);
// Дивергентная цветовая шкала
const divScale = d3.scaleDiverging(d3.interpolateRdBu)
.domain([-10, 0, 10]);
Всегда валидируйте и готовьте данные перед визуализацией:
// Отфильтровать невалидные значения
const cleanData = data.filter(d => d.value != null && !isNaN(d.value));
// Отсортировать данные, если порядок важен
const sortedData = [...data].sort((a, b) => b.value - a.value);
// Распарсить даты
const parsedData = data.map(d => ({
...d,
date: d3.timeParse("%Y-%m-%d")(d.date)
}));
Для больших датасетов (>1000 элементов):
// Используйте canvas вместо SVG для множества элементов
// Используйте quadtree для обнаружения коллизий
// Упрощайте пути через d3.line().curve(d3.curveStep)
// Реализуйте виртуальный скроллинг для больших списков
// Используйте requestAnimationFrame для кастомных анимаций
Делайте визуализации доступными:
// Добавьте ARIA-подписи
svg.attr("role", "img")
.attr("aria-label", "Bar chart showing quarterly revenue");
// Добавьте заголовок и описание
svg.append("title").text("Quarterly Revenue 2024");
svg.append("desc").text("Bar chart showing revenue growth across four quarters");
// Обеспечьте достаточный цветовой контраст
// Предоставьте навигацию с клавиатуры для интерактивных элементов
// Включите альтернативу в виде таблицы данных
Используйте последовательную, профессиональную стилизацию:
// Определите цветовые палитры заранее
const colours = {
primary: '#4A90E2',
secondary: '#7B68EE',
background: '#F5F7FA',
text: '#333333',
gridLines: '#E0E0E0'
};
// Примените единообразную типографику
svg.selectAll("text")
.style("font-family", "Inter, sans-serif")
.style("font-size", "12px");
// Используйте ненавязчивые линии сетки
g.selectAll(".tick line")
.attr("stroke", colours.gridLines)
.attr("stroke-dasharray", "2,2");
Проблема: Оси не появляются - Убедитесь, что у шкал валидные домены (проверьте на значения NaN) - Проверьте, что ось добавлена в правильную группу - Проверьте, что transform-трансляции корректны
Проблема: Переходы не работают
- Вызывайте .transition() перед изменением атрибутов
- Убедитесь, что элементы имеют уникальные ключи для правильной привязки данных
- Проверьте, что зависимости useEffect включают все изменяющиеся данные
Проблема: Адаптивный размер не работает - Используйте ResizeObserver или слушатель resize окна - Обновляйте размеры в состоянии, чтобы вызвать перерендер - Убедитесь, что у SVG есть атрибуты width/height или viewBox
Проблема: Проблемы производительности
- Ограничьте число DOM-элементов (рассмотрите canvas для >1000 элементов)
- Используйте debounce для обработчиков resize
- Используйте .join() вместо отдельных selection'ов enter/update/exit
- Избегайте лишних перерендеров, проверяя зависимости
Содержит детальные референсные материалы:
- d3-patterns.md — исчерпывающая коллекция паттернов визуализации и примеров кода
- scale-reference.md — полный гайд по шкалам d3 с примерами
- colour-schemes.md — цветовые схемы d3 и рекомендации по палитрам
Содержит шаблоны-заготовки:
chart-template.js — стартовый шаблон для базового чартаinteractive-template.js — шаблон с подсказками, zoom и взаимодействиямиsample-data.json — примеры датасетов для тестированияЭти шаблоны работают с чистым JavaScript, React, Vue, Svelte или любой другой JavaScript-средой. Адаптируйте их под ваш конкретный фреймворк.
Чтобы использовать эти ресурсы, читайте соответствующие файлы, когда нужно детальное руководство по конкретным типам визуализаций или паттернам.