How I created it:
- I found a Wavefront OBJ file defining a wireframe of a cow from somewhere unremembered on the internet. Here’s a large collection of downloadable OBJ files. An OBJ file first lists the vertices of the object in 3D space, then a list of the each face of the object.
- In Tableau, each edge of the object could be described as the endpoints of of a line, so Tableau needed as its source data an x, y, and z coordinate, and an edge name.
- I wrote this Python script to convert an OBJ file into a CSV file that Tableau would be able to load. The script is at the end of this post.
- From this Wikipedia page about the 3D Rotation Group I found the formulae to rotate an object around the x, y and z axis. In a Tableau calculated field, Here is how the x coordinate is changed by rotation on the y axis:
[X:RotateZ] * cos( ([ThetaY] / 360) * 2 * PI() ) - [Z] * sin( ([ThetaY] / 360) * 2 * PI() )
- If you download the workbook, it includes a rotatable dodecahedron on another tab. That one you can animate the rotation as there's a theta on the page shelf (as opposed to be a parameter). For the theta, for each row in the CSV file, I repeated the coordinate data with a different theta value at 5° intervals.
- The previous mentioned Python script to convert an OBJ file to a CSV file suitable for Tableau:
import sys def print_line(vertex, edge): sys.stdout.write(('%s, %s, %s, "%d"\n') % (vertex, vertex, vertex, edge)) # Column names print "X, Y, Z, Edge" lineNum = 0 doneWithV = False faces =  vertices = [] f = open(sys.argv) for line in f: lineNum += 1 line = line.rstrip('#') line = line.strip() if len(line) == 0: continue vals = line.split() if vals == 'v': if doneWithV: print "V after F" sys.exit(0) if len(vals) != 4: print "Invalid line #", lineNum continue vertices.append(vals[1:]) elif vals == 'f': doneWithV = True faces.append(vals[1:]) edge = 0 for face in faces: # Add the first vertex to the end of the list of vertices # so there is a line from the last to the first vertex. face += [face] for i in range(len(face) - 1): print_line(vertices[int(face[i])], edge) print_line(vertices[int(face[i + 1])], edge) edge += 1;