ایجاد نمودار حبابی (Bubble Chart) با استفاده از کتابخانه D3

در این مقاله، قصد داریم نحوه‌ی ایجاد یک نمودار حبابی (Bubble Chart) را که جمعیت استان‌های ایران را به‌صورت بصری نمایش می‌دهد، با استفاده از کتابخانه‌ی D3.js بررسی کنیم. این نمودار حبابی به ما کمک می‌کند تا تفاوت‌های جمعیتی استان‌های مختلف را با اندازه‌های مختلف دایره‌ها نمایش دهیم.

 

کد کامل:

 

var width = 800;
var height = 600;
 
var svg = d3.create("svg")
.attr("width", width)
.attr("height", height);
 
var radiusScale = d3.scaleSqrt()
.domain([0, d3.max(iranPopulation, d => d.population)])
.range([0, 100]);
 
var simulation = d3.forceSimulation(iranPopulation)
.force("x", d3.forceX(width / 2).strength(0.05))
.force("y", d3.forceY(height / 2).strength(0.05))
.force("collide", d3.forceCollide(d => radiusScale(d.population) + 2))
.on("tick", ticked);
 
function ticked() {
var u = svg.selectAll("circle")
.data(iranPopulation)
.join("circle")
.attr("r", d => radiusScale(d.population))
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", "steelblue");
 
var labels = svg.selectAll(".circle-label")
.data(iranPopulation)
.join("text")
.attr("class", "circle-label")
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr('text-anchor', 'middle')
.attr('font-size', 10)
.text(d => d.province);
}
 
svg.node()

 

 خروجی:

 

 

 تنظیمات اولیه و ایجاد عنصر SVG

 

در این خطوط، عرض و ارتفاع نمودار را تنظیم می‌کنیم. این مقادیر تعیین‌کننده اندازه نهایی نمودار خواهند بود. پس از آن مقادیر را به برای ایجاد عنصر SVG به تابع سازنده (create) کتابخانه D3 ارجاع می‌دهیم.

 

var width = 800;
var height = 600;
 
var svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

 

تعریف مقیاس برای اندازه دایره‌ها

 

در اینجا از مقیاس d3.scaleSqrt برای تعیین اندازه دایره‌ها استفاده می‌کنیم. دامنه‌ی این مقیاس از صفر تا حداکثر جمعیت در داده‌هایمان است، و بازه‌ی خروجی آن از 0 تا 100 پیکسل برای شعاع دایره‌ها است.

 

var radiusScale = d3.scaleSqrt()
.domain([0, d3.max(iranPopulation, d => d.population)])
.range([0, 100]);

 

تنظیم نیروهای D3 برای محاسبه موقعیت دایره‌ها

 

در اینجا، از شبیه‌سازی نیروها در D3 استفاده می‌کنیم. این شبیه‌سازی شامل سه نیرو است:

  • forceX و forceY برای جذب دایره‌ها به مرکز نمودار.
  • forceCollide برای جلوگیری از تداخل دایره‌ها، که شعاع هر دایره را با استفاده از radiusScale تنظیم می‌کند.

 

var simulation = d3.forceSimulation(iranPopulation)
.force("x", d3.forceX(width / 2).strength(0.05))
.force("y", d3.forceY(height / 2).strength(0.05))
.force("collide", d3.forceCollide(d => radiusScale(d.population) + 2))
.on("tick", ticked);

 

  • d3.forceSimulation(iranPopulation): این خط یک شبیه‌سازی نیروی D3 ایجاد می‌کند که بر روی آرایه iranPopulation اعمال می‌شود. هر عنصر از این آرایه یک نقطه داده در شبیه‌سازی است که نیروها بر روی آن اعمال می‌شوند.
  • .force("x", d3.forceX(width / 2).strength(0.05)): این خط یک نیروی x به شبیه‌سازی اضافه می‌کند که تمام نقاط داده را به سمت مرکز افقی نمودار جذب می‌کند. d3.forceX(width / 2) نیرویی ایجاد می‌کند که همه نقاط را به مقدار width / 2 در محور x جذب می‌کند.
  • .strength(0.05): این قسمت قدرت نیروی x را تنظیم می‌کند. قدرت نیرو تعیین می‌کند که چقدر سریع نقاط داده به مرکز افقی جذب شوند. مقدار 0.05 نسبتاً ضعیف است و باعث می‌شود نقاط به آرامی به سمت مرکز حرکت کنند.
  • .force("y", d3.forceY(height / 2).strength(0.05)): این خط یک نیروی y به شبیه‌سازی اضافه می‌کند که تمام نقاط داده را به سمت مرکز عمودی نمودار جذب می‌کند. d3.forceY(height / 2) نیرویی ایجاد می‌کند که همه نقاط را به مقدار height / 2 در محور y جذب می‌کند.
  • .strength(0.05): این قسمت قدرت نیروی y را تنظیم می‌کند. قدرت نیرو تعیین می‌کند که چقدر سریع نقاط داده به مرکز عمودی جذب شوند. مقدار 0.05 نسبتاً ضعیف است و باعث می‌شود نقاط به آرامی به سمت مرکز حرکت کنند.
  • .force("collide", d3.forceCollide(d => radiusScale(d.population) + 2)): این خط یک نیروی تداخل (collide) به شبیه‌سازی اضافه می‌کند که از همپوشانی نقاط داده جلوگیری می‌کند. d3.forceCollide یک نیروی تداخل ایجاد می‌کند.
  • d => radiusScale(d.population) + 2: این تابع برای هر نقطه داده (d) شعاع دایره را با استفاده از radiusScale محاسبه می‌کند و سپس 2 واحد به آن اضافه می‌کند. این کار اطمینان حاصل می‌کند که نقاط داده به اندازه کافی فاصله دارند تا دایره‌ها با هم تداخل نکنند.
  • .on("tick", ticked): این خط تابع ticked را به‌عنوان هندلر برای رویداد tick در شبیه‌سازی تنظیم می‌کند. هر بار که شبیه‌سازی به‌روز می‌شود (یا به اصطلاح یک "تیک" می‌زند)، تابع ticked فراخوانی می‌شود تا موقعیت‌های نقاط داده به‌روز شود و نمودار مجدداً رسم شود.

 

تابع ticked برای به‌روزرسانی موقعیت دایره‌ها

 

در این تابع، دایره‌ها و برچسب‌ها را در هر تیک شبیه‌سازی به‌روزرسانی می‌کنیم:

  • ابتدا دایره‌ها را براساس داده‌ها و مقیاس شعاع ایجاد و به‌روزرسانی می‌کنیم.
  • سپس برچسب‌ها را برای هر دایره ایجاد می‌کنیم و موقعیت و متن آن‌ها را تنظیم می‌کنیم.

 

function ticked() {
var u = svg.selectAll("circle")
.data(iranPopulation)
.join("circle")
.attr("r", d => radiusScale(d.population))
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", "steelblue");
 
var labels = svg.selectAll(".circle-label")
.data(iranPopulation)
.join("text")
.attr("class", "circle-label")
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr('text-anchor', 'middle')
.attr('font-size', 10)
.text(d => d.province);
}

 

  • var u = svg.selectAll("circle"): این خط تمام عناصر circle موجود در SVG را انتخاب می‌کند.
  • .data(iranPopulation): این خط داده‌ها (آرایه iranPopulation) را به انتخاب circle اتصال می‌دهد.
  • .join("circle"): این خط عناصر circle جدید را برای هر عنصر داده‌ای که وجود ندارد ایجاد می‌کند و اطمینان حاصل می‌کند که هر عنصر داده‌ای یک دایره مرتبط دارد.
  • .attr("r", d => radiusScale(d.population)): این خط شعاع هر دایره را بر اساس جمعیت استان تنظیم می‌کند. تابع radiusScale مقدار جمعیت را به شعاع دایره تبدیل می‌کند.
  • .attr("cx", d => d.x): این خط موقعیت افقی (x) دایره را بر اساس مختصات x محاسبه شده توسط شبیه‌سازی تنظیم می‌کند.
  • .attr("cy", d => d.y): این خط موقعیت عمودی (y) دایره را بر اساس مختصات y محاسبه شده توسط شبیه‌سازی تنظیم می‌کند.
  • .attr("fill", "steelblue"): این خط رنگ دایره‌ها را به "steelblue" تنظیم می‌کند.
  • var labels = svg.selectAll(".circle-label"): این خط تمام عناصر text با کلاس circle-label موجود در SVG را انتخاب می‌کند.
  • .data(iranPopulation): این خط داده‌ها (آرایه iranPopulation) را به انتخاب text اتصال می‌دهد.
  • .join("text"): این خط عناصر text جدید را برای هر عنصر داده‌ای که وجود ندارد ایجاد می‌کند و اطمینان حاصل می‌کند که هر عنصر داده‌ای یک برچسب متن مرتبط دارد.
  • .attr("class", "circle-label"): این خط کلاس circle-label را به هر عنصر text اضافه می‌کند تا بتوان آن‌ها را با این کلاس شناسایی کرد.
  • .attr("x", d => d.x): این خط موقعیت افقی (x) برچسب متن را بر اساس مختصات x محاسبه شده توسط شبیه‌سازی تنظیم می‌کند.
  • .attr("y", d => d.y): این خط موقعیت عمودی (y) برچسب متن را بر اساس مختصات y محاسبه شده توسط شبیه‌سازی تنظیم می‌کند.
  • .attr('text-anchor', 'middle'): این خط متن برچسب را در مرکز دایره تراز می‌کند.
  • .attr('font-size', 10): این خط اندازه فونت متن برچسب را به 10 تنظیم می‌کند.
  • .text(d => d.province): این خط متن برچسب را به نام استان تنظیم می‌کند.

 

در این مقاله، نحوه ایجاد یک نمودار حبابی برای نمایش جمعیت استان‌های ایران با استفاده از کتابخانه D3.js را بررسی کردیم. این نوع نمودار به ما امکان می‌دهد تا با استفاده از اندازه دایره‌ها، جمعیت هر استان را به‌صورت بصری مقایسه کنیم. با استفاده از نیروهای D3، موقعیت دایره‌ها به‌طور خودکار محاسبه و بهینه‌سازی می‌شود تا تداخل نداشته باشند و به‌صورت منظم در نمودار قرار گیرند.