كيفية إنشاء رسم بياني متحرك (Bump Chart) باستخدام أفتر إفكتس

BillkerX
Bilel Kerfas
20 أغسطس 2026
كيفية إنشاء رسم بياني متحرك (Bump Chart) باستخدام أفتر إفكتس

في عالم صناعة المحتوى اليوم، أصبحت لغة الأرقام الجافة مملة ولا تجذب انتباه المشاهدين. هنا يتدخل فن الموشن جرافيك ليحول هذه الجداول المعقدة إلى قصص بصرية ممتعة. ولعل من أكثر الأساليب البصرية  جذبا للانتباه هو ما يُعرف بـ "الرسم البياني المتحرك لتتبع الترتيب" أو (Bump Chart).

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

لكن، كيف نصنع هذا النوع من الفيديوهات ؟ هل نقوم بتحريك كل خط يدويا عبر مئات الـ Keyframes ؟ بالطبع لا إذا فعلنا ذلك، سنحتاج إلى أسابيع لإنجاز فيديو مدته دقيقة واحدة، وأي تعديل بسيط من العميل سيعني إعادة العمل من الصفر.

من خلال هذا الدليل أهدف لزرع في عقلك "طريقة التفكير البرمجية" داخل برنامج أدوبي أفتر إفكتس. سأعلمك كيف تجعل بيانات الجداول تقود حركتك تلقائيا باستخدام الـ Expressions.

سنطبق الشرح العملي على نموذج لترتيب "فرق كرة قدم خيالية" تتنافس على مدار 11 جولة (يوم). لكن تذكر، القوة الحقيقية تكمن في قدرتك على أخذ هذا الهيكل وتطبيقه على أي فكرة أخرى تخطر ببالك. 

الخطوة الأولى تحضير ملف الـ CSV

ملف الـ CSV للبيانات


أي رسم بياني متحرك احترافي لا يبدأ من الأفتر إفكتس، بل يبدأ من ملف البيانات (CSV). هذا الملف هو "العقل" الذي سيخبر الخطوط متى ترتفع ومتى تهبط. التنظيم هنا يوفر عليك ساعات من الأخطاء لاحقا.

قم بفتح برنامج Excel أو Google Sheets، وسنقوم ببناء الأعمدة بطريقة ذكية جدا. سأستعرض لك كيف رتبت ملفي الخاص، ولماذا اخترت هذا الترتيب:

اسم العمود الوظيفة والشرح مثال للتوضيح
الخانة الأولى (Team_Name) هنا نضع أسماء الفرق. لتسهيل الشرح، سميت الفرق بأسماء ألوان. الفريق الأحمر، الفريق الأزرق
الخانة الثانية (Hex_Color) نضع هنا أكواد الألوان. هذا العمود سيوفر علينا تلوين الخطوط يدويا؛ الأفتر إفكتس سيقرأ الكود ويلون الخط تلقائيا #FF0000 (للأحمر)
الخانة الثالثة (Logo_File) هنا نضع مسار الصورة الموجودة على جهاز الكمبيوتر الخاص بك. سأخبرك لاحقا كيف نستخدم سكريبت سحري لتبديل كل هذه الصور بضغطة زر. file:///C:/Users/BillkerX/Desktop/logos/red_team.png
الخانات المتبقية (ترتيب الجولات) الخانة الرابعة ستكون نقطة البداية (Start)، ثم MD1 للجولة الأولى، MD2 للجولة الثانية، وصولا إلى MD11. تحت كل جولة، نكتب "رقم مركز" الفريق في ذلك اليوم. المركز 1، المركز 5، إلخ.
نصيحة ذهبية: هل يجب أن تتقيد بنفس أسماء الأعمدة؟ برمجيا لا، ولكن تنظيميا نعم. يجب أن تعرف "رقم العمود" الذي تبدأ منه جولات السباق، لأننا سنخبر كود الأفتر إفكتس أن يبدأ قراءة الحركة من العمود الفلاني.


الخطوة الثانية تحضير ملف الأفتر إفكتس

الآن ننتقل إلى المتعة البصرية. افتح برنامج After Effects وأنشئ Composition جديد . أنا اخترت مقاس 1920x1920 (مربع) ليتناسب مع مختلف منصات التواصل، ولكن ميزة الطريقة التي سنبنيها أنها "مرنة" (Responsive) وتتأقلم مع أي مقاس بفضل الأكواد.

إنشاء طبقة "التحكم" (Null Object)

لا تبدأ أبدا برسم الخطوط مباشرة. أول شيء نفعله هو إنشاء طبقة فارغة (Null Object) ونسميها ctrl (اختصار لـ Control). هذه الطبقة ستكون بمثابة جهاز التحكم عن بعد لكل المشروع.


طبقة التحكم Null ctrl


أضف إلى هذه الطبقة 9 تأثيرات من نوع (Slider Control) وقم بتسميتها بالأسماء التالية تماما (الأسماء مهمة لكي تعمل الأكواد):

اسم السلايدر القيمة وظيفة السلايدر في المشروع
HOLD 1 تحدد كم ثانية يقف الشعار ليلتقط أنفاسه قبل أن يتحرك للجولة القادمة.
MOVE 1 تحدد كم ثانية تستغرق عملية انتقال الشعار من جولة إلى أخرى.
NUM_MD 11 إجمالي عدد الجولات (أيام السباق) التي نملكها في ملف الـ CSV.
RANK_COL 3 رقم العمود الذي تبدأ منه أول جولة في ملف الإكسيل (تذكر أن البرمجة تبدأ العد من الصفر).
START_X -114 نقطة البداية الأفقية على الشاشة لأول جولة.
START_Y 200 النقطة الرأسية التي سيقف عليها صاحب المركز الأول.
COL_SPACING 149 المسافة الأفقية بالبكسل بين الجولة والجولة التي تليها.
RANK_SPACING 100 المسافة الرأسية بالبكسل بين المركز الأول، والثاني، والثالث...

بفضل هذه السلايدرات، إذا طلب منك العميل بعد يومين "توسيع المسافة بين الفرق"، لن تضطر لتعديل 20 طبقة. ستغير فقط قيمة (RANK_SPACING) وسيتوسع المشروع بأكمله .

الخطوة الثالثة رسم مسار البيانات (هندسة الخطوط)

الآن سنقوم برسم الخط الذي يمثل مسيرة فريق واحد. اذهب إلى (Layer > New > Shape Layer). لا ترسم أي شيء بيدك افتح خصائص الطبقة، أضف مسارا (Path) وخطا خارجيا (Stroke).

 خطوة مصيرية:
قم بتسمية هذا الـ Layer بالرقم 1. هذا ليس رقما عشوائيا. الكود الذي سنضعه الآن سيبحث عن اسم الطبقة (1)، ليذهب إلى ملف الـ CSV ويجلب بيانات الفريق الموجود في "الصف رقم 1". التنظيم هنا هو سر النجاح.

تفكيك كود مسار الخط (Path Expression)

اضغط على أيقونة الساعة (Alt + Click) بجانب خاصية المسار (Path) وضع الكود التالي. لقد قمت بتقسيمه وشرحه لتفهم طريقة التفكير:

JavaScript (الجزء 1: الربط بالطبقات)
var teamRow = parseInt(thisLayer.name); var ctrl = thisComp.layer("ctrl"); var HOLD = ctrl.effect("HOLD")("Slider"); var MOVE = ctrl.effect("MOVE")("Slider"); var NUM_MD = Math.round(ctrl.effect("NUM_MD")("Slider")); var RANK_COL = Math.round(ctrl.effect("RANK_COL")("Slider")); var START_X = ctrl.effect("START_X")("Slider"); var COL_SPACING = ctrl.effect("COL_SPACING")("Slider"); var START_Y = ctrl.effect("START_Y")("Slider"); var RANK_SPACING = ctrl.effect("RANK_SPACING")("Slider");

  • teamRow: تقوم هذه الدالة بأخذ اسم الطبقة الحالية (الذي سميناه "1") وتحويله إلى رقم رياضي. هذا الرقم سيخبر البرنامج أي صف يجب أن يقرأه من ملف الإكسيل.
  • ctrl: هذا المتغير ينشئ جسرا أو رابطا مباشرا بين هذا الكود وطبقة التحكم ctrl التي أنشأناها سابقا.
  • HOLD و MOVE: يجلبان القيم التي أدخلناها في السلايدرات لمعرفة مدة وقوف وحركة الخط بالثواني.
  • NUM_MD و RANK_COL: يجلبان عدد الجولات الكلي و رقم العمود المفتاحي في CSV لمعرفة من أين تبدأ قراءة البيانات.
  • START_X و COL_SPACING وغيرها: هذه المتغيرات تسحب مقاسات الشبكة لكي نتمكن لاحقا من تحويل الترتيب (المركز 1 أو 2) إلى إحداثيات بكسل فعلية على شاشتك.
JavaScript (الجزء 2: قراءة ملف CSV)
var CSV_NAME = "data.csv"; var HEADER_ROWS = 0; var csv = footage(CSV_NAME); function cell(r, c){ try { var val = csv.dataValue([c, r]); var num = parseFloat(val); return isNaN(num) ? -1 : num; } catch(e) { return -1; } } function rankAt(md){ return cell(HEADER_ROWS + (teamRow - 1), RANK_COL + (md - 1)); } function xAt(md){ return START_X + (md - 1) * COL_SPACING; } function yAt(r){ return START_Y + (r - 1) * RANK_SPACING; }

  • CSV_NAME & HEADER_ROWS: يحددان اسم الملف المرفق في لوحة المشروع (Project Panel). قيمة HEADER_ROWS هي صفر لأن الأفتر إفكتس يتخطى صف العناوين تلقائيا.
  • دالة cell(r, c): هذه دالة "آمنة" برمجتها خصيصا لجلب القيمة من الخلية. إذا واجهت خلية فارغة أو نصا غير مفهوم (عبر أمر Try/Catch)، فإنها تعيد القيمة -1 بدلا من إيقاف وعرقلة كامل المشروع، وهي خطوة حماية ممتازة لضمان استمرار العمل.
  • دالة rankAt(md): تستخدم دالة cell لجلب "الترتيب الحالي" للفريق في الجولة التي نحددها لها (md).
  • دالتي xAt(md) و yAt(r): تقومان بتحويل الأرقام المجردة إلى إحداثيات بكسل حقيقية (X و Y) بناء على المسافات التي حددناها في السلايدرات.
JavaScript (الجزء 3: محرك الوقت والتنعيم)
var cycle = HOLD + MOVE; var last = NUM_MD; var t = Math.max(time - inPoint, 0); var i = 1 + Math.floor(t / cycle); if (i > last) i = last; var currentRank = rankAt(i); var nextRank = (i < last) ? rankAt(i + 1) : -1; var hasData = (currentRank > 0); var hasNextData = (nextRank > 0); var p = 0; if (hasData && hasNextData && i < last){ p = ease(t, (i - 1) * cycle + HOLD, i * cycle, 0, 1); }

  • cycle & t: الدورة الكاملة (cycle) هي مجموع وقت الانتظار ووقت الحركة. المتغير t يحسب الزمن الفعلي المنقضي منذ بداية ظهور الطبقة على التايم لاين.
  • i: هذا المتغير يحسب "رقم الجولة الحالية" بناءً على الزمن المنقضي، مع أمر شرطي يمنعه من تجاوز الجولة الأخيرة last.
  • hasData & hasNextData: أدوات تحقق منطقية تتأكد من وجود بيانات ترتيب فعلية (أكبر من الصفر) قبل أن ترسم الخط، لمنع تشوه المسار إذا كانت البيانات مفقودة.
  • p: هذا المتغير هو سر النعومة في حركتنا. يحسب نسبة التقدم من 0 إلى 1 بين الجولتين. باستخدام دالة ease، نضمن أن الخط يبدأ حركته ببطء وينتهي ببطء (Smooth Animation) بدلا من الحركة الآلية الحادة.
JavaScript (الجزء 4 و 5: بناء المسار النهائي)
var tipX = xAt(i); var tipY = hasData ? yAt(currentRank) : yAt(1); if (hasData && hasNextData && i < last) { tipX = xAt(i) + COL_SPACING * p; tipY = yAt(currentRank) + (yAt(nextRank) - yAt(currentRank)) * p; } var pts = []; for (var v = 1; v <= NUM_MD; v++){ if (v <= i && rankAt(v) > 0){ pts.push([xAt(v), yAt(rankAt(v))]); } else { pts.push([tipX, tipY]); } } if (rankAt(1) <= 0 && i == 1) { createPath([], [], [], false); } else { createPath(pts, [], [], false); }
  • tipX & tipY: هذان المتغيران يمثلان "رأس القلم" الذي يرسم الخط. جزء الـ if يقوم بإضافة التمدد والانزلاق التدريجي بناءً على نسبة التقدم p، ليجعل رأس الخط يتجه نحو نقطة الجولة القادمة بمرونة تامة.
  • المصفوفة pts: تقوم بجمع كل إحداثيات النقاط السابقة للخط (الجولات التي مرت) وتثبيتها في مكانها الصحيح، وتضيف لها موقع "رأس القلم" المتحرك الحالي.
  • createPath(): هذه الدالة النهائية هي التي تأمر الأفتر إفكتس برسم المسار الفعلي بناءً على النقاط المجمعة في المصفوفة.

قم بنسخ جميع الأجزاء الخمسة السابقة وضعها كقطعة واحدة في خاصية Path

الألوان التلقائية للخطوط (Stroke Color Expression)

لا تتعب نفسك بتلوين كل فريق يدويا. ضع هذا الكود في خاصية (Color) الخاصة بـ (Stroke). هذا الكود العبقري سيذهب للعمود الثاني في الـ CSV ويجلب كود الـ Hex ويترجمه للغة يفهمها الأفتر إفكتس:

JavaScript (كود التلوين التلقائي بالكامل)
var teamRow = parseInt(thisLayer.name); var CSV_NAME = "data.csv"; var HEADER_ROWS = 0; var HEX_COL = 1; var csv = footage(CSV_NAME); function cell(r, c){ try { var val = csv.dataValue([c, r]); return (val && val != "") ? String(val) : "#FFFFFF"; } catch(e) { return "#FFFFFF"; } } var hexStr = cell(HEADER_ROWS + (teamRow - 1), HEX_COL); function hexToRgb(hex) { var clean = String(hex).replace("#", "").trim(); if (clean.length < 6) return [1, 1, 1, 1]; var bigint = parseInt(clean, 16); return [ ((bigint >> 16) & 255) / 255, ((bigint >> 8) & 255) / 255, (bigint & 255) / 255, 1 ]; } hexToRgb(hexStr);

  • HEX_COL = 1: يحدد رقم العمود الذي يحتوي على كود الألوان (وهو العمود الثاني في الإكسيل، ورقمه برمجياً 1 لأن العد يبدأ من صفر).
  • hexStr: يجلب كود اللون النصي (مثلاً #6c71c4) الخاص بالفريق الحالي بالاعتماد على رقم الطبقة.
  • الدالة hexToRgb(hex): الأفتر إفكتس لا يفهم الألوان بنظام (0 إلى 255) في الأكواد، بل بنظام كسور (0 إلى 1). هذه الدالة تنظف الرمز #، ثم تحول النص السداسي عشري إلى أرقام، وتستخدم العمليات البتية (مثل >> 16) لتقسيم الرقم واستخراج نسب الأحمر، والأخضر، والأزرق، وتعيد مصفوفة لونية جاهزة لتلوين الخط مباشرة.

الخطوة الرابعة: إضافة الأرواح للسباق (الشعارات والصور)

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

 خطوة التسمية:
قم بتسمية طبقة الصورة بـ i1 (حرف i ثم الرقم 1). هذا سيجعل الصورة تفهم أنها تابعة للخط رقم 1.

افتح خاصية الـ Position للصورة، وضع الكود التالي:

JavaScript (تتبع رأس الخط - Position)
var match = thisLayer.name.match(/i(\d+)/i); var n = match ? parseInt(match[1]) : 0; var teamRow = n; var ctrl = thisComp.layer("ctrl"); var HOLD = ctrl.effect("HOLD")("Slider"); var MOVE = ctrl.effect("MOVE")("Slider"); var NUM_MD = Math.round(ctrl.effect("NUM_MD")("Slider")); var RANK_COL = Math.round(ctrl.effect("RANK_COL")("Slider")); var START_X = ctrl.effect("START_X")("Slider"); var COL_SPACING = ctrl.effect("COL_SPACING")("Slider"); var START_Y = ctrl.effect("START_Y")("Slider"); var RANK_SPACING = ctrl.effect("RANK_SPACING")("Slider"); var CSV_NAME = "data.csv"; var HEADER_ROWS = 0; var csv = footage(CSV_NAME); function cell(r, c){ return csv.dataValue([c, r]); } function rankAt(md){ var v = parseFloat(cell(HEADER_ROWS + (teamRow - 1), RANK_COL + (md - 1))); return isNaN(v) ? 1 : v; } function xAt(md){ return START_X + (md - 1) * COL_SPACING; } function yAt(r){ return START_Y + (r - 1) * RANK_SPACING; } var cycle = HOLD + MOVE; var last = NUM_MD; var t = Math.max(time - inPoint, 0); var i = 1 + Math.floor(t / cycle); if (i > last) i = last; var p = 0; if (i < last){ p = ease(t, (i - 1) * cycle + HOLD, i * cycle, 0, 1); } var tipX, tipY; if (i >= last){ tipX = xAt(last); tipY = yAt(rankAt(last)); } else { tipX = xAt(i) + COL_SPACING * p; tipY = yAt(rankAt(i)) + (yAt(rankAt(i + 1)) - yAt(rankAt(i))) * p; } var lineLayer = thisComp.layer(String(n)); lineLayer.toComp([tipX, tipY]);

  • match و n: الكود هنا يستخدم التعبيرات النمطية (RegEx) عبر /i(\d+)/i ليقرأ كلمة "i1" ويستخرج منها الرقم "1" فقط، ليعرف أن هذه الصورة تابعة للخط رقم 1.
  • الحسابات الرياضية: هي نفس الحسابات الموجودة في كود المسار تماماً (حساب الوقت، الدورة، ومكان رأس الخط) لكي تتحرك الصورة بنفس سرعة الخط تماما.
  • toComp([tipX, tipY]): هذا السطر هو الأهم ، يقوم بالبحث عن طبقة الخط المقابلة (الطبقة رقم 1)، ثم يستخدم أمر toComp لتحويل الإحداثيات من المساحة المحلية للخط إلى المساحة الكلية للمشهد. هذا يضمن أن يلتصق الشعار برأس الخط كالمغناطيس مهما حركت أو قمت بتدوير الطبقات.

المضاعفة بضغطة زر

الآن تأتي متعة العمل الذكي. هل لديك 20 فريقاً في ملف البيانات؟ قم بتحديد الطبقة 1 والطبقة i1، واضغط (Ctrl + D) لمضاعفتهم 19 مرة البرنامج سيقوم تلقائياً بتسميتهم (2, i2) ثم (3, i3)... والأكواد ستتكفل برسم الخطوط لكل فريق وجلب ألوانهم وحركتهم من ملف البيانات فورا  مذهل، أليس كذلك؟

الخطوة الخامسة تصميم الخلفية في فوتوشوب

تصميم الخلفية في فوتوشوب

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

  1. بعد أن تضبط المسافات بين الخطوط والجولات عبر طبقة التحكم (ctrl)، قم بالتقاط صورة شفافة (Snapshot) للمشهد الحالي من الأفتر إفكتس.
  2. افتح الصورة في برنامج Photoshop.
  3. الآن لديك خريطة دقيقة جداً لأماكن التقاطعات. ارسم خطوط الشبكة، واكتب أرقام الترتيب وأيام الجولات بشكل أنيق ومريح.
  4. كخلفية، استخدمت صورة  ملعب، وضعت فوقه طبقة بلون أخضر داكن وبنظام دمج (Multiply). هذا يعطي طابعا رياضيا دون تشتيت الانتباه عن حركة الخطوط.
  5. قم بتصدير طبقة "الشبكة والنصوص" بصيغة PNG، والخلفية كصورة منفصلة، وأعد إدخالهما إلى الأفتر إفكتس أسفل خطوطنا المتحركة.

الخطوة السادسة تغيير الصور 

تخيل أن لديك 40 صورة مختلفة لـ 40 فريقا، هل ستقوم بتبديلها يدويا داخل الأفتر إفكتس؟ هذا اهدار كبير  للوقت .

في العمود الخاص بـ Logo_File في ملف الـ CSV، نحن قمنا بتسجيل المسار الحقيقي لكل صورة على جهازك.

ستحتاج إلى استخدام سكريبت  JSX  مخصص لتبديل المصادر الطريقة بسيطة جدا يمكنك تحميل الملف من هنا  اذا لم يسبق لك التحميل من GitHub فقط اضغط على زر التحميل Download raw file ثم ارجع الى أفتر افكتس و أتبع الخطوات:

  • قم بتحديد كل طبقات الصور (i1, i2, i3...) في التايم لاين الخاص بك.
  • من القائمة العلوية اذهب إلى (File > Scripts > Run Script File).
  • اختر ملف السكريبت الذي حملته من GitHub ، سيطلب منك البرنامج اختيار ملف الـ CSV الذي يحتوي على مسارات الصور.
  • في لمح البصر، سيقوم السكريبت بقراءة الملف، وسحب الصور من مجلدك، واستبدالها بالترتيب الدقيق في المشروع.
ملاحظة مهمة 
أحيانا نقوم بتحويل بعض الطبقات إلى (Guide Layer) لنستعين بها كدليل بصري أثناء التصميم. إياك أن تنسى إلغاء هذه الخاصية عن Shape layers  قبل تصدير الفيديو النهائي (Render)، حدث هذا  لي شخصيا نسيتها  و تفاجأت بفيديو فارغ لا يحتوي على أي خطوط بيانية


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

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

مواضيع قد تهمك: