Contents
Let’s visualize a bar graph in blender
Data visualization is an integral part of anything to do with IT. Be it when dealing Database monitoring, Observability metrics or Revenue streams or anything else.
Let’s begin
In this article we’ll explore how we can automate the visualization of a large air quality index dataset using open source tools.
(Note that we are not building any dashboards here, just an animated bar graph that you can show off in presentations or your website.)
What we need
•Any blender version upwards of 2.8. In this project I am using 4.5.5.
•Python 3.9
•Libreoffice calc / MS excel
•Freely available dataset from Kaggle
Downloading the dataset
For this project, we will be using this dataset from Kaggle. The idea is to visualize the level of O3 in the environment in different cities over time.
You could also go a step further and calculate the AQI for each city.
Prepping the data
Open up the csv in any spreadsheet software of your choice (Calc for me).
First we sort the data in increasing order of time.
Then we can optionally fill in missing values in the columns we want; or we can handle this in the visualization part by ignoring those entries altogether or averaging the values.
To make things easier, I have also created a separate file with the names of all the cities separated by commas.
Scripting the graph
The purpose of our python scripts is to automate this graph creation and animation process in blender.
Creating the graph
The initial scene will contain a sample bar and sample text along with the axes. We will tell blender to read from our “cities list file” and create the bars and text for all cities.
Initial scene
Below is the code for generating the bars in the graph:-
create-graph.py
import random # Define the path to the file file_path = './air-quality-data-in-india/all_cities.txt' city_list = [] # Read the file and split contents by comma try: with open(file_path, 'r') as file: content = file.read() city_list = [city.strip() for city in content.split(',') if city.strip()] print("Cities loaded:", city_list) except FileNotFoundError: print(f"File not found at path: {file_path}") except Exception as e: print(f"An error occurred: {e}") import bpy scene = bpy.context.scene # Get the reference strips bar_sample = bpy.data.objects["Plane"] text_sample = bpy.data.objects["Text"] # Strip duration and frame position duration = 720 start_frame = 0 frame_gap = 0 # Optional spacing displacement = 3 for idx, city in enumerate(city_list): # Step 2: Compute frame start frame_start = start_frame + idx * frame_gap # Duplicate the bar with linked data (same mesh) duplicate = bar_sample.copy() duplicate.data = bar_sample.data # Link the same mesh datablock duplicate.animation_data_clear() # Optional: clear animation data # Optionally offset the duplicate to avoid overlap duplicate.location.x += displacement * (idx + 1) # Link the new object to the same collection(s) as the original for collection in bar_sample.users_collection: collection.objects.link(duplicate) duplicate.name = f"{city}_bar" # Duplicate the text duplicate_text = text_sample.copy() duplicate_text.data = text_sample.data.copy() # Duplicate mesh data (new mesh datablock) duplicate_text.animation_data_clear() # Optional # Offset to see the duplicate duplicate_text.location.x += displacement * (idx + 1) # Link the duplicate to the same collection(s) for collection in text_sample.users_collection: collection.objects.link(duplicate_text) duplicate_text.data.body = city
Bars generated
Animating the graph
Once the scene is created, we can now tell blender to animate the graph based on the values in the cleaned up csv file:-
animate-graph.py
import csv import bpy def get_o3_naqi_category(o3_value): """ Takes O₃ concentration in µg/m³ (8-hour average) and returns a category number: 1 = Good, 2 = Satisfactory, 3 = Moderate, 4 = Poor, 5 = Very Poor, 6 = Severe """ if o3_value <= 50: return 1 # Good elif o3_value <= 100: return 2 # Satisfactory elif o3_value <= 168: return 3 # Moderate elif o3_value <= 208: return 4 # Poor elif o3_value <= 748: return 5 # Very Poor else: return 6 # Severe cur_frame = 0 # 109.26 max in camera (amravati as reference) max_in_camera_value_scale = 0.276 #(0:0.65) <=> 0:257.73 #max value max_value = 257.73 min_value = 0.65 length_ref = bpy.data.objects["length_reference"] with open('./air-quality-data-in-india/city_day (cleaned-up).csv', newline='') as csvfile: reader = csv.DictReader(csvfile) for row in reader: city = row['City'] value = row['O3'] bar = bpy.data.objects[city+'_bar'] flag = 0 try: float(value) except: flag = 1 pass if(flag==0): value = float(value) bar.scale[1] = value * min_value / max_value if (bar.scale[1] >= max_in_camera_value_scale): delta_perc = 1 - max_in_camera_value_scale - bar.scale[1] else: delta_perc = 1 length_ref.scale[1]=delta_perc length_ref.keyframe_insert(data_path='scale',frame=cur_frame) bar.keyframe_insert(data_path='scale', frame=cur_frame) color_start = (0.0, 0.0, get_o3_naqi_category(value)/6, 0.0) # Red (RGBA) bar.color = color_start bar.keyframe_insert(data_path="color", frame=cur_frame) cur_frame += 2
Once the keyframes have been set, you can preview the animation in the 3d view.
Graph with keyframes generated
As a bonus, using the timeline, you can also clean up the keyframes or even better,
add / duplicate a set of keyframes to fill in the gaps to simulate data cleanup.
Rendering the graph
Its time to add colours to the bars and render the video. Use emission shaders (pure light, no shadows) with clever driver magic to animate the color of the bars. That, and a complementary background color for the sky adds to the perfection.
Your graph should look something like this
Download doc