ایجاد نمودار هرم جمعیتی با استفاده از کتابخانه D3

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

 

برای نمایش داده‌های مربوط به جمعیت از داده‌های بخش جمعیت و مهاجرت درگاه آمار استفاده شده است.

 

کد:

 

var margin = ({top: 10, right: 0, bottom: 20, left: 0})
var height = 600
var width = 900
 
var svg = d3.create('svg')
.attr('width', width)
.attr('height', height);
 
var xM = d3.scaleLinear()
.domain([0, d3.max(populationPyramid, d => d.value)])
.rangeRound([width / 2, margin.left])
 
var xF = d3.scaleLinear()
.domain(xM.domain())
.rangeRound([width / 2, width - margin.right])
 
var y = d3.scaleBand()
.domain(populationPyramid.map(d => d.age))
.rangeRound([height - margin.bottom, margin.top])
.padding(0.1)
 
var xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(g => g.append("g").call(d3.axisBottom(xM).ticks(width / 80, "s")))
.call(g => g.append("g").call(d3.axisBottom(xF).ticks(width / 80, "s")))
.call(g => g.selectAll(".domain").remove())
.call(g => g.selectAll(".tick:first-of-type").remove())
 
var yAxis = g => g
.attr("transform", `translate(${xM(0)},0)`)
.call(d3.axisRight(y).tickSizeOuter(0))
.call(g => g.selectAll(".tick text").attr("fill", "white"))
 
svg.append("g")
.selectAll("rect")
.data(populationPyramid)
.join("rect")
.attr("fill", d => d3.schemeSet1[d.sex === "M" ? 1 : 0])
.attr("x", d => d.sex === "M" ? xM(d.value) : xF(0))
.attr("y", d => y(d.age))
.attr("width", d => d.sex === "M" ? xM(0) - xM(d.value) : xF(d.value) - xF(0))
.attr("height", y.bandwidth())
.append("title")
.text(d => d.value.toLocaleString());
 
svg.append("g")
.attr("fill", "white")
.selectAll("text")
.data(populationPyramid)
.join("text")
.attr("text-anchor", d => d.sex === "M" ? "start" : "end")
.attr("x", d => d.sex === "M" ? xM(d.value) + 4 : xF(d.value) - 4)
.attr("y", d => y(d.age) + y.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("font-size", "10px");
 
svg.append("text")
.attr("text-anchor", "end")
.attr("fill", "white")
.attr("dy", "0.35em")
.attr("x", xM(0) - 8)
.attr("y", y(populationPyramid[0].age) + y.bandwidth() / 2)
.text("Male");
 
svg.append("text")
.attr("text-anchor", "start")
.attr("fill", "white")
.attr("dy", "0.35em")
.attr("x", xF(0) + 34)
.attr("y", y(populationPyramid[0].age) + y.bandwidth() / 2)
.text("Female");
 
svg.append("g")
.call(xAxis);
 
svg.append("g")
.call(yAxis);
 
svg.node()

 

خروجی:

 

 

داده‌های جمعیت

 

داده‌های مورد استفاده در این مثال به صورت یک آرایه از اشیا است که هر شیء شامل سن، تعداد مردان و تعداد زنان در آن رده سنی می‌باشد. به عنوان مثال:

 

{
"age": "0-4",
"sex": "M",
"value": 2904000
},
{
"age": "0-4",
"sex": "F",
"value": 2777000
}

 

تعریف ابعاد نمودار و حاشیه‌ها

 

var margin = ({top: 10, right: 0, bottom: 20, left: 0})
var height = 600
var width = 900

 

ایجاد عنصر SVG

 

var svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

 

تعریف مقیاس‌های محور x

 

در این بخش از کد، دو مقیاس خطی (linear scales) برای محور x ایجاد می‌شود. یکی برای نمایش تعداد مردان (xM) و دیگری برای نمایش تعداد زنان (xF). این مقیاس‌ها به منظور نمایش صحیح داده‌ها در نمودار هرم جمعیتی استفاده می‌شوند.

 

var xM = d3.scaleLinear()
.domain([0, d3.max(populationPyramid, d => d.value)])
.rangeRound([width / 2, margin.left])
 
var xF = d3.scaleLinear()
.domain(xM.domain())
.rangeRound([width / 2, width - margin.right])

 

  • var xM = d3.scaleLinear() در این بخش از کد، دو مقیاس خطی (linear scales) برای محور x ایجاد می‌شود. یکی برای نمایش تعداد مردان (xM) و دیگری برای نمایش تعداد زنان (xF). این مقیاس‌ها به منظور نمایش صحیح داده‌ها در نمودار هرم جمعیتی استفاده می‌شوند.
  • domain([0, d3.max(populationPyramid, d => d.value)]) دامنه از 0 تا بزرگترین مقدار value در آرایه populationPyramid تنظیم می‌شود. تابع d3.max(populationPyramid, d => d.value) بزرگترین مقدار value را از داده‌ها استخراج می‌کند.
  • rangeRound([width / 2, margin.left]) برد مقیاس خطی را تعریف می‌کند. این برد محدوده پیکسلی است که داده‌ها به آن نگاشت می‌شوند. در اینجا، برد از وسط عرض (یعنی width / 2) تا سمت چپ نمودار (یعنی margin.left) تنظیم می‌شود. rangeRound مقادیر نگاشت شده را گرد می‌کند تا از دقت بالاتری برخوردار باشد.
  • var xF = d3.scaleLinear() مانند مقیاس مردان، d3.scaleLinear() یک مقیاس خطی برای زنان ایجاد می‌کند.
  • domain(xM.domain()) دامنه مقیاس زنان همان دامنه مقیاس مردان (xM) است. این تضمین می‌کند که هر دو مقیاس دامنه یکسانی داشته باشند.
  • rangeRound([width / 2, width - margin.right]) برد مقیاس زنان از وسط عرض (یعنی width / 2) تا سمت راست نمودار (یعنی width - margin.right) تنظیم می‌شود. این برد تضمین می‌کند که داده‌های زنان در سمت راست نمودار نمایش داده شوند.

 

تعریف مقیاس محور y

 

این بخش از کد مقیاس y را تعریف می‌کند که برای نگاشت سنین به موقعیت‌های عمودی در نمودار هرم جمعیتی استفاده می‌شود. از مقیاس باندی (band scale) استفاده می‌شود که برای دسته‌بندی داده‌ها به صورت باندهای جداگانه مناسب است.

 

var y = d3.scaleBand()
.domain(populationPyramid.map(d => d.age))
.rangeRound([height - margin.bottom, margin.top])
.padding(0.1)

 

  • d3.scaleBand() یک مقیاس باندی ایجاد می‌کند که برای نگاشت داده‌های دسته‌ای به باندهای مجزا مناسب است. این مقیاس برای نمایش گروه‌های سنی در محور y استفاده می‌شود.
  • domain(populationPyramid.map(d => d.age)) دامنه مقیاس باندی را تعریف می‌کند. دامنه شامل تمام گروه‌های سنی است که از داده‌های populationPyramid استخراج شده‌اند. populationPyramid.map(d => d.age) یک آرایه از تمامی سنین را برمی‌گرداند. هر سن به عنوان یک دسته مجزا در مقیاس باندی در نظر گرفته می‌شود.
  • rangeRound([height - margin.bottom, margin.top]) این برد محدوده پیکسلی است که داده‌های دسته‌ای به آن نگاشت می‌شوند. در اینجا، برد از height - margin.bottom (پایین‌ترین نقطه نمودار، کمی بالاتر از حاشیه پایینی) تا margin.top (بالا‌ترین نقطه نمودار، کمی پایین‌تر از حاشیه بالایی) تنظیم می‌شود. استفاده از rangeRound باعث می‌شود که مقادیر باندی به نزدیک‌ترین عدد صحیح گرد شوند تا از دقت بالاتری برخوردار باشند.
  • padding(0.1) فاصله (padding) بین باندهای مقیاس را تعریف می‌کند. مقدار 0.1 به این معناست که فاصله‌ای معادل 10% عرض هر باند بین باندها قرار داده شود. این فاصله‌ها باعث جدا شدن باندها و بهبود خوانایی نمودار می‌شوند.

 

تعریف محور x و y

 

این بخش از کد محور x و y را تعریف و به SVG اضافه می‌کند. در اینجا محور x برای هر دو جنسیت مرد و زن به طور مجزا تعریف و رسم می‌شود.

 

var xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(g => g.append("g").call(d3.axisBottom(xM).ticks(width / 80, "s")))
.call(g => g.append("g").call(d3.axisBottom(xF).ticks(width / 80, "s")))
.call(g => g.selectAll(".domain").remove())
.call(g => g.selectAll(".tick:first-of-type").remove())
 
var yAxis = g => g
.attr("transform", `translate(${xM(0)},0)`)
.call(d3.axisRight(y).tickSizeOuter(0))
.call(g => g.selectAll(".tick text").attr("fill", "white"))

 

  • var xAxis = g => g متغیر xAxis به عنوان یک تابع تعریف می‌شود که یک انتخاب (selection) D3 به نام g (گروه) را دریافت می‌کند و تنظیمات محور x را روی آن اعمال می‌کند.
  • attr("transform", `translate(0,${height - margin.bottom})`) این خط کد مکان محور x را به پایین نمودار منتقل می‌کند، به طوری که محور در امتداد خط افقی پایینی نمودار قرار گیرد.
  • call(g => g.append("g").call(d3.axisBottom(xM).ticks(width / 80, "s"))) این خط کد یک گروه (group) جدید به g اضافه می‌کند و محور x را برای داده‌های مردان رسم می‌کند. تابع d3.axisBottom(xM) محور x را براساس مقیاس xM (مقیاس خطی برای مردان) تعریف می‌کند. متد ticks(width / 80, "s") تعداد تیک‌ها (ticks) را تنظیم می‌کند و فرمت آن‌ها را به صورت مختصر (short format) نمایش می‌دهد.
  • call(g => g.append("g").call(d3.axisBottom(xF).ticks(width / 80, "s"))) مشابه خط قبلی، این خط کد محور x را برای داده‌های زنان رسم می‌کند با استفاده از مقیاس xF (مقیاس خطی برای زنان).
  • call(g => g.selectAll(".domain").remove()) این خط کد تمامی خطوط پایه محور (خطوطی که محور را از ابتدا تا انتها پوشش می‌دهند) را حذف می‌کند تا نمودار تمیزتر و ساده‌تر به نظر برسد.
  • var yAxis = g => g متغیر yAxis به عنوان یک تابع تعریف می‌شود که یک انتخاب (selection) D3 به نام g (گروه) را دریافت کرده و تنظیمات محور y را روی آن اعمال می‌کند.
  • attr("transform", `translate(${xM(0)},0)`) این خط کد محور y را به سمت راست نمودار منتقل می‌کند. xM(0) موقعیت x برای محور y را مشخص می‌کند، که در اینجا به معنی مکان محور y در نزدیکی محور x برای مردان است. این کار باعث می‌شود که محور y به سمت راست (سمت زنانه) جابجا شود.
  • call(d3.axisRight(y).tickSizeOuter(0)) این خط کد، محور y را در سمت راست نمودار رسم می‌کند. تابع d3.axisRight(y) برای رسم محور y به کار می‌رود و tickSizeOuter(0) اندازه تیک‌های خارجی را به صفر تنظیم می‌کند تا تیک‌های اضافی که از محور بیرون می‌زنند، نمایش داده نشوند.
  • call(g => g.selectAll(".tick text").attr("fill", "white")) این خط کد به انتخاب g اعمال می‌شود و به طور خاص به تمامی متن‌های تیک‌های محور y (که با کلاس .tick text مشخص می‌شوند) رنگ سفید (سفید) می‌دهد. این کار به منظور بهبود وضوح و قابلیت مشاهده متون تیک‌ها روی پس‌زمینه تاریک یا رنگی انجام می‌شود.

 

افزودن میله‌ها برای نمایش جمعیت هر رده سنی

 

در این بخش از کد، نمودار هرم جمعیت بر اساس داده‌هایی که در populationPyramid ذخیره شده است، تعریف و ایجاد می‌شود. زیرا این کد به ترتیب اقداماتی را برای ایجاد مستطیل‌ها (rectangles) که نماینده هر گروه جمعیتی هستند، انجام می‌دهد. حال به توضیح هر بخش می‌پردازیم:

 

svg.append("g")
.selectAll("rect")
.data(populationPyramid)
.join("rect")
.attr("fill", d => d3.schemeSet1[d.sex === "M" ? 1 : 0])
.attr("x", d => d.sex === "M" ? xM(d.value) : xF(0))
.attr("y", d => y(d.age))
.attr("width", d => d.sex === "M" ? xM(0) - xM(d.value) : xF(d.value) - xF(0))
.attr("height", y.bandwidth())
.append("title")
.text(d => d.value.toLocaleString());

 

  • ابتدا یک g به SVG اضافه می‌شود. این گروه به عنوان ظرفیتی برای نگهداری مستطیل‌هایی (rectangles) که بر اساس داده‌های populationPyramid ساخته خواهند شد، عمل می‌کند.
  • join("rect") نشان می‌دهد که یک مستطیل برای هر عضو داده‌ها باید ایجاد شود.
  • attr("fill", d => d3.schemeSet1[d.sex === "M" ? 1 : 0])، به مستطیل‌ها رنگی (بر اساس جنسیت) اختصاص می‌دهد. اگر جنسیت M باشد، از رنگ دوم (d3.schemeSet1[1]) استفاده می‌شود و در غیر این صورت، از رنگ اول (d3.schemeSet1[0]) استفاده می‌شود.
  • attr("x", d => d.sex === "M" ? xM(d.value) : xF(0)): تعیین موقعیت افقی (x) مستطیل بر اساس جنسیت. اگر جنسیت M باشد، مستطیل از موقعیت xM(d.value) شروع می‌شود و در غیر این صورت از xF(0) شروع می‌شود.
  • attr("y", d => y(d.age)): تعیین موقعیت عمودی (y) مستطیل بر اساس سن افراد.
  • attr("width", d => d.sex === "M" ? xM(0) - xM(d.value) : xF(d.value) - xF(0)): تعیین عرض مستطیل بر اساس جنسیت. اگر جنسیت M باشد، عرض مستطیل برابر با xM(0) منهای xM(d.value) می‌شود و در غیر این صورت، عرض مستطیل برابر با xF(d.value) منهای xF(0) می‌شود.
  • attr("height", y.bandwidth()): تعیین ارتفاع ثابت برای تمام مستطیل‌ها که توسط y.bandwidth() مشخص می‌شود.
  • append("title") یک عنصر title به هر مستطیل اضافه می‌کند و متن عنوان را با استفاده از مقدار value از داده‌ها (با استفاده از toLocaleString() برای قالب‌بندی) تنظیم می‌کند.

 

افزودن برچسب‌های محوری برای نمایش "Male" و "Female"

 

svg.append("text")
.attr("text-anchor", "end")
.attr("fill", "white")
.attr("dy", "0.35em")
.attr("x", xM(0) - 8)
.attr("y", y(populationPyramid[0].age) + y.bandwidth() / 2)
.text("Male");
 
svg.append("text")
.attr("text-anchor", "start")
.attr("fill", "white")
.attr("dy", "0.35em")
.attr("x", xF(0) + 34)
.attr("y", y(populationPyramid[0].age) + y.bandwidth() / 2)
.text("Female");

 

  • svg.append("text") این خط یک عنصر text به SVG اضافه می‌کند. این عنصر به عنوان برچسبی برای نشان دادن بخش مردان در هرم جمعیت استفاده خواهد شد.
  • attr("text-anchor", "end") text-anchor به "end" تنظیم شده است، به این معنی که متن از انتهای آن (سمت راست) تراز می‌شود. این باعث می‌شود که متن "Male" از موقعیت مشخص شده به سمت چپ کشیده شود و انتهای آن در موقعیت داده شده قرار گیرد.
  • attr("fill", "white") رنگ متن به "white" تنظیم شده است، که باعث می‌شود متن به رنگ سفید نمایش داده شود. این رنگ به خوبی با پس‌زمینه تیره‌تر (یا رنگ‌های دیگر) کنتراست دارد.
  • attr("dy", "0.35em") dy به "0.35em" تنظیم شده است، که موقعیت عمودی متن را تغییر می‌دهد. این به طور خاص برای جابه‌جایی متن به طور عمودی به مرکز بافتی مستطیل‌های نمودار استفاده می‌شود.
  • attr("x", xM(0) - 8) موقعیت افقی متن با استفاده از xM(0) - 8 تنظیم می‌شود. xM(0) موقعیت افقی برای مقدار صفر (محور مرکزی هرم) است، و -8 برای فاصله دادن متن از محور x استفاده شده است. این به معنای آن است که متن "Male" 8 واحد به سمت چپ از مرکز نمودار قرار می‌گیرد.
  • attr("y", y(populationPyramid[0].age) + y.bandwidth() / 2) موقعیت عمودی متن به صورت y(populationPyramid[0].age) + y.bandwidth() / 2 تنظیم می‌شود. y(populationPyramid[0].age) موقعیت عمودی برای اولین گروه سنی داده شده است (معمولاً جوان‌ترین گروه)، و y.bandwidth() / 2 برای موقعیت وسط مستطیل‌های نمودار استفاده می‌شود. این باعث می‌شود که متن در وسط مستطیل‌ها قرار گیرد.
  • text("Male") در نهایت، متنی که باید نمایش داده شود، "Male"، به عنصر text افزوده می‌شود.
  • attr("text-anchor", "start") text-anchor به "start" تنظیم شده است، به این معنی که متن از ابتدای آن (سمت چپ) تراز می‌شود. این باعث می‌شود که متن "Female" از موقعیت مشخص شده به سمت راست کشیده شود و ابتدای آن در موقعیت داده شده قرار گیرد.
  • attr("x", xF(0) + 34) موقعیت افقی متن با استفاده از xF(0) + 34 تنظیم می‌شود. xF(0) موقعیت افقی برای مقدار صفر (محور مرکزی هرم) است و +34 برای فاصله دادن متن از محور x به سمت راست استفاده شده است. این به معنای آن است که متن "Female" 34 واحد به سمت راست از مرکز نمودار قرار می‌گیرد.
  • attr("y", y(populationPyramid[0].age) + y.bandwidth() / 2) موقعیت عمودی متن به صورت y(populationPyramid[0].age) + y.bandwidth() / 2 تنظیم می‌شود. y(populationPyramid[0].age) موقعیت عمودی برای اولین گروه سنی داده شده است (معمولاً جوان‌ترین گروه)، و y.bandwidth() / 2 برای موقعیت وسط مستطیل‌های نمودار استفاده می‌شود. این باعث می‌شود که متن در وسط مستطیل‌ها قرار گیرد.
  • text("Female") در نهایت، متنی که باید نمایش داده شود، "Female"، به عنصر text افزوده می‌شود.

 

افزودن محورهای x و y به نمودار

 

svg.append("g")
.call(xAxis);
 
svg.append("g")
.call(yAxis);

 

  • svg.append("g") یک عنصر g به SVG اضافه می‌شود. عنصر g به طور معمول برای گروه‌بندی و سازماندهی دیگر عناصر SVG استفاده می‌شود. در اینجا برای اضافه کردن محور x استفاده شده است.
  • call(xAxis) تابع call برای اعمال تنظیمات و اضافه کردن عناصر مربوط به محور x استفاده می‌شود. xAxis، که قبلاً تعریف شده بود، شامل تنظیمات و فرمت‌بندی‌های محور x است، مانند اضافه کردن مقیاس، برچسب‌ها و خطوط محوری.
  • svg.append("g") مشابه خط قبلی، یک عنصر g به SVG اضافه می‌شود. این عنصر g جدید برای محور y استفاده می‌شود.
  • call(yAxis) تابع call برای اعمال تنظیمات و اضافه کردن عناصر مربوط به محور y استفاده می‌شود. yAxis، که قبلاً تعریف شده بود، شامل تنظیمات و فرمت‌بندی‌های محور y است، مانند اضافه کردن مقیاس، برچسب‌ها و خطوط محوری

 

با استفاده از این کد، می‌توانید یک هرم جمعیتی زیبا و کارآمد برای نمایش توزیع سنی و جنسیتی جمعیت ایران ایجاد کنید. این نمودار می‌تواند در تحلیل‌های جمعیتی و برنامه‌ریزی‌های مرتبط بسیار مفید باشد.