How to create an animated bump chart using After Effects

How to Create an Animated Bump Chart using After Effects

In the world of content creation today, the dry language of numbers has become boring and no longer attracts viewers' attention. This is where the art of motion graphics intervenes to turn these complex tables into enjoyable visual stories. Perhaps one of the most attention-grabbing visual methods is what is known as an "Animated Ranking Tracker" or a (Bump Chart).

 Colorful lines racing on the screen, intersecting, rising, and falling to tell you a story of a struggle between tech companies, a YouTube channel race, or even the ranking of sports clubs over time. This type of video achieves millions of views because it touches human instinct for "competition and anticipation."

But how do we create this type of video? Do we manually animate every line across hundreds of Keyframes? Of course not. If we did that, we would need weeks to finish a one-minute video, and any minor edit from the client would mean restarting the work from scratch.

Through this guide, I aim to plant the "programmatic way of thinking" in your mind inside Adobe After Effects. I will teach you how to make table data drive your animation automatically using Expressions.

We will apply the practical explanation to a model ranking "fictional football teams" competing over 11 rounds (days). But remember, the real power lies in your ability to take this structure and apply it to any other idea that comes to mind. 

Step One: Preparing the CSV File

The CSV data file


Any professional animated chart does not start in After Effects; it starts from the data file (CSV). This file is the "brain" that will tell the lines when to rise and when to fall. Organization here saves you hours of mistakes later.

Open Excel or Google Sheets, and we will build the columns in a very smart way. Let me show you how I arranged my own file, and why I chose this arrangement:

Column Name Function and Explanation Example for Clarification
First Column (Team_Name) Here we put the team names. To simplify the explanation, I named the teams with colors. Red Team, Blue Team
Second Column (Hex_Color) We put the color codes here. This column will save us from coloring the lines manually; After Effects will read the code and color the line automatically. #FF0000 (for Red)
Third Column (Logo_File) Here we put the path of the image located on your computer. I'll tell you later how we use a magic script to swap all these images with a click of a button. file:///C:/Users/BillkerX/Desktop/logos/red_team.png
Remaining Columns (Round Rankings) The fourth column will be the starting point (Start), then MD1 for the first round, MD2 for the second round, all the way to MD11. Under each round, we write the "rank number" of the team on that day. Rank 1, Rank 5, etc.
Golden Tip: Do you have to stick to the exact same column names? Programmatically no, but organizationally yes. You must know the "column number" from which the race rounds begin, because we will tell the After Effects code to start reading the animation from that specific column.


Step Two: Preparing the After Effects File

Now we move to the visual fun. Open After Effects and create a new Composition. I chose a 1920x1920 (square) size to fit various social media platforms, but the advantage of the method we will build is that it's "Responsive" and adapts to any size thanks to the code.

Creating the "Control" Layer (Null Object)

Never start drawing the lines directly. The first thing we do is create an empty layer (Null Object) and name it ctrl (short for Control). This layer will act as a remote control for the entire project.


Null ctrl Layer


Add 9 (Slider Control) effects to this layer and name them exactly as follows (the names are important for the codes to work):

Slider Name Value Slider Function in the Project
HOLD 1 Determines how many seconds the logo stands to catch its breath before moving to the next round.
MOVE 1 Determines how many seconds it takes for the logo to transition from one round to another.
NUM_MD 11 Total number of rounds (race days) we have in the CSV file.
RANK_COL 3 The column number from which the first round begins in the Excel file (remember that programming starts counting from zero).
START_X -114 The horizontal starting point on the screen for the first round.
START_Y 200 The vertical point where the first-place holder will stand.
COL_SPACING 149 The horizontal distance in pixels between a round and the next one.
RANK_SPACING 100 The vertical distance in pixels between the first place, the second, the third...

Thanks to these sliders, if a client asks you two days later to "widen the distance between the teams," you won't have to adjust 20 layers. You will only change the (RANK_SPACING) value, and the entire project will expand.

Step Three: Drawing the Data Path (Line Engineering)

Now we will draw the line representing a single team's journey. Go to (Layer > New > Shape Layer). Don't draw anything by hand. Open the layer properties, add a path (Path) and an outline (Stroke).

 Crucial Step:
Name this Layer with the number 1. This is not a random number. The code we will place now will look for the layer name (1), to go to the CSV file and fetch the team's data located in "Row number 1". Organization here is the secret to success.

Breaking Down the Line Path Code (Path Expression)

Click on the stopwatch icon (Alt + Click) next to the Path property and place the following code. I have divided and explained it so you understand the thinking process:

JavaScript (Part 1: Linking to Layers)
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: This function takes the current layer's name (which we named "1") and turns it into a mathematical number. This number will tell the program which row to read from the Excel file.
  • ctrl: This variable creates a bridge or a direct link between this code and the control layer ctrl that we created earlier.
  • HOLD and MOVE: They fetch the values we entered in the sliders to know the duration of the line's rest and movement in seconds.
  • NUM_MD and RANK_COL: They fetch the total number of rounds and the key column number in the CSV to know where to start reading the data.
  • START_X, COL_SPACING, and others: These variables pull the grid dimensions so we can later convert the ranking (1st or 2nd place) into actual pixel coordinates on your screen.
JavaScript (Part 2: Reading the CSV file)
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: They define the name of the attached file in the Project Panel. The value of HEADER_ROWS is zero because After Effects skips the header row automatically.
  • cell(r, c) function: This is a "safe" function I programmed specifically to fetch the value from the cell. If it encounters an empty cell or incomprehensible text (via Try/Catch command), it returns the value -1 instead of stopping and halting the entire project, which is an excellent protective step to ensure work continuity.
  • rankAt(md) function: Uses the cell function to fetch the team's "current rank" in the round we specify for it (md).
  • xAt(md) and yAt(r) functions: They convert abstract numbers into real pixel coordinates (X and Y) based on the distances we specified in the sliders.
JavaScript (Part 3: Time Engine and Smoothing)
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: The full cycle (cycle) is the sum of the wait time and movement time. The variable t calculates the actual elapsed time since the layer appeared on the timeline.
  • i: This variable calculates the "current round number" based on the elapsed time, with a conditional command that prevents it from exceeding the last round last.
  • hasData & hasNextData: Logical verification tools that ensure there is actual ranking data (greater than zero) before drawing the line, to prevent path distortion if data is missing.
  • p: This variable is the secret of smoothness in our animation. It calculates the progress percentage from 0 to 1 between the two rounds. Using the ease function, we ensure that the line starts its movement slowly and ends slowly (Smooth Animation) instead of a sharp, robotic movement.
JavaScript (Part 4 & 5: Building the Final Path)
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: These two variables represent the "tip of the pen" that draws the line. The if part adds gradual expansion and sliding based on the progress percentage p, to make the tip of the line head towards the next round's point with perfect flexibility.
  • pts Array: It gathers all the previous coordinate points of the line (the rounds that passed) and fixes them in their correct place, and adds the current moving "tip of the pen" location to it.
  • createPath(): This final function is what commands After Effects to draw the actual path based on the points gathered in the array.

Copy all five previous parts and place them as a single piece in the Path property.

Automatic Colors for Lines (Stroke Color Expression)

Don't tire yourself out coloring each team manually. Place this code in the (Color) property of the (Stroke). This genius code will go to the second column in the CSV, fetch the Hex code, and translate it into a language After Effects understands:

JavaScript (Fully Automatic Coloring Code)
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: Specifies the number of the column containing the color code (which is the second column in Excel, and its number programmatically is 1 because counting starts from zero).
  • hexStr: Fetches the text color code (e.g., #6c71c4) of the current team based on the layer number.
  • hexToRgb(hex) function: After Effects does not understand colors in the (0 to 255) system in expressions, but rather in a fraction system (0 to 1). This function cleans the # symbol, then converts the hex text into numbers, and uses bitwise operations (like >> 16) to divide the number and extract the red, green, and blue percentages, returning a ready color array to color the line directly.

Step Four: Adding Souls to the Race (Logos and Images)

Lines alone are vague, we need the logo or the player's face at the forefront of each line. Add a small image to the timeline.

 Naming Step:
Name the image layer i1 (the letter i then the number 1). This will make the image understand that it belongs to line number 1.

Open the Position property of the image, and place the following code:

JavaScript (Tracking the line tip - 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 and n: The code here uses Regular Expressions (RegEx) via /i(d+)/i to read the word "i1" and extract only the number "1" from it, to know that this image belongs to line number 1.
  • Mathematical Calculations: These are exactly the same calculations found in the path code (calculating time, cycle, and line tip location) so that the image moves at the exact same speed as the line.
  • toComp([tipX, tipY]): This line is the most important. It searches for the corresponding line layer (layer number 1), then uses the toComp command to convert the coordinates from the local space of the line to the global space of the scene. This ensures the logo sticks to the tip of the line like a magnet, no matter how you move or rotate the layers.

Duplicating with a Button Click

Now comes the fun of smart work. Do you have 20 teams in the data file? Select layer 1 and layer i1, and press (Ctrl + D) to duplicate them 19 times. The program will automatically name them (2, i2) then (3, i3)... And the codes will take care of drawing the lines for each team and fetching their colors and movement from the data file instantly. Amazing, isn't it?

Step Five: Designing the Background in Photoshop

Designing the background in Photoshop

Many designers exhaust themselves trying to draw the ranking grid inside After Effects using hundreds of lines. My own method is easier and more accurate:

  1. After you set the distances between lines and rounds via the control layer (ctrl), take a transparent snapshot of the current scene from After Effects.
  2. Open the image in Photoshop.
  3. Now you have a very accurate map of the intersection locations. Draw the grid lines, and write the ranking numbers and round days elegantly and comfortably.
  4. As a background, I used a picture of a stadium, placed a dark green layer over it with a (Multiply) blending mode. This gives a sports vibe without distracting from the movement of the lines.
  5. Export the "Grid and Texts" layer in PNG format, and the background as a separate image, and re-import them into After Effects below our animated lines.

Step Six: Changing the Images

Imagine you have 40 different images for 40 teams, will you replace them manually inside After Effects? This is a huge waste of time.

In the Logo_File column in the CSV file, we have recorded the real path for each image on your device.

You will need to use a custom JSX Script to swap the sources. The method is very simple, you can download the file from here. If you haven't downloaded from GitHub before, just click on the Download raw file button, then go back to After Effects and follow the steps:

  • Select all image layers (i1, i2, i3...) in your timeline.
  • From the top menu go to (File > Scripts > Run Script File).
  • Choose the script file you downloaded from GitHub. The program will ask you to choose the CSV file containing the image paths.
  • In the blink of an eye, the script will read the file, pull the images from your folder, and replace them in exact order in the project.
Important Note: 
Sometimes we convert some layers to a (Guide Layer) to use as a visual guide during design. Never forget to disable this property on Shape layers before exporting the final video (Render). This happened to me personally; I forgot it and was surprised by an empty video containing no graph lines.


What we built today is not just a "project", but rather a powerful "tool" (Template) of your own. Next time a client asks you to build an animated chart showing the rise of his company's profits or the decline of its competitors, or you need it for a personal video or your content, you will not open the timeline and start moving points manually.

All you have to do is enter the new data into the Excel file, and change the sliders with a click of a button, and After Effects will engineer and draw the animation for you with precision and fluidity. Today you moved from being an exhausted manual "order executor" to being a "smart motion graphics creator" who employs technology and programmatic expressions to speed up his production and multiply its quality. Keep experimenting, for in the world of visual data, there are no limits.


شارك مع أصدقائك :

Related Posts:

ad