آموزش جامع D3.js
ویژوالیزیشن داده با SVG — کنترل کامل بر DOM، Scales، Axes و انیمیشن
معرفی و نصب
D3.js چیست؟
D3.js (مخفف Data-Driven Documents) یک کتابخانه JavaScript است که توسط Mike Bostock ساخته شده. برخلاف Chart.js که نمودارهای آماده دارد، D3 ابزار سطح پایینتری است که کنترل کامل بر DOM، SVG و انیمیشن میدهد.
SVG در مقابل Canvas
| ویژگی | SVG | Canvas |
|---|---|---|
| نوع | برداری | رستری |
| DOM Element | ✓ بله | ✗ خیر |
| رویداد (click/hover) | ✓ راحت | ✗ دستی |
| عملکرد داده زیاد | خوب | عالی |
| D3 پیشفرض | ✓ بله | ممکن |
نصب
<!-- فایل لوکال -->
<script src="d3.min.js"></script>
<!-- CDN -->
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
/* NPM */
// npm install d3
import * as d3 from "d3";
Selection و DOM
Selection در D3 شبیه document.querySelector است اما با قابلیتهای بیشتر برای تغییر دستهجمعی المانها.
انتخاب و تغییر المانها
// انتخاب یک المان
d3.select('#my-svg')
// انتخاب همه المانها
d3.selectAll('circle')
// تغییر خاصیتها
d3.select('#title')
.text('D3.js') // محتوای متنی
.style('color', '#88ce02') // CSS style
.attr('class', 'title'); // HTML attribute
// اضافه و حذف کلاس
d3.select('.box').classed('active', true);
// اضافه کردن المان
d3.select('#container')
.append('p')
.text('یک پاراگراف جدید');
// حذف المان
d3.select('.old').remove();
// رویداد
d3.select('button').on('click', event => {
console.log('کلیک شد!', event);
});
Data Binding — اتصال داده
Data Binding قلب D3 است: دادهها را به عناصر DOM متصل میکنید. با .data(array) هر عنصر آرایه به یک DOM element مرتبط میشود.
const data = [10, 30, 20, 50, 40];
d3.select('#chart')
.selectAll('div') // انتخاب مجازی (هنوز وجود ندارند)
.data(data) // اتصال داده
.join('div') // ایجاد/آپدیت/حذف خودکار
.style('width', d => d * 5 + 'px') // d = مقدار هر داده
.style('background', '#88ce02')
.text(d => d);
// آرایهای از object
const products = [
{ name: 'لپتاپ', sales: 45 },
{ name: 'موبایل', sales: 80 },
{ name: 'تبلت', sales: 30 }
];
d3.select('#list')
.selectAll('li')
.data(products)
.join('li')
.text(d => `${d.name}: ${d.sales} فروش`);
Enter / Update / Exit
این مفهوم کلیدی D3 است که تعیین میکند وقتی دادهها تغییر میکنند، DOM چطور آپدیت شود.
| حالت | معنی | کاربرد |
|---|---|---|
.enter() | داده بیشتر از DOM | اضافه کردن المان جدید |
.exit() | DOM بیشتر از داده | حذف المان اضافی |
| update (selection) | تطابق داده و DOM | آپدیت المانهای موجود |
.join() | هر سه با هم | سادهترین روش |
// روش قدیمی (کنترل کامل)
const sel = svg.selectAll('circle').data(newData);
sel.enter().append('circle') // المانهای جدید
.attr('r', 0)
.transition()
.attr('r', d => d);
sel.exit() // المانهای اضافی
.transition()
.attr('r', 0)
.remove();
sel // المانهای موجود
.transition()
.attr('r', d => d);
// روش جدید (D3 v5+) — توصیهشده
svg.selectAll('circle')
.data(newData, d => d.id) // key function
.join(
enter => enter.append('circle').attr('r', 0).transition().attr('r', d => d.r),
update => update.transition().attr('r', d => d.r),
exit => exit.transition().attr('r', 0).remove()
);
SVG و رسم اشکال
// ساختار پایه SVG
const margin = { top: 20, right: 20, bottom: 40, left: 50 };
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g') // گروه اصلی
.attr('transform', `translate(${margin.left},${margin.top})`);
// اشکال اصلی SVG
svg.append('rect').attr('x',0).attr('y',0).attr('width',100).attr('height',60).attr('fill','#88ce02');
svg.append('circle').attr('cx',150).attr('cy',50).attr('r',40).attr('fill','#3498db');
svg.append('line').attr('x1',0).attr('y1',100).attr('x2',300).attr('y2',100).attr('stroke','#e74c3c').attr('stroke-width',2);
svg.append('text').attr('x',50).attr('y',150).text('متن SVG').attr('fill','#333');
Scales — مقیاسها
Scale در D3 یک تابع تبدیل است که مقادیر داده (domain) را به مختصات تصویر (range) تبدیل میکند.
// ۱. scaleLinear — برای اعداد پیوسته
const yScale = d3.scaleLinear()
.domain([0, 100]) // محدوده داده (min → max)
.range([height, 0]); // محدوده SVG (پایین → بالا)
yScale(50); // → وسط height
yScale(0); // → height (پایین)
yScale(100); // → 0 (بالا)
// ۲. scaleBand — برای دستهها (Bar chart)
const xScale = d3.scaleBand()
.domain(['A', 'B', 'C', 'D'])
.range([0, width])
.padding(0.2); // فاصله بین میلهها
xScale.bandwidth(); // عرض هر میله
// ۳. scaleTime — برای تاریخ
const tScale = d3.scaleTime()
.domain([new Date('2024-01-01'), new Date('2024-12-31')])
.range([0, width]);
// ۴. scaleOrdinal — رنگبندی
const colorScale = d3.scaleOrdinal(d3.schemeTableau10);
colorScale('فروردین'); // رنگ اول
colorScale('اردیبهشت'); // رنگ دوم
// ۵. scaleSequential — گرادیان رنگ
const heatScale = d3.scaleSequential(d3.interpolateRdYlGn)
.domain([0, 100]);
heatScale(0); // قرمز
heatScale(50); // زرد
heatScale(100); // سبز
Axes — محورها
// ساختن محور X
const xAxis = d3.axisBottom(xScale) // محور پایین
.tickSize(6)
.tickPadding(8);
// ساختن محور Y
const yAxis = d3.axisLeft(yScale) // محور چپ
.ticks(5)
.tickFormat(d => d + '٪'); // فرمتبندی
// اعمال محورها به SVG
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${height})`)
.call(xAxis);
svg.append('g')
.attr('class', 'y-axis')
.call(yAxis);
// خطوط راهنما (Grid Lines)
svg.append('g')
.attr('class', 'grid')
.call(d3.axisLeft(yScale)
.tickSize(-width)
.tickFormat('') // بدون متن
);
// برچسب محور
svg.append('text')
.attr('transform', `translate(${width/2},${height+35})`)
.text('ماه')
.style('text-anchor', 'middle');
نمودار میلهای کامل
ترکیب همه مفاهیم: Data Binding، Scales، Axes، Tooltip و Transition.
const data = [
{ month: 'فروردین', value: 45 },
{ month: 'اردیبهشت', value: 72 },
// ...
];
const margin = {top:20, right:20, bottom:60, left:60};
const w = 560 - margin.left - margin.right;
const h = 320 - margin.top - margin.bottom;
const svg = d3.select('#bar-chart')
.append('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const x = d3.scaleBand().domain(data.map(d=>d.month)).range([0,w]).padding(0.25);
const y = d3.scaleLinear().domain([0, d3.max(data, d=>d.value)*1.1]).range([h,0]);
// میلهها
svg.selectAll('.bar').data(data).join('rect')
.attr('class', 'bar')
.attr('x', d => x(d.month))
.attr('width', x.bandwidth())
.attr('y', h) // شروع از پایین
.attr('height', 0) // ارتفاع صفر
.attr('fill', '#88ce02')
.transition().duration(800).delay((d,i) => i*80)
.attr('y', d => y(d.value))
.attr('height', d => h - y(d.value));
نمودار خطی و ناحیهای
// d3.line — تولید path برای خط
const lineGen = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX); // نوع منحنی
svg.append('path')
.datum(data) // .datum() برای یک مجموعه داده
.attr('fill', 'none')
.attr('stroke', '#88ce02')
.attr('stroke-width', 2.5)
.attr('d', lineGen); // d = SVG path string
// d3.area — تولید path برای ناحیه زیر خط
const areaGen = d3.area()
.x(d => xScale(d.date))
.y0(height) // خط پایین (baseline)
.y1(d => yScale(d.value)) // خط بالا (مقدار)
.curve(d3.curveMonotoneX);
svg.append('path')
.datum(data)
.attr('fill', 'rgba(136,206,2,0.15)')
.attr('d', areaGen);
curveLinear (خط مستقیم) | curveMonotoneX (منحنی بدون دشواری) | curveCatmullRom (منحنی نرم) | curveStep (پلهای)
نمودار دایرهای و دونات
// d3.pie — محاسبه زوایا
const pieGen = d3.pie()
.value(d => d.value)
.sort(null); // حفظ ترتیب اصلی
// d3.arc — رسم قوس
const arcGen = d3.arc()
.innerRadius(0) // 0 = pie | > 0 = doughnut
.outerRadius(radius);
// arc برای hover (کمی بزرگتر)
const arcHover = d3.arc()
.innerRadius(0)
.outerRadius(radius + 10);
// رسم قطعهها
const arcs = svg.selectAll('.slice')
.data(pieGen(data))
.join('path')
.attr('class', 'slice')
.attr('d', arcGen)
.attr('fill', (d,i) => colorScale(i))
.on('mouseenter', function(event, d) {
d3.select(this).attr('d', arcHover); // بزرگ شدن
})
.on('mouseleave', function(event, d) {
d3.select(this).attr('d', arcGen); // برگشت
});
Tooltip و تعامل
// ایجاد tooltip div
const tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('opacity', '0')
.style('background', 'rgba(0,0,0,.8)')
.style('color', 'white')
.style('padding', '8px 12px')
.style('border-radius', '6px');
// رویدادهای hover
bars
.on('mouseover', (event, d) => {
tooltip
.html(`${d.month}
فروش: ${d.value} میلیون`)
.style('opacity', '1')
.style('left', event.pageX + 10 + 'px')
.style('top', event.pageY - 20 + 'px');
})
.on('mousemove', event => {
tooltip
.style('left', event.pageX + 10 + 'px')
.style('top', event.pageY - 20 + 'px');
})
.on('mouseleave', () => {
tooltip.style('opacity', '0');
});
// Bisector — یافتن نزدیکترین نقطه به موس در Line Chart
const bisect = d3.bisector(d => d.date).left;
svg.on('mousemove', event => {
const xVal = xScale.invert(d3.pointer(event)[0]);
const idx = bisect(data, xVal);
const point = data[idx];
tooltip.html(`${point.date}: ${point.value}`);
});
Transitions و انیمیشن
// انیمیشن ساده
d3.select('#circle')
.transition()
.duration(1000)
.ease(d3.easeElastic)
.attr('r', 50)
.attr('fill', '#3498db');
// Easing انواع
d3.easeLinear // یکنواخت
d3.easeQuad // شتاب quadratic
d3.easeCubicInOut // شروع و پایان کند
d3.easeBounce // پرشی
d3.easeElastic // ارتجاعی
d3.easeBack // کمی از target رد میشود
// زنجیره transition
d3.select('#box')
.transition().duration(500)
.attr('width', 200)
.transition().duration(300)
.style('fill', '#e74c3c')
.transition().duration(500)
.attr('width', 50);
// on('end') — بعد از تمام انیمیشن
d3.select('.bar')
.transition()
.on('end', () => console.log('تمام شد!'));
خلاصه مهمترین توابع D3
| تابع | کاربرد |
|---|---|
d3.select / selectAll | انتخاب DOM |
selection.data().join() | Data Binding |
d3.scaleLinear/Band/Time | تبدیل داده به مختصات |
d3.axisBottom/Left | رسم محور |
d3.line / area | مولد مسیر SVG |
d3.pie / arc | نمودار دایرهای |
d3.max / min / extent | محاسبه domain |
d3.bisector | جستجوی باینری در آرایه |
selection.transition() | انیمیشن |
d3.schemeTableau10 | پالت رنگ |
🚀زمین بازی کد (Playground)
کد D3.js را همینجا اجرا کن و نتیجه را روی SVG زنده ببین. data binding، scales و transitions را تغییر بده و ▶ اجرا را بزن. تب HTML شامل <svg> است. نسخهٔ ۷.۹.۰ آفلاین بارگذاری میشود.
console.log() در پنل پایین نمایش داده میشود.