كيفية إنشاء مخطط أعمدة متباعدة باستخدام JavaScript خطوة بخطوة

دقائق القراءة: 10

مقدمة إلى مخطط الأعمدة المتباعدة باستخدام JavaScript

يُعد مخطط الأعمدة المتباعدة من الأساليب البصرية الفعّالة عند الحاجة إلى مقارنة قيمتين أو أكثر انطلاقاً من خط أساس مركزي. وبدلاً من عرض جميع القيم في اتجاه واحد، يتم توزيع البيانات إلى اليمين واليسار أو إلى الأعلى والأسفل، مما يجعل الفروق أكثر وضوحاً وسهولة في القراءة.

في هذا الشرح العملي، سنتعلّم كيفية بناء مخطط أعمدة متباعدة تفاعلي باستخدام JavaScript ومكتبة AnyChart، بهدف عرض سجل الفوز والخسارة لفريق LA Lakers خلال حقبة اللاعب الأسطوري Kobe Bryant الممتدة على مدار 20 عاماً.

مخطط أعمدة متباعدة تفاعلي باستخدام جافاسكربت لعرض بيانات رياضية

هذا النوع من التصور البياني مناسب جداً عندما تريد إبراز التوازن أو التباين بين حالتين مثل:

  • الربح والخسارة
  • الزيادة والنقصان
  • الآراء الإيجابية والسلبية
  • القيم قبل وبعد التغيير

ما هو مخطط الأعمدة المتباعدة؟

مخطط الأعمدة المتباعدة هو تمثيل بصري يعرض قياسين أو أكثر انطلاقاً من نقطة وسطية مشتركة. يتمدد أحد القياسات في اتجاه، بينما يتمدد الآخر في الاتجاه المعاكس. وتكمن فائدته الأساسية في تسهيل المقارنة بين الفئات المختلفة من خلال نقطة مرجعية واضحة.

في هذا المثال، سنستخدم هذا الأسلوب لإظهار عدد مرات الفوز وعدد مرات الخسارة لكل موسم من مواسم فريق LA Lakers خلال مسيرة Kobe Bryant.

معاينة لمخطط أعمدة متباعدة تفاعلي يعرض الفوز والخسارة عبر المواسم

لماذا نستخدم مكتبة AnyChart؟

هناك العديد من مكتبات الرسوم البيانية في عالم JavaScript، لكن مكتبة AnyChart تُعد خياراً مناسباً لهذا المثال لعدة أسباب:

  • توفر دعماً مباشراً لرسوم rangeBar دون الحاجة إلى حلول ملتوية.
  • سهولة البدء بها حتى للمبتدئين.
  • توفر أمثلة جاهزة ووثائق تقنية مفصلة.
  • مرونة عالية في التخصيص من حيث الألوان، المحاور، والتلميحات التفاعلية.

بشكل عام، يمكن تلخيص عملية إنشاء هذا المخطط في أربع خطوات رئيسية:

  1. إنشاء صفحة HTML أساسية.
  2. إضافة ملفات مكتبة الرسم البياني.
  3. إدخال البيانات.
  4. كتابة كود JavaScript المسؤول عن إنشاء المخطط وتخصيصه.

الخطوة الأولى: إنشاء صفحة HTML أساسية

نبدأ بإنشاء هيكل بسيط للصفحة، مع عنصر <div> ليكون حاوية للمخطط. سنمنحه المعرّف container حتى نستخدمه لاحقاً داخل الكود.

<html>
<head>
  <title>JavaScript Diverging Bar Chart</title>
  <style type="text/css">
    html, body, #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>
<body>
  <div id="container"></div>
</body>
</html>

يمكنك تعديل خاصيتي العرض والارتفاع داخل الوسم <style> بما يتناسب مع تصميم صفحتك. في هذا المثال، جرى تعيين القيمتين إلى 100% حتى يملأ المخطط كامل المساحة المتاحة.

الخطوة الثانية: تضمين ملفات JavaScript الضرورية

بعد تجهيز الصفحة، نضيف ملف المكتبة من خلال شبكة توزيع المحتوى CDN. وبما أننا نستخدم AnyChart، فسنضيف ملف الوحدة الأساسية داخل قسم <head>.

<html>
<head>
  <title>JavaScript Diverging Bar Chart</title>
  <script src="https://cdn.anychart.com/releases/8.9.0/js/anychart-base.min.js" type="text/javascript"></script>
  <style type="text/css">
    html, body, #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <script>
    // All the code for the JS diverging bar chart will come here
  </script>
</body>
</html>

إذا كنت تفضّل العمل محلياً، يمكنك تنزيل ملفات المكتبة وربطها من مشروعك مباشرة بدلاً من استخدام CDN.

الخطوة الثالثة: إدخال البيانات

الآن نضيف البيانات التي تمثل عدد مرات الفوز والخسارة لكل موسم. في هذا المثال، تم بناء مصفوفة تحتوي على عدد الخسائر، وعدد الانتصارات، واسم الموسم.

var winlossData = [
  [65, 17, "2015-16"],
  [61, 21, "2014-15"],
  [55, 27, "2013-14"],
  [37, 45, "2012-13"],
  [25, 41, "2011-12"],
  [25, 57, "2010-11"],
  [25, 57, "2009-10"],
  [17, 65, "2008-09"],
  [25, 57, "2007-08"],
  [40, 42, "2006-07"],
  [37, 45, "2005-06"],
  [48, 34, "2004-05"],
  [26, 56, "2003-04"],
  [32, 50, "2002-03"],
  [24, 58, "2001-02"],
  [26, 56, "2000-01"],
  [15, 67, "1999-00"],
  [19, 31, "1998-99"],
  [21, 61, "1997-98"],
  [26, 56, "1996-97"]
];

بما أن حجم البيانات محدود، يمكن إدخالها مباشرة داخل الكود. أما إذا كنت تعمل على بيانات أكبر، فمن الأفضل جلبها من ملف JSON أو واجهة API.

الخطوة الرابعة: كتابة كود JavaScript لإنشاء المخطط

قبل أي شيء، من الأفضل وضع الكود داخل الدالة anychart.onDocumentReady() لضمان تنفيذ الرسم بعد اكتمال تحميل الصفحة.

<script>
anychart.onDocumentReady(function () {
  // The place for the JS diverging bar chart code
});
</script>

إنشاء المخطط وتهيئة البيانات

في البداية ننشئ مخططاً من نوع bar، ثم نُعرّف البيانات، وبعدها نبني دالة تساعدنا على إنشاء كل سلسلة بيانات بشكل مستقل.

// create a bar chart
var chart = anychart.bar();

// data
var winlossData = [
  [65, 17, "2015-16"],
  [61, 21, "2014-15"],
  [55, 27, "2013-14"],
  [37, 45, "2012-13"],
  [25, 41, "2011-12"],
  [25, 57, "2010-11"],
  [25, 57, "2009-10"],
  [17, 65, "2008-09"],
  [25, 57, "2007-08"],
  [40, 42, "2006-07"],
  [37, 45, "2005-06"],
  [48, 34, "2004-05"],
  [26, 56, "2003-04"],
  [32, 50, "2002-03"],
  [24, 58, "2001-02"],
  [26, 56, "2000-01"],
  [15, 67, "1999-00"],
  [19, 31, "1998-99"],
  [21, 61, "1997-98"],
  [26, 56, "1996-97"]
];

إنشاء السلاسل البيانية للفوز والخسارة

سنكتب دالة باسم createSeries تستقبل رقم العمود والاسم. الفكرة هنا هي دفع قيم الفوز إلى جهة، وقيم الخسارة إلى الجهة المقابلة، بحيث نحصل على تأثير التباعد حول الصفر.

var createSeries = function (columnNumber, name) {
  var data = [];
  for (var i = 0; i < winlossData.length; i++) {
    var value = winlossData[i][columnNumber];
    var center = 0;

    if (name === "Wins") {
      data.push({
        x: winlossData[i][2],
        low: center,
        high: center + value,
        value: value
      });
    } else {
      data.push({
        x: winlossData[i][2],
        low: -center,
        high: -center - value,
        value: value
      });
    }
  }

  var series = chart.rangeBar(data);
  series.name(name);
};

بعد تعريف الدالة، ننشئ سلسلتين: الأولى للخسائر، والثانية للانتصارات.

createSeries(0, "Losses");
createSeries(1, "Wins");

إضافة العنوان وتفعيل وسيلة الإيضاح

لجعل الرسم أوضح للمستخدم، نضيف عنواناً يصف المحتوى بدقة، ثم نفعّل legend لتمييز السلاسل.

chart.title()
  .enabled(true)
  .text("20 Years of LA Lakers Win-Loss Record with Kobe Bryant (1996-2016)");

chart.legend().enabled(true);

تحويل الرسم إلى مخطط مكدس ورسمه داخل الحاوية

لكي تظهر القيم حول المحور الأوسط بشكل صحيح، نستخدم وضع التكديس بالقيمة value، ثم نربط المخطط بالحاوية ونرسمه.

// create a stacked bar chart from the multi-series bar chart
chart.yScale().stackMode("value");

// set a container id for the chart
chart.container("container");

// initiate chart drawing
chart.draw();

بهذا أصبح لدينا إصدار أولي يعمل بشكل كامل ويعرض البيانات بصورة تفاعلية.

الإصدار الأولي من مخطط الأعمدة المتباعدة قبل التخصيص

الكود الكامل للإصدار الأساسي

<html>
<head>
  <title>JavaScript Diverging Bar Chart</title>
  <script src="https://cdn.anychart.com/releases/8.9.0/js/anychart-base.min.js" type="text/javascript"></script>
  <style type="text/css">
    html, body, #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <script>
    anychart.onDocumentReady(function () {
      var chart = anychart.bar();

      var winlossData = [
        [65, 17, "2015-16"],
        [61, 21, "2014-15"],
        [55, 27, "2013-14"],
        [37, 45, "2012-13"],
        [25, 41, "2011-12"],
        [25, 57, "2010-11"],
        [25, 57, "2009-10"],
        [17, 65, "2008-09"],
        [25, 57, "2007-08"],
        [40, 42, "2006-07"],
        [37, 45, "2005-06"],
        [48, 34, "2004-05"],
        [26, 56, "2003-04"],
        [32, 50, "2002-03"],
        [24, 58, "2001-02"],
        [26, 56, "2000-01"],
        [15, 67, "1999-00"],
        [19, 31, "1998-99"],
        [21, 61, "1997-98"],
        [26, 56, "1996-97"]
      ];

      var createSeries = function (columnNumber, name) {
        var data = [];
        for (var i = 0; i < winlossData.length; i++) {
          var value = winlossData[i][columnNumber];
          var center = 0;
          if (name === "Wins") {
            data.push({ x: winlossData[i][2], low: center, high: center + value, value: value });
          } else {
            data.push({ x: winlossData[i][2], low: -center, high: -center - value, value: value });
          }
        }
        var series = chart.rangeBar(data);
        series.name(name);
      };

      createSeries(0, "Losses");
      createSeries(1, "Wins");

      chart.title()
        .enabled(true)
        .text("20 Years of LA Lakers Win-Loss Record with Kobe Bryant (1996-2016)");

      chart.legend().enabled(true);
      chart.yScale().stackMode("value");
      chart.container("container");
      chart.draw();
    });
  </script>
</body>
</html>

كيفية تخصيص مخطط الأعمدة المتباعدة

بعد إنشاء النسخة الأساسية، تأتي مرحلة التخصيص، وهي المرحلة التي تمنح الرسم البياني قيمة أكبر من الناحية البصرية والتحليلية. فالتخصيص الجيد لا يجعل المخطط أجمل فقط، بل يجعله أوضح وأكثر فائدة للمستخدم.

1. تحسين التنسيق وإعدادات المحاور

يمكننا ضبط المحاور لجعل القراءة أسهل، مثل إزالة العلامات الصغيرة، وتعديل عنوان المحور، وتحسين شكل التسميات. كما يمكن إزالة الإشارة السالبة من القيم المعروضة على المحور الأفقي لأننا نستخدم الاتجاه السالب فقط لأغراض بصرية.

chart.xAxis().ticks(false);

chart.xAxis().title()
  .enabled(true)
  .text("Years")
  .padding([0, 0, 10, 0]);

chart.xAxis().labels()
  .fontSize(11)
  .fontColor("#474747")
  .padding([0, 10, 0, 0]);

chart.yScale().maximum(80);

chart.yAxis(0).labels().format(function () {
  return Math.abs(this.value);
});

ولإبراز نقطة التباعد في المنتصف، يمكننا إضافة خط مرجعي عند القيمة 0، إلى جانب حد أبيض بين السلسلتين.

// add the stroke by setting it in this line
series.name(name).stroke("3 #fff 1");

// create a line marker at 0
chart.lineMarker()
  .value(0)
  .stroke("#CECECE");

مخطط أعمدة متباعدة بعد تحسين المحاور والتنسيق الأساسي

2. تخصيص Tooltip لعرض معلومات أعمق

أحد أهم عناصر التفاعل في الرسوم البيانية هو tooltip. ومن الجيد هنا ألا نكتفي بعرض عدد المباريات فقط، بل نضيف أيضاً النسبة المئوية للفوز أو الخسارة في كل موسم.

أولاً، نحسب النسبة المئوية داخل دالة تكوين السلسلة:

// calculate percentages for the tooltip
var val = winlossData[i][columnNumber] * 100;
if (columnNumber == 0) {
  var percentValue = val / (winlossData[i][columnNumber] + winlossData[i][columnNumber + 1]);
} else {
  var percentValue = val / (winlossData[i][columnNumber] + winlossData[i][columnNumber - 1]);
}
percentValue = percentValue.toFixed(2);

ثم نضيف القيمة المحسوبة إلى كل عنصر بيانات:

var createSeries = function (columnNumber, name) {
  var data = [];
  for (var i = 0; i < winlossData.length; i++) {
    var val = winlossData[i][columnNumber] * 100;
    if (columnNumber == 0) {
      var percentValue = val / (winlossData[i][columnNumber] + winlossData[i][columnNumber + 1]);
    } else {
      var percentValue = val / (winlossData[i][columnNumber] + winlossData[i][columnNumber - 1]);
    }
    percentValue = percentValue.toFixed(2);

    var value = winlossData[i][columnNumber];
    var center = 0;

    if (name === "Wins") {
      data.push({
        x: winlossData[i][2],
        low: center,
        high: center + value,
        value: value,
        percentValue: percentValue
      });
    } else {
      data.push({
        x: winlossData[i][2],
        low: -center,
        high: -center - value,
        value: value,
        percentValue: percentValue
      });
    }
  }
};

وأخيراً، ننسّق التلميح ليعرض البيانات بشكل جذاب وسهل الفهم:

chart.tooltip()
  .useHtml(true)
  .fontSize(12)
  .titleFormat(function () {
    return this.getData("x") + " " + this.seriesName;
  })
  .format(function () {
    return (
      "<h6 style='font-size:12px; font-weight:400; margin: 0.25rem 0;'>Total games: " +
      "<b>" + this.getData("value") + "</b></h6>" +
      "<h6 style='font-size:12px; font-weight:400; margin: 0.25rem 0;'>Percentage games: " +
      "<b>" + this.getData("percentValue") + " %</b></h6>"
    );
  });

3. تغيير لوحة الألوان لتخدم القصة البصرية

اختيار الألوان ليس تفصيلاً ثانوياً. في هذا المثال، يمكن استخدام ألوان مستوحاة من هوية فريق LA Lakers لإضفاء طابع بصري متناسق مع موضوع البيانات.

chart.palette(
  anychart.palettes.distinctColors().items(["#FDB827", "#542583"])
);

النسخة النهائية من مخطط الأعمدة المتباعدة بألوان مستوحاة من فريق ليكرز

ولإيقاف وضع التحديد التفاعلي على السلاسل، يمكن تعديل السطر الخاص بالسلسلة كما يلي:

series.name(name).stroke("3 #fff 1").selectionMode("none");

الكود الكامل للنسخة النهائية

<html>
<head>
  <title>JavaScript Diverging Bar Chart</title>
  <script src="https://cdn.anychart.com/releases/8.9.0/js/anychart-base.min.js" type="text/javascript"></script>
  <style type="text/css">
    html, body, #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <script>
    anychart.onDocumentReady(function () {
      var chart = anychart.bar();

      var winlossData = [
        [65, 17, "2015-16"],
        [61, 21, "2014-15"],
        [55, 27, "2013-14"],
        [37, 45, "2012-13"],
        [25, 41, "2011-12"],
        [25, 57, "2010-11"],
        [25, 57, "2009-10"],
        [17, 65, "2008-09"],
        [25, 57, "2007-08"],
        [40, 42, "2006-07"],
        [37, 45, "2005-06"],
        [48, 34, "2004-05"],
        [26, 56, "2003-04"],
        [32, 50, "2002-03"],
        [24, 58, "2001-02"],
        [26, 56, "2000-01"],
        [15, 67, "1999-00"],
        [19, 31, "1998-99"],
        [21, 61, "1997-98"],
        [26, 56, "1996-97"]
      ];

      var createSeries = function (columnNumber, name) {
        var data = [];
        for (var i = 0; i < winlossData.length; i++) {
          var val = winlossData[i][columnNumber] * 100;
          if (columnNumber == 0) {
            var percentValue = val / (winlossData[i][columnNumber] + winlossData[i][columnNumber + 1]);
          } else {
            var percentValue = val / (winlossData[i][columnNumber] + winlossData[i][columnNumber - 1]);
          }
          percentValue = percentValue.toFixed(2);

          var value = winlossData[i][columnNumber];
          var center = 0;
          if (name === "Wins") {
            data.push({
              x: winlossData[i][2],
              low: center,
              high: center + value,
              value: value,
              percentValue: percentValue
            });
          } else {
            data.push({
              x: winlossData[i][2],
              low: -center,
              high: -center - value,
              value: value,
              percentValue: percentValue
            });
          }
        }
        var series = chart.rangeBar(data);
        series.name(name).stroke("3 #fff 1").selectionMode("none");
      };

      createSeries(0, "Losses");
      createSeries(1, "Wins");

      chart.title()
        .enabled(true)
        .text("20 Years of LA Lakers Win-Loss Record with Kobe Bryant (1996-2016)");

      chart.legend().enabled(true);
      chart.yScale().stackMode("value");

      chart.xAxis().ticks(false);
      chart.xAxis().title()
        .enabled(true)
        .text("Years")
        .padding([0, 0, 10, 0]);

      chart.xAxis().labels()
        .fontSize(11)
        .fontColor("#474747")
        .padding([0, 10, 0, 0]);

      chart.yScale().maximum(80);
      chart.yAxis(0).labels().format(function () {
        return Math.abs(this.value);
      });

      chart.lineMarker()
        .value(0)
        .stroke("#CECECE");

      chart.tooltip()
        .useHtml(true)
        .fontSize(12)
        .titleFormat(function () {
          return this.getData("x") + " " + this.seriesName;
        })
        .format(function () {
          return (
            "<h6 style='font-size:12px; font-weight:400; margin: 0.25rem 0;'>Total games: " +
            "<b>" + this.getData("value") + "</b></h6>" +
            "<h6 style='font-size:12px; font-weight:400; margin: 0.25rem 0;'>Percentage games: " +
            "<b>" + this.getData("percentValue") + " %</b></h6>"
          );
        });

      chart.palette(anychart.palettes.distinctColors().items(["#FDB827", "#542583"]));
      chart.container("container");
      chart.draw();
    });
  </script>
</body>
</html>

أفضل الممارسات عند بناء هذا النوع من الرسوم

  • احرص على أن تكون نقطة الوسط منطقية وواضحة للمستخدم.
  • لا تكثر من الألوان، بل استخدم تبايناً يخدم المعنى.
  • أضف tooltip غنيّاً بالمعلومات بدلاً من إغراق الرسم بالنصوص.
  • إذا كانت البيانات كثيرة، رتّب المواسم أو الفئات بشكل يسهل تتبعه بصرياً.
  • اختبر المخطط على الشاشات الصغيرة لضمان قابلية القراءة.

الخلاصة التقنية

إن إنشاء مخطط أعمدة متباعدة باستخدام JavaScript ليس معقداً كما قد يبدو، خصوصاً عند الاعتماد على مكتبة ناضجة مثل AnyChart. القيمة الحقيقية هنا لا تكمن في رسم الأعمدة فقط، بل في تحويل البيانات الخام إلى قصة بصرية مفهومة. وعندما تضيف تخصيصاً مدروساً للمحاور والألوان ووسائل التلميح، يصبح المخطط أداة تحليلية فعالة، لا مجرد شكل جمالي داخل الصفحة.

إذا كنت تنشئ محتوى تقنياً أو لوحة معلومات تفاعلية، فإن هذا النوع من الرسوم يُعد خياراً ممتازاً لعرض البيانات الثنائية المتقابلة بطريقة واضحة، احترافية، وسهلة الاستيعاب.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *