ایجاد نمودار محیطی Area با کتابخانه D3

در این مقاله، نحوه ساخت نمودار Area با استفاده از کتابخانه D3.js برای نمایش داده‌های تورم به‌صورت بصری توضیح داده شده است. با ما همراه باشید تا مراحل مختلف این فرآیند را بررسی کنیم.

 

داده‌های این مقاله از وبگاه بانک مرکزی جمع‌آوری شده است. فرمت داده‌ها به صورت زیر می‌باشد:

 

{
"date":"1401-12"
"inflation":46.5
},
{
"date":"1401-11"
"inflation":43.6
}

 

کد کامل نمودار:

 

Inflation.forEach(d => d.date = new Date(d.date + "-01")); // تاریخ کامل با اضافه کردن روز اول ماه
 
var margin = {top: 50, right: 30, bottom: 50, left: 50},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
 
var svg = d3.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
 
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
 
var x = d3.scaleTime()
.domain(d3.extent(Inflation, d => d.date))
.range([0, width]);
 
var y = d3.scaleLinear()
.domain([0, d3.max(Inflation, d => d.inflation)])
.range([height, 0]);
 
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y"))
.ticks(d3.timeYear.every(1)));
 
g.append("g")
.call(d3.axisLeft(y));
 
g.append("path")
.datum(Inflation)
.attr("fill", "steelblue")
.attr("fill-opacity", 0.6)
.attr("stroke", "none")
.attr("d", d3.area()
.x(d => x(d.date))
.y0(height)
.y1(d => y(d.inflation)))
 
g.append("text")
.attr("transform", "translate(" + (width / 2) + " ," + (height + margin.bottom / 1.5) + ")")
.style("text-anchor", "middle")
.text("سال");
 
g.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left / 1.5)
.attr("x", 0 - (height / 2))
.style("text-anchor", "middle")
.text("درصد");
 
svg.node()

 

خروجی:

 

 

آماده‌سازی داده‌ها

 

پیش از رسم نمودار، باید داده‌ها را به فرمت مناسب تبدیل کنیم. برای تبدیل این داده‌ها به فرمت قابل استفاده در D3.js، یک روز به تاریخ اضافه می‌کنیم تا بتوانیم آن را به‌عنوان یک تاریخ معتبر در جاوااسکریپت شناسایی کنیم. این کار با استفاده از new Date(d.date + "-01") انجام می‌شود که به هر تاریخ ماه، روز اول ماه اضافه می‌کند و آن را به یک شی Date جاوااسکریپت تبدیل می‌کند.

 

Inflation.forEach(d => d.date = new Date(d.date + "-01"));

 

تنظیمات و ساختار SVG

 

برای رسم نمودار، ابتدا باید ساختار SVG را تنظیم کنیم. در اینجا، ما حاشیه‌های نمودار (margin) را تعریف می‌کنیم تا فضای کافی برای برچسب‌ها و محورها در اطراف نمودار ایجاد شود. سپس با استفاده از d3.create("svg") یک عنصر SVG با اندازه‌های مناسب ایجاد می‌کنیم و آن را به یک گروه (g) اضافه می‌کنیم. گروه g برای اعمال تغییر مکان و رسم محورهای نمودار استفاده می‌شود.

 

var margin = {top: 50, right: 30, bottom: 50, left: 50},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
 
var svg = d3.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
 
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

 

تعریف مقیاس‌ها

 

برای رسم نمودار، نیاز به تعیین مقیاس‌های محورهای x و y داریم. مقیاس x از نوع d3.scaleTime() است که برای مقیاس‌گذاری داده‌های زمانی استفاده می‌شود. domain آن شامل کمترین و بیشترین تاریخ‌ها است و range آن مشخص‌کننده محدوده افقی نمودار است. مقیاس y از نوع d3.scaleLinear() است که برای داده‌های عددی استفاده می‌شود. domain شامل حداقل و حداکثر مقادیر تورم است و range آن برای نمایش مقادیر عمودی در نمودار استفاده می‌شود.

 

var x = d3.scaleTime()
.domain(d3.extent(Inflation, d => d.date))
.range([0, width]);
 
var y = d3.scaleLinear()
.domain([0, d3.max(Inflation, d => d.inflation)])
.range([height, 0]);

 

  • d3.scaleTime(): این تابع برای ایجاد مقیاس‌های زمانی استفاده می‌شود. مقیاس‌های زمانی به شما اجازه می‌دهند که داده‌های زمانی را به‌طور درست بر روی محور افقی (x) نمایش دهید.
  • domain(d3.extent(Inflation, d => d.date)): domain محدوده مقادیر ورودی برای مقیاس را تعیین می‌کند. در اینجا، d3.extent(Inflation, d => d.date) برای به‌دست‌آوردن کمترین و بیشترین تاریخ‌ها در داده‌های Inflation استفاده می‌شود.
  • d3.extent یک آرایه با دو عنصر باز می‌گرداند: کمترین و بیشترین مقدار از داده‌های ورودی. این مقادیر برای تعیین محدوده زمانی که نمودار باید نمایش دهد، استفاده می‌شود.
  • range([0, width]): range محدوده مقادیر خروجی را تعیین می‌کند. در اینجا، [0, width] نشان‌دهنده محدوده افقی نمودار است. این به معنای آن است که کمترین تاریخ به موقعیت 0 و بیشترین تاریخ به موقعیت width در محور افقی تبدیل می‌شود.
  • d3.scaleLinear(): این تابع برای ایجاد مقیاس‌های خطی استفاده می‌شود. مقیاس‌های خطی برای داده‌های عددی مناسب هستند و به شما این امکان را می‌دهند که مقادیر عددی را به موقعیت‌های بصری در محور عمودی (y) تبدیل کنید.
  • domain([0, d3.max(Inflation, d => d.inflation)]): domain در اینجا به‌طور دستی و از طریق یک آرایه شامل دو مقدار تعیین شده است: 0 و d3.max(Inflation, d => d.inflation).
  • d3.max برای به‌دست‌آوردن حداکثر مقدار از داده‌های تورم استفاده می‌شود. این مقدار و 0 به‌عنوان محدوده ورودی مقیاس استفاده می‌شود. این به این معنی است که محور عمودی از 0 تا بیشترین مقدار تورم نمایش داده خواهد شد.
  • range([height, 0]): range محدوده مقادیر خروجی را تعیین می‌کند. در اینجا، [height, 0] نشان‌دهنده موقعیت عمودی در نمودار است. به این معنا است که مقدار 0 به پایین‌ترین نقطه نمودار (height) و بیشترین مقدار به بالاترین نقطه (0) تبدیل می‌شود. این برعکس عمل می‌کند چون در سیستم مختصات SVG، مقادیر عمودی از بالا به پایین افزایش می‌یابند.

 

اضافه کردن محورهای نمودار

 

در این مرحله، محورهای x و y به نمودار اضافه می‌شوند. محور افقی با d3.axisBottom(x) و محور عمودی با d3.axisLeft(y) ایجاد می‌شود. برای محور افقی، از tickFormat(d3.timeFormat("%Y")) استفاده می‌شود تا فقط سال‌ها نمایش داده شوند و ticks(d3.timeYear.every(1)) برای تنظیم برچسب‌های سالانه است.

 

g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y"))
.ticks(d3.timeYear.every(1)));
 
g.append("g")
.call(d3.axisLeft(y));

 

  • g.append("g"): با استفاده از append("g") یک گروه جدید (<g>) به گروه اصلی (g) اضافه می‌شود. این گروه جدید برای رسم محور افقی استفاده می‌شود.
  • attr("transform", "translate(0," + height + ")"): این خط گروه جدید را به پایین‌ترین موقعیت ممکن در محور افقی منتقل می‌کند. در اینجا، محور افقی در پایین نمودار واقع شده است، بنابراین باید گروه را به ارتفاع کامل نمودار منتقل کرد تا محور در پایین‌ترین نقطه قرار گیرد. translate(0, height) باعث جابجایی گروه به موقعیت عمودی height می‌شود.
  • call(d3.axisBottom(x): d3.axisBottom(x) یک محور افقی با مقیاس x ایجاد می‌کند. call این محور را به گروه جدید اضافه می‌کند.
  • tickFormat(d3.timeFormat("%Y")): tickFormat برای تنظیم فرمت برچسب‌های محور استفاده می‌شود. در اینجا، از d3.timeFormat("%Y") استفاده می‌شود تا فقط سال‌ها (فرمت YYYY) نمایش داده شوند.
  • ticks(d3.timeYear.every(1)): ticks تعداد و موقعیت برچسب‌ها را تنظیم می‌کند. در اینجا، d3.timeYear.every(1) باعث می‌شود که فقط برچسب‌های سالانه نمایش داده شوند. این تنظیم به جلوگیری از نمایش برچسب‌های اضافی و تکراری کمک می‌کند.
  • g.append("g"): مشابه محور افقی، این خط یک گروه جدید (<g>) به گروه اصلی اضافه می‌کند که برای رسم محور عمودی استفاده می‌شود.
  • call(d3.axisLeft(y)): d3.axisLeft(y) یک محور عمودی با مقیاس y ایجاد می‌کند. call این محور را به گروه جدید اضافه می‌کند. این محور عمودی مقادیر تورم را نمایش می‌دهد و به‌طور خودکار برچسب‌ها و خطوط مقیاس را تنظیم می‌کند.

 

رسم نمودار

 

نمودار محیطی (Area) با استفاده از d3.area() رسم می‌شود. این تابع به ما اجازه می‌دهد تا نواحی پر شده در زیر خط نمودار را نمایش دهیم. x(d => x(d.date)) برای تعیین موقعیت افقی هر نقطه و y0(height) و y1(d => y(d.inflation)) برای تعیین موقعیت عمودی هر نقطه استفاده می‌شود.

 

g.append("path")
.datum(Inflation)
.attr("fill", "steelblue")
.attr("fill-opacity", 0.6)
.attr("stroke", "none")
.attr("d", d3.area()
.x(d => x(d.date))
.y0(height)
.y1(d => y(d.inflation)))

 

  • g.append("path"): این خط به گروه (<g>) اصلی، یک مسیر جدید (<path>) اضافه می‌کند. این عنصر مسیر برای رسم نمودار Area استفاده می‌شود.
  • datum(Inflation): datum داده‌های ورودی برای عنصر مسیر را تنظیم می‌کند. با استفاده از datum(Inflation), داده‌های تورم به عنصر مسیر متصل می‌شود. این داده‌ها شامل تاریخ‌ها و مقادیر تورم هستند که برای رسم نمودار استفاده می‌شود.
  • attr("fill", "steelblue"): fill رنگ داخلی مسیر را تعیین می‌کند. در اینجا، رنگ steelblue برای پر کردن ناحیه زیر خط نمودار انتخاب شده است.
  • attr("fill-opacity", 0.6): fill-opacity میزان شفافیت رنگ پر شده را تنظیم می‌کند. مقدار 0.6 به معنای آن است که ناحیه زیر خط نمودار به میزان 60٪ شفاف خواهد بود.
  • attr("stroke", "none"): stroke ویژگی خط حاشیه را تنظیم می‌کند. در اینجا، مقدار "none" به معنای آن است که هیچ خط حاشیه‌ای برای مسیر وجود نخواهد داشت. این باعث می‌شود که تنها ناحیه پر شده نمایش داده شود و هیچ حاشیه‌ای دور آن نباشد.
  • attr("d", d3.area(): attr("d", d3.area()) ویژگی d برای مسیر را تنظیم می‌کند. d3.area() تابعی است که برای ایجاد ناحیه زیر خط نمودار استفاده می‌شود. این تابع نیاز به تنظیمات برای تعیین موقعیت نقاط داده‌ها و نواحی پر شده دارد.
  • x(d => x(d.date)): x(d => x(d.date)) تعیین می‌کند که چگونه موقعیت افقی نقاط داده‌ها باید محاسبه شود. x(d.date) هر تاریخ را به موقعیت افقی بر اساس مقیاس x تبدیل می‌کند.
  • y0(height): y0(height) تعیین می‌کند که پایه ناحیه نمودار در کجا شروع می‌شود. در اینجا، پایه ناحیه در پایین‌ترین نقطه نمودار، یعنی height قرار دارد.
  • y1(d => y(d.inflation)): y1(d => y(d.inflation)) تعیین می‌کند که موقعیت عمودی هر نقطه داده‌ها چگونه محاسبه می‌شود. y(d.inflation) هر مقدار تورم را به موقعیت عمودی بر اساس مقیاس y تبدیل می‌کند.

 

اضافه کردن برچسب‌ها به محورهای نمودار

 

برای افزودن برچسب‌ها به محورهای افقی و عمودی، از append("text") استفاده می‌کنیم. برای محور افقی، برچسب "سال" در وسط محور قرار می‌گیرد. برای محور عمودی، برچسب "درصد" به صورت عمودی و در وسط محور قرار می‌گیرد.

 

g.append("text")
.attr("transform", "translate(" + (width / 2) + " ," + (height + margin.bottom / 1.5) + ")")
.style("text-anchor", "middle")
.text("سال");
 
g.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left / 1.5)
.attr("x", 0 - (height / 2))
.style("text-anchor", "middle")
.text("درصد");

 

  •  g.append("text"): این خط یک عنصر متنی (<text>) به گروه (<g>) اصلی اضافه می‌کند. این عنصر برای نمایش برچسب محور افقی استفاده می‌شود.
  • attr("transform", "translate(" + (width / 2) + " ," + (height + margin.bottom / 1.5) + ")"): transform ویژگی برای موقعیت‌یابی عنصر متنی استفاده می‌شود. در اینجا، با استفاده از translate, برچسب به موقعیت افقی وسط نمودار (width / 2) و کمی پایین‌تر از محور افقی (height + margin.bottom / 1.5) منتقل می‌شود. این موقعیت‌یابی باعث می‌شود که برچسب در وسط محور افقی و در پایین نمودار قرار گیرد.
  • style("text-anchor", "middle"): text-anchor ویژگی‌ای است که نحوه‌ی چسباندن متن به موقعیت مشخص شده را تعیین می‌کند. در اینجا، middle باعث می‌شود که متن برچسب به طور مرکزی نسبت به موقعیت افقی خود قرار گیرد. این به معنای آن است که متن به طور کامل در وسط برچسب قرار می‌گیرد.
  • text("سال"): text محتوای متنی عنصر را تعیین می‌کند. در اینجا، برچسب محور افقی به زبان فارسی "سال" است.
  • g.append("text"): مشابه بخش قبلی، این خط یک عنصر متنی (<text>) به گروه (<g>) اصلی اضافه می‌کند. این عنصر برای نمایش برچسب محور عمودی استفاده می‌شود.
  • attr("transform", "rotate(-90)"): transform ویژگی برای چرخاندن عنصر متنی استفاده می‌شود. در اینجا، rotate(-90) باعث می‌شود که متن به میزان 90 درجه به سمت چپ چرخانده شود. این چرخش باعث می‌شود که برچسب عمودی به درستی در کنار محور عمودی نمایش داده شود.
  • attr("y", 0 - margin.left / 1.5): y موقعیت عمودی برچسب را تعیین می‌کند. در اینجا، برچسب به مقداری بالاتر از محور عمودی جابجا می‌شود تا در موقعیت مناسب قرار گیرد.
  • attr("x", 0 - (height / 2)): x موقعیت افقی برچسب را تعیین می‌کند. در اینجا، برچسب به میزان نیمی از ارتفاع نمودار به سمت چپ جابجا می‌شود تا در موقعیت مناسب نسبت به محور عمودی قرار گیرد.
  • style("text-anchor", "middle"): مشابه برچسب افقی، text-anchor ویژگی‌ای است که متن را نسبت به موقعیت افقی خود مرکزی می‌کند. این به معنای آن است که متن به طور کامل در وسط برچسب عمودی قرار می‌گیرد.
  • text("درصد"): text محتوای متنی عنصر را تعیین می‌کند. در اینجا، برچسب محور عمودی به زبان فارسی "درصد" است.