Return to site

Data Visualisation in Blender - Part 1.2

Horizontal Bar Graphs

May 3, 2020

After Part 1 where we created column graphs, this tutorial is the same as the first but for a horizontal bar graph. Those of you who are experienced coders won't have a problem transforming your vertical column graph from the first tutorial but I made this version for those who are less experienced coders or for people who get stuck modifying the code from the first tutorial.

 

Conceptually, this tutorial is the same as the first which is why I've labelled it as Part 1.2 instead of Part 2. If you've done the first tutorial and aren't interested in this one you might want to jump to Part 2 which is on developing bar graph races.

Section 1. Workspace and modules

The main workspace that I used was the Scripting workspace, found along the top of your interface when you first open Blender. I set the 3D viewport in the top left-hand corner to the Top Orthogonal view so that we have x axis in the horizontal and y axis in the vertical. The only other workspace that I used was the Layout workspace which is the default when you open up Blender. I only used this workspace to animate once everything was generated.

 

In your Text Editor space you will need to click 'New' to create a new script. I suggest that you rename your script in the bar at the top of the Text Editor to something recognisable. Python comes installed with various modules that are built for different purposes. The bpy module is what we use to control Blender with Python. I've added the random module so we can use it later to generate random colours for materials.

 

The first line of your script will be the following:

Section 2. Defining the data

Here, we want to include the data that we want our graph to display. There are a few ways that this can be done but for this case we're just going to use a basic Python list to store our data. We're also going to make lists for the labels and ticks that we're going to use. Finally, we're also going to declare some variables in which we'll store strings that contain headings for our axes and graph title.

Please note, the data we use here is dummy data and has no meaning at all whatsoever.

Section 3. Defining dimensions

In this section we're going to take some time to pre-define how large our grids, bars, titles and labels are, as well as some variables that are going to help space these things out evenly across our graph. Doing these things beforehand allows you to make changes easily later and re-use your script for different sets of data.

Gridx and Gridy are the x and y lengths of the grid that we will generate for our graph. You can tweak these to change the size of the grid

labelSize is the size of the labels on the x axis

ticksize is the size of the labels on the y axis

titleSize is the size of the main title that will display at the top of the graoh

axisTitleSize is the size of the titles on the x and y axis

barWidth is the width of each bar that we will generate

nsubs is the number of subdivisions we want in our grid. We will need 11, not 10, in order to accurately display values in tenths. E.g. in our case we're going from 0 to 1. If we only had 10 subdivisions our values would only go from 0 to 0.9. Easy mistake to make.

Section 4. Generating a grid

We're finally ready to start building some of our bar graph in 3D. The first thing we're going to do is generate a grid for our bars to be displayed on. To do this we're going to use a little Python and a wireframe modifier.

The first line in this code snippet adds a grid to our scene in the same way as using the 'Add' menu in the main viewport. x_subdivisions and y_subdivisions are arguments we can use to tell Blender how many subdivisions we want in both axes of our grid. size is self-explanatory as is the location which we've set to the origin for ease.

In the second line we rescale the grid to the dimensions we specified in Gridx and Gridy.

In the third line we apply a wireframe modifier to change the native grid object in Blender to look like the sort of grid that we would like for our graph. Once you run your script, you can find this modifier in the modifier stack and change the options manually if you don't like the thickness of the lines etc.

At this point you may want to test your script to see if your grid does indeed appear in the viewport. To do this, first save your script and then go to Text>Run Script. A Grid should appear, hopefully without any errors. If the script does fail, you'll need to see the console in order to understand what went wrong. Go Window>Toggle System Console and scroll to the bottom to see the error message.

Note: Before you run your script again you must delete everything in the viewport. The default Camera and Light are the only exceptions.

Section 5. Generating bars

This is one of the main differences between this tutorial and the previous column graph one. Take some time to understand what is happening here and don't forget to try and test things out for yourself if it helps you to understand how the code works.

The first line defines the step which is the spacing between each of your bars. The second line calculates the step which is the spacing between each of the labels on the x axis. The third line calculates the y co-ordinates of your bars based on the step calculated in the first line.

This is the for loop which generates the actual cubes that will act as bars in our bar graph. Each bar is generated one at a time. currentBarWidth calculates the length of the bar based on the percentage value in our data and the length of our grid. currentBarLocationx calculates the x co-ordinate of our bars which is the same for all bars. We then add a new cube on the next line where x = currentBarLocationx, y = barLocation[i] and z is a standard value across all the bars.

The last six lines are for resizing the bars according to the thickness we defined in the barWidth variable and then for the length we calculated in currentBarWidth.

Section 6. Generating axis labels

The following code looks a bit bulky but this is due to the nature of text objects in Blender, which aren't very easy to manipulate with code.

The first line calculates the x co-ordinate of the current x label. The second line calculates the y co-ordinate of the x labels. The third line adds a new text object and puts it into edit mode.

In the first line of Changing the text, we have a list comprehension that deletes the default 'Text' from our text object.

The second line then types out the text we want from the tickLabels list.

The third line puts us back into object mode where we then change the origin of our text object to be at its centre instead of the default bottom left hand corner of the first letter. We then realign the location of our text with the new origin point location.

Everything under Rescale selects the current Text object and rescales it by the labelSize variable we created earlier in the script.

The code for the y labels is more or less the same as the x labels with some minor differences. We only have one calculation for the x axis location of all the text objects because the y axis locations are the same as the y axis location of the bar objects that we've already generated.

A new line is tacked onto the end of Changing the text. This line rotates the labels by 45 degrees. You can find the euler conversion of a degree value by opening up the properties menu in the 3D viewport (shortcut 'n') and hovering over any rotation field. A tooltip will come up with the euler value. Otherwise, you can Google conversions or use a calculator to perform them if you know how to do so.

Rescale is the same except that we've swapped labelSize with tickSize and swapped i for n since we're using Blender's default naming system and the object names increment by 1 for each new object created.

Section 7. Adding Titles

This code will be more or less the same as the code for the labels. The only difference is in the locations, the text variables and the size.

There are only a few things here to note. t was calculated previously at the start of the script. Use the line with bpy.context.object.location to tweak the location of the title.

Same as the main title except for location, scale factor and t+1.

Same as the x label titles except for rotation, location and t+2.

Section 8. Adding materials

Congrats! If you've made it this far you should have a bar graph built. However, it looks rather grey. You could manually create and assign new materials to your labels, bars and grid or you could get your Python script to do that for you.

Here, we've started with the labels, ticks and titles. Pretty much any object with 'Text' in it's name. Traditionally, these bits of text would just be coloured black so that's what we're going to do here. Of course, you could make your text any colour you like.

In the first line, we create a new material called 'LabelMaterial' which we assign to a variable called labelMat.

In the second line, we set the diffuse colour of the material to be black or (0,0,0,1).

In the third line the material 'LabelMaterial' in labelMat is added to every object with 'Text' in it's name.

In this last bit of code we're assigning a material for each object in our scene with 'Cube' in it's name (which should just be the bars). We're giving each material a colour using the random function in Python to generate 3 random numbers between 0 and 1. In the last line of the loop we're simply adding the materials to the objects as they are generated.

A final word from Kit.

I hope you all enjoyed this tutorial. There are some extra sections in Part 2 that explain how to animate your bars with hooks if you'd like to make a video render instead of an image render, as well as an appendix with code for generating cameras, lights and changing some basic render settings with your script. I've included an example render and my cleaned up version of this script at the end of this page. You can also download an offline version of this tutorial here.

If anyone has any questions or suggestions you can email me at: steambeanblog@gmail.com

Have a nice one guys, and stay safe. :)

Appendix