نمودارهای پشتهای یکی از ابزارهای قدرتمند برای مصورسازی دادهها به شکلی هستند که نه تنها مقادیر مطلق، بلکه روابط نسبی بین دستههای مختلف دادهها را نمایش میدهند. در این مقاله، قصد داریم با استفاده از کتابخانه D3.js یک نمودار پشتهای بسازیم که توزیع جمعیت، واجدین شرایط و شرکتکنندگان در دورههای مختلف انتخابات ریاست جمهوری ایران را نمایش میدهد.
دادههای استفاده شده در این مقاله از ویکیپدیا فارسی جمعآوری شده است. ساختار فایل داده بارگذاری شده در داده نگار به صورت زیر است:
کد کامل نمودار:
خروجی:
تعریف ابعاد و حاشیهها
در ابتدا، ابعاد نمودار و حاشیهها تعیین میکنیم. حاشیهها از بخشهای بالایی، راست، پایین و چپ مقداردهی میشوند تا فضای کافی برای محورها و توضیحات ایجاد شود.
ایجاد عنصر SVG
عنصر SVG ایجاد شده و اندازه آن با استفاده از عرض و ارتفاع تعیین شده به علاوه حاشیهها تنظیم میشود.
- var svg = d3.create("svg") با استفاده از متد
d3.create
، یک عنصر SVG جدید ایجاد میشود. - attr("width", width + margin.left + margin.right) عرض SVG برابر با عرض نمودار به اضافه حاشیههای سمت چپ و راست است.
- attr("height", height + margin.top + margin.bottom) ارتفاع SVG برابر با ارتفاع نمودار به اضافه حاشیههای بالا و پایین است.
- var g = svg.append("g") یک عنصر گروهی (
<g>
) به عنصر SVG اضافه میشود - attr("transform", "translate(" + margin.left + "," + margin.top + ")") با استفاده از ویژگی
transform
، مکان گروه در داخل عنصر SVG تنظیم میشود. این ویژگی باعث میشود که تمام عناصر داخل گروه، از جمله نمودار، به اندازه مشخص شده توسطmargin.left
وmargin.top
از گوشه بالا و چپ SVG فاصله بگیرند.
مقیاسبندی دادهها
برای تعیین نحوه نمایش دادهها، از مقیاسهای scaleBand
و scaleLinear
استفاده میشود. scaleBand
برای مقیاسبندی محور افقی (X) به کار میرود که دورههای انتخابات را نمایش میدهد، در حالی که scaleLinear
برای محور عمودی (Y) استفاده میشود تا جمعیتها را به نسبت مناسب نمایش دهد.
d3.scaleBand(): این تابع یک مقیاس دستهای (band scale) ایجاد میکند که برای مقادیر دستهبندی شده (غیرعددی) استفاده میشود، مانند نام دورههای انتخابات در این مثال. این نوع مقیاس به خوبی برای نمودارهای میلهای یا ستوندار مناسب است.
domain(): این تابع دامنه مقادیر ورودی را تعیین میکند. در اینجا، دامنه برابر با یک آرایه از نامهای دورههای انتخابات ریاست جمهوری ایران است که با استفاده از iranianPresidentialElection.map(d => d.election) ایجاد شده است. این تابع، برای هر دوره از انتخابات یک موقعیت روی محور x تخصیص میدهد.
range([0, width]): این تابع محدودهی خروجی مقیاس را تعیین میکند، که در اینجا از 0 تا عرض (width) نمودار است. به عبارت دیگر، موقعیتهای دورههای انتخاباتی بر روی محور x از ابتدای نمودار تا انتهای آن توزیع میشوند.
padding(0.3): این تابع فاصله بین میلهها را تنظیم میکند. مقدار 0.3 به این معنی است که 30 درصد از عرض هر بند برای فضای خالی (padding) بین میلهها اختصاص داده میشود.
d3.scaleLinear(): این تابع یک مقیاس خطی ایجاد میکند که برای دادههای عددی پیوسته مناسب است. این مقیاس، مقادیر عددی را به مقادیر گرافیکی در محور y تبدیل میکند.
domain([0, d3.max(iranianPresidentialElection, d => d.population) * 1.1]): این تابع دامنهی مقادیر ورودی را تنظیم میکند. 0 به عنوان مقدار شروع دامنه انتخاب شده است.
d3.max(iranianPresidentialElection, d => d.population) * 1.1 حداکثر مقدار جمعیت در دادهها را محاسبه کرده و سپس آن را ۱۰ درصد افزایش میدهد تا کمی فضای خالی بالای نمودار باقی بماند.
range([height, 0]): این تابع محدوده خروجی مقیاس را تعیین میکند. در اینجا، دامنه مقیاس از height (پایین نمودار) تا 0 (بالای نمودار) تنظیم شده است. به عبارت دیگر، پایینترین مقدار y در پایین نمودار قرار میگیرد و بالاترین مقدار در بالای نمودار.
رنگبندی و مرتبسازی دادهها
این بخش از کد، ابتدا یک مقیاس رنگ برای دستههای مختلف دادهها تعریف میکند، سپس دادهها را بر اساس دستههای تعریف شده (شرکتکننده، واجدین شرایط، جمعیت کل) به صورت پشتهای سازماندهی میکند. این مقیاس رنگی و دادههای پشتهای شده برای رسم نمودار پشتهای استفاده میشوند که به وضوح روابط نسبی بین دستههای مختلف دادهها را نمایش میدهد.
- d3.scaleOrdinal(): این تابع یک مقیاس ترتیبی (ordinal scale) ایجاد میکند که برای دستههای غیرعددی استفاده میشود. این نوع مقیاس معمولاً برای تخصیص رنگها یا سایر خصوصیات بصری به دستههای مختلف دادهها به کار میرود.
- domain(["participant", "eligible", "population"]): این تابع دامنه مقادیر ورودی را تعیین میکند. در اینجا، دامنه شامل سه دستهی داده است: "participant" (شرکتکننده)، "eligible" (واجدین شرایط) و "population" (جمعیت کل). هر یک از این دستهها به یک رنگ خاص تخصیص داده میشود.
- range(["#4daf4a", "#377eb8", "#ff7f00"]): این تابع محدودهی خروجی مقیاس را مشخص میکند که در اینجا سه رنگ به ترتیب سبز (#4daf4a)، آبی (#377eb8) و نارنجی (#ff7f00) است. به این ترتیب، هر دسته داده به یکی از این رنگها اختصاص داده میشود.
- d3.stack(): این تابع یک سازنده (constructor) برای ایجاد پشتههای داده (stack) است. در نمودار پشتهای، دادهها به صورت لایههای روی هم انباشته میشوند. هر لایه (layer) مربوط به یک دسته از دادهها است.
- keys(["participant", "eligible", "population"]): این تابع کلیدهایی را که برای ساخت پشتهها استفاده میشوند، مشخص میکند. این کلیدها، دستههای مختلف دادهها هستند: "participant" (شرکتکننده)، "eligible" (واجدین شرایط) و "population" (جمعیت کل). با استفاده از این کلیدها، دادههای هر دسته جداگانه در پشتههای مربوطه قرار میگیرند.
- stack(iranianPresidentialElection): در این خط، تابع stack به دادههای iranianPresidentialElection اعمال میشود. این دادهها شامل اطلاعاتی درباره انتخاباتهای ریاست جمهوری ایران هستند.
- layers: خروجی این عملیات یک آرایه از لایهها (layers) است که هر لایه شامل دستههای مختلف دادههای پشتهای شده (stacked) است. هر لایه نمایانگر یک سری از دادههاست که به طور عمودی روی هم قرار گرفتهاند.
رسم نمودار
دادهها با استفاده از d3.stack
روی هم پشته میشوند و سپس به هر دسته یک مستطیل رنگی اختصاص داده میشود که با استفاده از تابع color
تعیین شده است. هر مستطیل نمایانگر یکی از دستهها (شرکتکنندگان، واجدین شرایط و جمعیت) است.
- g.selectAll(".bar"): این خط همهی عناصر با کلاس bar را انتخاب میکند (هرچند در ابتدای کار هنوز چنین عناصری وجود ندارد).
- data(iranianPresidentialElection): دادههای مربوط به انتخابات ریاست جمهوری ایران (iranianPresidentialElection) به انتخابگر متصل میشود. این دادهها برای رسم مستطیلها استفاده خواهد شد.
- enter().append("g"): برای هر ورودی دادهای که در iranianPresidentialElection وجود دارد اما هنوز عنصر گرافیکی مرتبط با آن ایجاد نشده است، یک گروه (<g>) جدید ایجاد میشود. این گروهها به عنوان کانتینر برای مستطیلهای هر دسته از دادهها عمل میکنند.
- attr("transform", d => translate(${x(d.election)},0)): این خط، گروه را به موقعیت افقی متناظر با هر دورهی انتخاباتی بر روی محور x منتقل میکند. تابع x(d.election) مکان مربوط به هر دورهی انتخاباتی را محاسبه میکند و سپس گروه را به آن نقطه در محور افقی منتقل میکند. محور عمودی (y) تغییری ندارد، بنابراین مقدار آن صفر است.
- each(function(d) { ... }): این بخش یک تابع برای هر گروه دادهای اجرا میکند. در این تابع، سه مستطیل برای هر گروه دادهای رسم میشود که نمایانگر سه دستهی مختلف (شرکتکننده، واجدین شرایط، جمعیت) هستند.
- d3.select(this).append("rect"): یک مستطیل (rect) جدید به گروه فعلی (this) اضافه میکند.
- attr("y", y(d.participant)): موقعیت عمودی (y) مستطیل را تعیین میکند. این مقدار بر اساس تعداد شرکتکنندگان (d.participant) و مقیاس y محاسبه میشود، که تعیین میکند مستطیل از کجا شروع شود.
- attr("height", height - y(d.participant)): ارتفاع مستطیل را بر اساس تعداد شرکتکنندگان محاسبه میکند. اختلاف بین ارتفاع نمودار و موقعیت y تعداد شرکتکنندگان، ارتفاع مستطیل را مشخص میکند.
- attr("width", x.bandwidth()): عرض مستطیل را تنظیم میکند که بر اساس پهنای باند x، یعنی عرض دستهبندی، تعیین میشود.
- attr("fill", color("participant")): رنگ مستطیل را بر اساس مقیاس رنگ color تعیین میکند که برای شرکتکنندگان (participant) به رنگ مشخصی تنظیم شده است.
- بخشهای مشابه برای رسم مستطیلهای دیگر (واجدین شرایط و جمعیت) با تغییر در ویژگیهای y, height و fill، هر دسته از دادهها را به صورت پشتهای روی هم رسم میکنند.
افزودن محورها
این بخش از کد مسئولیت رسم محورها (axes) در نمودار را بر عهده دارد. به طور مشخص، دو محور افقی (bottom axis) و عمودی (left axis) به نمودار اضافه میشوند که دادهها را در محورهای x
و y
نمایش میدهند.
- g.append("g"): این خط یک گروه جدید (<g>) به عنصر g اضافه میکند. این گروه، برای محور افقی (x axis) در نظر گرفته شده است.
- attr("transform", "translate(0," + height + ")"): این خط، گروه محوری (g) را به پایین نمودار منتقل میکند.
- translate(0, height): به این معنی است که گروه به مقدار height در جهت عمودی (y) منتقل میشود، که باعث میشود محور x در پایینترین نقطه نمودار قرار گیرد.
- call(d3.axisBottom(x)): این خط، محور x را به گروه اضافه میکند.
- d3.axisBottom(x): یک محور در پایین (bottom axis) بر اساس مقیاس x ایجاد میکند. این محور نشاندهندهی دورههای انتخاباتی است که به صورت افقی در پایین نمودار نمایش داده میشود.
- g.append("g"): یک گروه جدید (<g>) برای محور عمودی (y axis) اضافه میکند.
- call(d3.axisLeft(y)): این خط، محور y را به گروه اضافه میکند.
- d3.axisLeft(y): یک محور در سمت چپ (left axis) بر اساس مقیاس y ایجاد میکند. این محور نشاندهندهی مقادیر عددی مانند جمعیت، تعداد شرکتکنندگان و واجدین شرایط است که به صورت عمودی در سمت چپ نمودار نمایش داده میشود.
افزودن برچسبهای محورها
این بخش از کد مسئولیت اضافه کردن برچسبهای (labels) متنی به نمودار را بر عهده دارد. این برچسبها به عنوان توضیحات محورها استفاده میشوند و به کاربر کمک میکنند تا متوجه شوند که محورها چه چیزی را نمایش میدهند.
- g.append("text"): این خط یک عنصر متنی (<text>) به گروه g اضافه میکند. این متن قرار است به عنوان برچسب محور x استفاده شود.
- attr("x", width / 2): این خط موقعیت افقی (x) متن را در وسط عرض نمودار قرار میدهد.
- width / 2: موقعیت متن در نیمهی عرض نمودار تنظیم میشود، به طوری که برچسب محور دقیقاً در مرکز محور x قرار گیرد.
- attr("y", height + margin.bottom - 10): این خط موقعیت عمودی (y) متن را تنظیم میکند.
- height + margin.bottom - 10: این مقدار، متن را دقیقاً زیر محور x و نزدیک به حاشیه پایین نمودار قرار میدهد.
- attr("text-anchor", "middle"): این خط باعث میشود که متن به صورت مرکزی (وسطچین) نسبت به موقعیت افقی مشخص شده تنظیم شود.
- style("font-size", "14px") و .style("font-weight", "bold"): این خطها اندازه فونت (14 پیکسل) و وزن (ضخامت) فونت (bold) متن را تنظیم میکنند.
- text("دورههای انتخابات"): این خط متن واقعی را به عنصر text اضافه میکند. در اینجا، متن "دورههای انتخابات" به عنوان برچسب محور افقی نمایش داده میشود.
- g.append("text"): یک عنصر متنی (<text>) جدید برای برچسب محور y ایجاد میکند.
- attr("x", -height / 2): موقعیت افقی (x) متن را تنظیم میکند. این مقدار به این دلیل منفی است که قرار است برچسب عمودی (عمود بر محور y) نمایش داده شود و به همین دلیل محور x آن با توجه به ارتفاع تنظیم میشود.
- -height / 2: متن را در وسط ارتفاع نمودار (بعد از چرخش 90 درجه) قرار میدهد.
- attr("y", -margin.left + 20): موقعیت عمودی (y) متن را تنظیم میکند. این مقدار متن را نزدیک به لبه چپ نمودار قرار میدهد.
- attr("text-anchor", "middle"): مشابه برچسب محور x، این خط متن را به صورت مرکزی نسبت به موقعیت عمودی مشخص شده تنظیم میکند.
- style("font-size", "14px") و .style("font-weight", "bold"): اندازه و وزن فونت برچسب را تنظیم میکند تا خوانایی بهتری داشته باشد.
- attr("transform", "rotate(-90)"): این خط متن را 90 درجه خلاف جهت عقربههای ساعت میچرخاند. این چرخش باعث میشود که برچسب عمودی در کنار محور y قرار بگیرد.
- text("جمعیت"): متن "جمعیت" را به عنوان برچسب محور عمودی نمایش میدهد.
افزودن راهنما
بخش آخر کد مربوط به ایجاد یک راهنما یا legend در نمودار است که توضیح میدهد هر رنگ به کدام دسته از دادهها تعلق دارد.
- var legendText: این خط یک شیء (object) به نام legendText ایجاد میکند که شامل کلیدهایی برای هر دسته از دادهها (شرکتکننده، واجدین شرایط، جمعیت) است. مقدار هر کلید متن فارسی مربوط به آن دسته از دادهها است که در راهنما نمایش داده میشود.
- g.selectAll(".legend"): این خط تلاش میکند تا تمام عناصر با کلاس legend را انتخاب کند. اگر چنین عناصری وجود نداشته باشند، یک گروه جدید برای هر عنصر داده ایجاد خواهد شد.
- data(color.domain()): این خط دادهها را به عناصر راهنما متصل میکند. color.domain() مقادیر (کلیدها) مربوط به هر دسته داده را برمیگرداند که در اینجا شامل participant, eligible, و population است.
- enter().append("g"): برای هر ورودی دادهای که در color.domain() وجود دارد اما هنوز گروهی برای آن ایجاد نشده است، یک گروه جدید (<g>) ایجاد میکند.
- attr("class", "legend"): این خط به گروه جدید کلاس legend میدهد تا بتوان آن را به راحتی شناسایی و سبکدهی کرد.
- attr("transform", (d, i) => "translate(0," + i * 20 + ")"): این خط موقعیت هر گروه راهنما را تنظیم میکند.
- (d, i) پارامترهای مربوط به داده و اندیس هستند. i * 20 هر گروه را به اندازهی 20 پیکسل از گروه قبلی پایینتر قرار میدهد تا عناصر راهنما به صورت عمودی مرتب شوند.
- legend.append("rect"): این خط یک عنصر rect (مستطیل) به هر گروه راهنما اضافه میکند. این مستطیلها نشاندهنده رنگ هر دسته داده در نمودار هستند.
- attr("x", width + 45): موقعیت افقی مستطیل را تنظیم میکند تا در کنار نمودار، در فضای مخصوص راهنما قرار بگیرد.
- attr("width", 12) و .attr("height", 12): ابعاد مستطیل را تعیین میکند (عرض و ارتفاع 12 پیکسل).
- style("fill", color): رنگ مستطیل را بر اساس مقیاس رنگ color تنظیم میکند. هر مستطیل رنگ مربوط به دسته داده خود را خواهد داشت.
- legend.append("text"): این خط یک عنصر متنی (<text>) به هر گروه راهنما اضافه میکند.
- attr("x", width + 40): موقعیت افقی متن را تنظیم میکند. این موقعیت به گونهای است که متن در کنار مستطیل رنگی قرار میگیرد.
- attr("y", 6): موقعیت عمودی متن را تنظیم میکند تا در وسط مستطیل رنگی قرار گیرد.
- attr("dy", ".35em"): این مقدار عمودی متن را تنظیم میکند تا در راستای میانی (vertical alignment) قرار گیرد.
- style("text-anchor", "end"): این ویژگی باعث میشود که متن در سمت راست مستطیل رنگی قرار بگیرد و راستچین شود.
- text(d => legendText[d]): این خط متن مربوط به هر دسته داده را از شیء legendText میگیرد و در عنصر متنی نمایش میدهد.
- style("font-size", "11px"): اندازه فونت متن راهنما را تنظیم میکند تا به خوبی قابل خواندن باشد.