This post explores how to plot a cube in ggplot2 using the threed library.
ggplot2 doesn’t include any notion of a 3rd spatial axis, so instead, after manipulating a 3d object, we use perspective projection to “flatten” its faces and vertices onto a 2d plane. These projected faces/vertices are what ggplot2 will plot.
Prepare an object for plotting Create an object (here the standard 2x2x2 cube is being used) Define where camera is located, and where it is looking Transform the object into camera space Perspective transform the data #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # The `threed` package has some builtin objects in `threed::mesh3dobj` #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ obj <- threed::mesh3dobj$cube #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Define camera 'lookat' matrix i.e. camera-to-world transform #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ camera_to_world <- threed::look_at_matrix(eye = c(1.5, 1.75, 4), at = c(0, 0, 0)) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Transform the object into camera space and do perspective projection #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ obj <- obj %>% transform_by(invert_matrix(camera_to_world)) %>% perspective_projection()as.data.frame(obj) %>% knitr::kable() element_id element_type vorder x y z vertex vnx vny vnz fnx fny fnz fcx fcy fcz zorder zorder_var hidden 1 4 1 -0.0945866 -0.0706857 1.581399 1 -0.1855445 -0.1356142 -0.9732328 0.1523493 0.1540033 -0.9762544 0.0748818 0.0756647 1.630932 1 1.630932 TRUE 1 4 2 -0.1077957 0.2603509 1.631559 3 -0.2154400 -0.3088482 -0.9263900 0.1523493 0.1540033 -0.9762544 0.0748818 0.0756647 1.630932 1 1.630932 TRUE 1 4 3 0.2693976 0.2400504 1.687219 4 -0.4345424 -0.3475777 -0.8308806 0.1523493 0.1540033 -0.9762544 0.0748818 0.0756647 1.630932 1 1.630932 TRUE 1 4 4 0.2325119 -0.1270569 1.623552 2 -0.3706042 -0.1407513 -0.9180640 0.1523493 0.1540033 -0.9762544 0.0748818 0.0756647 1.630932 1 1.630932 TRUE 2 4 1 -0.1077957 0.2603509 1.631559 3 -0.2154400 -0.3088482 -0.9263900 0.0000000 0.9394660 0.3426420 0.0013440 0.2085811 1.773502 5 1.773502 FALSE 2 4 2 -0.3483420 0.1903526 1.823482 7 0.1812743 0.2777220 0.9434035 0.0000000 0.9394660 0.3426420 0.0013440 0.2085811 1.773502 5 1.773502 FALSE 2 4 3 0.1921159 0.1435705 1.951750 8 -0.6699657 0.4605475 0.5822731 0.0000000 0.9394660 0.3426420 0.0013440 0.2085811 1.773502 5 1.773502 FALSE 2 4 4 0.2693976 0.2400504 1.687219 4 -0.4345424 -0.3475777 -0.8308806 0.0000000 0.9394660 0.3426420 0.0013440 0.2085811 1.773502 5 1.773502 FALSE 3 4 1 0.2325119 -0.1270569 1.623552 2 -0.3706042 -0.1407513 -0.9180640 0.9631644 -0.1369155 0.2314485 0.2119637 -0.0287422 1.767221 4 1.767221 FALSE 3 4 2 0.2693976 0.2400504 1.687219 4 -0.4345424 -0.3475777 -0.8308806 0.9631644 -0.1369155 0.2314485 0.2119637 -0.0287422 1.767221 4 1.767221 FALSE 3 4 3 0.1921159 0.1435705 1.951750 8 -0.6699657 0.4605475 0.5822731 0.9631644 -0.1369155 0.2314485 0.2119637 -0.0287422 1.767221 4 1.767221 FALSE 3 4 4 0.1538294 -0.3715328 1.806364 6 0.3411191 0.1021382 0.9344547 0.9631644 -0.1369155 0.2314485 0.2119637 -0.0287422 1.767221 4 1.767221 FALSE 4 4 1 -0.0945866 -0.0706857 1.581399 1 -0.1855445 -0.1356142 -0.9732328 -0.6370835 0.0905625 -0.7654561 -0.2099435 0.0306140 1.689395 3 1.689395 TRUE 4 4 2 -0.2890497 -0.2575617 1.721140 5 -0.1542710 -0.1026941 -0.9826771 -0.6370835 0.0905625 -0.7654561 -0.2099435 0.0306140 1.689395 3 1.689395 TRUE 4 4 3 -0.3483420 0.1903526 1.823482 7 0.1812743 0.2777220 0.9434035 -0.6370835 0.0905625 -0.7654561 -0.2099435 0.0306140 1.689395 3 1.689395 TRUE 4 4 4 -0.1077957 0.2603509 1.631559 3 -0.2154400 -0.3088482 -0.9263900 -0.6370835 0.0905625 -0.7654561 -0.2099435 0.0306140 1.689395 3 1.689395 TRUE 5 4 1 -0.0945866 -0.0706857 1.581399 1 -0.1855445 -0.1356142 -0.9732328 0.0000000 -0.5988573 -0.8008558 0.0006763 -0.2067093 1.683114 2 1.683114 TRUE 5 4 2 0.2325119 -0.1270569 1.623552 2 -0.3706042 -0.1407513 -0.9180640 0.0000000 -0.5988573 -0.8008558 0.0006763 -0.2067093 1.683114 2 1.683114 TRUE 5 4 3 0.1538294 -0.3715328 1.806364 6 0.3411191 0.1021382 0.9344547 0.0000000 -0.5988573 -0.8008558 0.0006763 -0.2067093 1.683114 2 1.683114 TRUE 5 4 4 -0.2890497 -0.2575617 1.721140 5 -0.1542710 -0.1026941 -0.9826771 0.0000000 -0.5988573 -0.8008558 0.0006763 -0.2067093 1.683114 2 1.683114 TRUE 6 4 1 -0.2890497 -0.2575617 1.721140 5 -0.1542710 -0.1026941 -0.9826771 -0.2439436 -0.2465920 0.9379147 -0.0728616 -0.0737928 1.825684 6 1.825684 FALSE 6 4 2 0.1538294 -0.3715328 1.806364 6 0.3411191 0.1021382 0.9344547 -0.2439436 -0.2465920 0.9379147 -0.0728616 -0.0737928 1.825684 6 1.825684 FALSE 6 4 3 0.1921159 0.1435705 1.951750 8 -0.6699657 0.4605475 0.5822731 -0.2439436 -0.2465920 0.9379147 -0.0728616 -0.0737928 1.825684 6 1.825684 FALSE 6 4 4 -0.3483420 0.1903526 1.823482 7 0.1812743 0.2777220 0.9434035 -0.2439436 -0.2465920 0.9379147 -0.0728616 -0.0737928 1.825684 6 1.825684 FALSE
Plot the points for the vertices of the object threed defines a fortify.mesh3d() function. If a mesh3d object is given as the data for a ggplot2 call, ggplot2 will automatically use fortify() to convert into a data.frame. i.e.because threed defines fortify.mesh3d() , we can call ggplot2 directly with a mesh3d object. ggplot(obj, aes(x, y)) + geom_point() + theme_void() + theme(legend.position = 'none') + coord_equal()Plot the outline of each polygon Each element has a unique element_id , and this is used as the group aesthetic to inform ggplot that it should draw one polygon for each element. Set fill = NA, colour = 'black' to draw only the borders of each polygon. ggplot(obj, aes(x, y, group = element_id)) + geom_polygon(fill = NA, colour = 'black', size = 0.2) + theme_void() + theme(legend.position = 'none') + coord_equal()
Dotted rendering of hidden lines To draw hidden elements in a different way, use the hidden variable. hidden is a boolean variable indicating if a triangle or quad element is facing away from the camera. Here a different linetype is used for hidden elements ggplot(obj, aes(x, y, group = element_id)) + geom_polygon(fill = NA, colour='black', aes(linetype = hidden, size = hidden)) + scale_linetype_manual(values = c('TRUE' = "dotted", 'FALSE' = 'solid')) + scale_size_manual(values = c('TRUE' = 0.2, 'FALSE' = 0.5)) + theme_void() + theme(legend.position = 'none') + coord_equal()
Hidden line removal Here a zero-width linetype is used for hidden elements to hide them. ggplot(obj, aes(x, y, group = element_id)) + geom_polygon(fill = NA, colour = 'black', aes(size = hidden)) + scale_size_manual(values = c('TRUE' = 0, 'FALSE' = 0.5)) + theme_void() + theme(legend.position = 'none') + coord_equal()
Naive Filled Polygons When filling polygons, ggplot2 will draw them in the order of the group variable. If we group by element_id then the polygons are drawn in the order in which they were defined. This means that polygons which are furt