├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R ├── aaa.R └── decompose.R ├── README.Rmd ├── README.md └── man ├── assign_unique_vertex_indices.Rd ├── create_S.Rd ├── decompose.Rd ├── decompose_single.Rd ├── figures ├── README-unnamed-chunk-10-1.png ├── README-unnamed-chunk-11-1.png ├── README-unnamed-chunk-12-1.png ├── README-unnamed-chunk-12-2.png ├── README-unnamed-chunk-13-1.png ├── README-unnamed-chunk-13-2.png ├── README-unnamed-chunk-14-1.png ├── README-unnamed-chunk-15-1.png ├── README-unnamed-chunk-2-1.png ├── README-unnamed-chunk-3-1.png ├── README-unnamed-chunk-4-1.png ├── README-unnamed-chunk-5-1.png ├── README-unnamed-chunk-6-1.png ├── README-unnamed-chunk-7-1.png ├── README-unnamed-chunk-8-1.png ├── README-unnamed-chunk-9-1.png └── logo.png ├── simplify_polygon.Rd └── simplify_polygons.Rd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README.Rmd$ 4 | ^README.md$ 5 | ^working$ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Rhistory 3 | *.Rproj 4 | .Rproj.user 5 | *.swp 6 | *.txt 7 | inst/doc 8 | doc 9 | Meta 10 | pkgdown 11 | working 12 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: triangular 2 | Type: Package 3 | Title: Decompose Complex Polygons into Triangles 4 | Version: 0.1.8 5 | Author: mikefc 6 | Maintainer: mikefc 7 | Description: Decompose complex, self-intersecting polygons (which may contain 8 | holes) into sets of triangles. 9 | License: MIT + file LICENSE 10 | Encoding: UTF-8 11 | LazyData: true 12 | RoxygenNote: 7.1.1 13 | Imports: 14 | RTriangle, 15 | polyclip 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 mikefc@coolbutuseless.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(decompose) 4 | import(RTriangle) 5 | import(polyclip) 6 | importFrom(stats,aggregate) 7 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # triangular 0.1.8 2020-10-05 2 | 3 | * `decompose()` now passes through all other columns on the data.frame. However, 4 | the value in each column is enforced as a single unique value across the 5 | `group` i.e. you cannot have different values at different vertices within 6 | a single polygon `group`. 7 | 8 | 9 | # triangular 0.1.7 2020-10-01 10 | 11 | * Simplified return structure to just be the data.frame of triangles of interest 12 | * There's a segfault in RTriangle that doesn't occur if I use `polyclip::polysimplify()` 13 | for duplicated vertices. Resurrecting that code from v0.1.5. 14 | * If there aren't dupe verts then RTriangle is all that's needed. 15 | 16 | 17 | # triangular 0.1.6 2020-10-01 18 | 19 | * `polyclip` package is now only used for `pointinpolygon()` calculation 20 | 21 | # triangular 0.1.5 2020-10-01 22 | 23 | * Only use `polyclip::polysimplify()` if there are duplicated vertices, 24 | otherwise, all test examples work with `RTriangle::triangulate()` without 25 | any other processing. 26 | 27 | # triangular 0.1.4 2020-09-30 28 | 29 | * new `accetpable` column in `triangles_df` returned by `decompose()`. 30 | * `acceptable = interior & !too_thin` 31 | * where `too_thin` indicates triangles in which the centroid lies on the 32 | boundary, which makes them impossibly thin 33 | 34 | # triangular 0.1.3 2020-09-28 35 | 36 | * Combine `polyclip::polysimplify()` with `RTriangle::triangulate()` to now 37 | cope with: 38 | * holes in polygons 39 | * self intersecting polygons 40 | * polygons with duplicated vertices 41 | 42 | # triangular 0.1.2 2020-09-27 43 | 44 | * Refactor: Now use `polyclip` package for point-in-polygon calculations. 45 | Removed bespoke point-in-polygon R functions from this package. 46 | 47 | # triangular 0.1.1 2020-09-27 48 | 49 | * Refactor: abstracting 'point-in-polygons' 50 | 51 | # triangular 0.1.0 2020-09-26 52 | 53 | * Initial release 54 | -------------------------------------------------------------------------------- /R/aaa.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/R/aaa.R -------------------------------------------------------------------------------- /R/decompose.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | #' Simplify a single polygon 5 | #' 6 | #' @param polygon_df data.frame containing a single polygon (x, y) 7 | #' 8 | #' @import polyclip 9 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | simplify_polygon <- function(polygon_df) { 11 | 12 | sp <- polyclip::polysimplify(polygon_df) 13 | sp <- lapply(seq_along(sp), function(ii) { 14 | res <- as.data.frame(sp[[ii]]) 15 | res$group <- polygon_df$group[1] 16 | res$subgroup <- polygon_df$subgroup[1] + ii/10000 17 | res 18 | }) 19 | sp <- do.call(rbind, sp) 20 | 21 | sp 22 | } 23 | 24 | 25 | 26 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 27 | #' Simplify a polygon made up of groups and subgroups 28 | #' 29 | #' @param polygons_df data.frame containing multiple polygons 30 | #' distinguished by group and subgroup 31 | #' 32 | #' @import polyclip 33 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 | simplify_polygons <- function(polygons_df) { 35 | 36 | polygons_list <- split(polygons_df, polygons_df$subgroup) 37 | 38 | sp <- lapply(seq_along(polygons_list), function(ii) { 39 | simplify_polygon(polygons_list[[ii]]) 40 | }) 41 | 42 | sp <- do.call(rbind, sp) 43 | 44 | sp 45 | } 46 | 47 | 48 | 49 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 50 | #' Assign unique vertex indices to all points 51 | #' 52 | #' This will be used later to de-dupe the input data for RTriangle::triangulate() 53 | #' 54 | #' @param polygons_df data.frame containing multiple polygons 55 | #' distinguished by group and subgroup 56 | #' 57 | #' @return inpput wit new 'vidx' and 'dupe' columns 58 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 59 | assign_unique_vertex_indices <- function(polygons_df) { 60 | 61 | polygons_df$dupe <- duplicated(polygons_df[, c('x', 'y')]) 62 | 63 | polygons_df$vidx <- as.integer(as.factor(with(polygons_df, interaction(x, y)))) 64 | polygons_df$vidx <- match(polygons_df$vidx, unique(polygons_df$vidx)) 65 | 66 | polygons_df 67 | } 68 | 69 | 70 | 71 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 | #' Create the S matrix for RTriangle::pslg() for a single polygon 73 | #' 74 | #' pslg = Planar Straight Line Graph object. A collection of vertices and segments. 75 | #' 76 | #' The 'S' matrix is: A 2-column matrix of segments in which each row is a 77 | #' segment. Segments are edges whose endpoints are vertices in the PSLG, and 78 | #' whose presence in any mesh generated from the PSLG is enforced. Each 79 | #' segment refers to the indices in V of the endpoints of the segment. By 80 | #' default the segments are not specified (NA), in which case the convex 81 | #' hull of the vertices are taken to be the segments. Any vertices outside 82 | #' the region enclosed by the segments are eaten away by the triangulation 83 | #' algorithm. If the segments do not enclose a region the whole triangulation 84 | #' may be eaten away. 85 | #' 86 | #' @param polygon_df data.frame containing a single polygon. Must have 'vidx' column 87 | #' 88 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 89 | create_S <- function(polygon_df) { 90 | v1 <- polygon_df$vidx 91 | v2 <- c(polygon_df$vidx[-1], polygon_df$vidx[1]) 92 | 93 | matrix(c(v1, v2), ncol = 2) 94 | } 95 | 96 | 97 | 98 | 99 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100 | #' Inner function to decompose a single polygon 'group' 101 | #' 102 | #' @inheritParams decompose 103 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 104 | decompose_single <- function(polygons_df) { 105 | stopifnot(length(unique(polygons_df$group)) == 1) 106 | 107 | 108 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 109 | # If there are any duplicate vertices, then simplify the polygon first 110 | # with `polyclip`. 111 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 112 | if (anyDuplicated(polygons_df[,c('x', 'y')])) { 113 | polygons_df_1 <- simplify_polygons(polygons_df) 114 | polygons_df_2 <- assign_unique_vertex_indices(polygons_df_1) 115 | } else { 116 | polygons_df_2 <- assign_unique_vertex_indices(polygons_df) 117 | } 118 | polygons_list <- split(polygons_df_2, polygons_df_2$subgroup) 119 | 120 | 121 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 122 | # Create an S matrix for each group/subgroup and combine into single matrix 123 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 124 | S <- lapply(seq_along(polygons_list), function(i) { 125 | create_S(polygons_list[[i]]) 126 | }) 127 | S <- do.call(rbind, S) 128 | 129 | 130 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 131 | # Only want the unique vertices passed to RTriangle 132 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 133 | unique_verts <- as.matrix(polygons_df_2[!polygons_df_2$dupe, c('x', 'y')]) 134 | 135 | 136 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 137 | # Properties to save/restore. 138 | # This assumes that the properties across a polygon 'group' are all 139 | # identical 140 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 141 | property_cols <- setdiff(colnames(polygons_df), c('x', 'y', 'group', 'subgroup')) 142 | properties_df <- polygons_df[1, property_cols, drop = FALSE] 143 | 144 | 145 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 146 | # Use RTriangle to triangulate the groups/subgroups 147 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 148 | ps <- RTriangle::pslg(P = unique_verts, S = S) 149 | tt <- RTriangle::triangulate(ps) 150 | 151 | 152 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 153 | # Result from RTriangle::triangulate(): 154 | # 155 | # T Triangulation specified as 3 column matrix in which each row 156 | # contains indices in P of vertices. 157 | # E Set of edges in the triangulation. 158 | # EB Boundary markers of edges. For each edge this is 1 if the point is 159 | # on a boundary of the triangulation and 0 otherwise. 160 | # VP The points of the Voronoi tessalation as a 2-column matrix 161 | # VE Set of edges of the Voronoi tessalation. An index of -1 indicates an 162 | # infinite ray. 163 | # VN Directions of infinite rays of Voroni tessalation as a 2-column 164 | # matrix with the same number of rows as VP. 165 | # VA Matrix of attributes associated with the polygons of the Voronoi 166 | # tessalation. 167 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 168 | triangles_df <- as.data.frame(tt$P) # the original points 169 | idx <- as.vector(t(tt$T)) # indices (in groups of 3) indicating vertices of tris 170 | colnames(triangles_df) <- c('x', 'y') # Set actual colnames 171 | triangles_df <- triangles_df[idx,] # Replicate the vertices according to 'tt$T' 172 | N <- nrow(triangles_df)/3 # Assign an index to each triangle 173 | triangles_df$vert <- idx 174 | triangles_df$idx <- rep(seq_len(N), each = 3) 175 | 176 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 177 | # Calculate the centroid of each triangle 178 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 179 | centroids <- aggregate(triangles_df[,c('x', 'y')], by=list(triangles_df$idx), FUN=mean) 180 | 181 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 182 | # Use polyclip to do testing of point in polygon. 183 | # polyclip::pointinpolygon() only tests a set of points against a single 184 | # polygon, so have to calculations over all polygons and then accumulate 185 | # the result with a `Reduce()` call 186 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 187 | P <- list(x = centroids$x, y = centroids$y) 188 | pip <- lapply(polygons_list, function(A) polyclip::pointinpolygon(P, A)) 189 | 190 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 191 | # if centroid lies on the boundary pointinpolygon() return -1 192 | # and the only way a triangle centroid can lie on the boundary: the triangle 193 | # is so thin that numerical error lets it (a) exist as a triangle, (b) but 194 | # the cnetroid of the triangle is on the boundary. delete these 195 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 196 | too_thin <- do.call(pmin, pip) == -1 197 | 198 | 199 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 200 | # Prep dataframe for return. Filter out triangles which aren't acceptable 201 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 202 | ncrosses <- Reduce(`+`, pip) 203 | interior <- ncrosses %% 2 == 1 204 | 205 | inside <- data.frame(idx = seq_along(interior), acceptable = interior & !too_thin) 206 | triangles_df <- merge(triangles_df, inside) 207 | triangles_df <- subset(triangles_df, triangles_df$acceptable) 208 | 209 | triangles_df$acceptable <- NULL 210 | triangles_df$vert <- NULL 211 | 212 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 213 | # Join back in the properties of this polygon 214 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 215 | triangles_df <- cbind(triangles_df, properties_df) 216 | 217 | 218 | triangles_df 219 | } 220 | 221 | 222 | 223 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 224 | #' Decompose complex polygons into triangles 225 | #' 226 | #' This function wraps \code{RTriangle::triangulate()} to make it easier to 227 | #' call for my use case, and returns results appropriate for plotting in 228 | #' \code{ggplot2}. 229 | #' 230 | #' By combining \code{polyclip} and \code{RTriangle} packages, this package 231 | #' will successfully decompose into triangles the following polygon types: 232 | #' 233 | #' \itemize{ 234 | #' \item{Simple polygons} 235 | #' \item{Polyons with holes} 236 | #' \item{Multiple polygons with holes} 237 | #' \item{Polygons with self-intersection} 238 | #' \item{Polygons with duplicated vertices} 239 | #' } 240 | #' 241 | #' @param polygons_df polygon data.frame with \code{x,y} coordinates, 'group' 242 | #' column denoting coordinates which belong to the same group, 243 | #' and 'subgroup' indicating holes within that polygon. It is assumed 244 | #' that any other column values are consistent across the entire group 245 | #' e.g. 'fill' 246 | #' 247 | #' @return data.frame with \code{x}, \code{y} coordinates of the vertices of the 248 | #' resulting triangles. Also includes \code{idx} numbering the triangles. 249 | #' 250 | #' @import RTriangle 251 | #' @import polyclip 252 | #' @importFrom stats aggregate 253 | #' 254 | #' @export 255 | #' 256 | #' @examples 257 | #' \dontrun{ 258 | #' # Polygon with a hole 259 | #' polygons_df <- df <- data.frame( 260 | #' x = c(4, 8, 8, 4, 6, 7, 7, 6), 261 | #' y = c(4, 4, 8, 8, 6, 6, 7, 7), 262 | #' group = c(1, 1, 1, 1, 1, 1, 1, 1), 263 | #' subgroup = c(1, 1, 1, 1, 2, 2, 2, 2) 264 | #' ) 265 | #' triangles_df <- triangular::decompose(polygons_df) 266 | #' ggplot(triangles_df) + 267 | #' geom_polygon(aes(x, y, fill = as.factor(idx))) 268 | #' } 269 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 270 | decompose <- function(polygons_df) { 271 | 272 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 273 | # Polygons must be supplied in a data.frame with group/subgroup designations 274 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 275 | stopifnot(is.data.frame(polygons_df)) 276 | stopifnot('group' %in% names(polygons_df)) 277 | stopifnot('subgroup' %in% names(polygons_df)) 278 | stopifnot('x' %in% names(polygons_df)) 279 | stopifnot('y' %in% names(polygons_df)) 280 | 281 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 282 | # polyclip::polysimplify() can be touchy on 'x', and 'y' being plain 283 | # numeric values. So ensure conversion here. 'ggplot2' also sometimes 284 | # add some weird classes to numeric columns in data.frames for plotting, 285 | # and this will drop the classes which confuse polyclip 286 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 287 | polygons_df$x <- as.numeric(polygons_df$x) 288 | polygons_df$y <- as.numeric(polygons_df$y) 289 | 290 | 291 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 292 | # For each polygon group, decompose into triangles 293 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 294 | polygon_groups <- split(polygons_df, polygons_df$group) 295 | triangles_df_list <- lapply( 296 | polygon_groups, 297 | decompose_single 298 | ) 299 | triangles_df <- do.call(rbind, triangles_df_list) 300 | 301 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 302 | # Final triangle numbering 303 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 304 | N <- nrow(triangles_df)/3 # Assign an index to each triangle 305 | triangles_df$idx <- rep(seq_len(N), each = 3) 306 | 307 | triangles_df 308 | } 309 | 310 | 311 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = FALSE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "60%" 13 | ) 14 | 15 | library(ggplot2) 16 | library(dplyr) 17 | library(triangular) 18 | ``` 19 | 20 | # triangular 21 | 22 | 23 | ![](https://img.shields.io/badge/cool-useless-green.svg) 24 | [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental) 25 | 26 | 27 | `triangular` decomposes complex polygons into sets of triangles and works with: 28 | 29 | * polygons with holes 30 | * self-intersecting polygons 31 | * polygons with duplicated vertices 32 | 33 | 34 | ## Licensing 35 | 36 | While the code in this package is licensed under [MIT](https://mit-license.org/), this package relies heavily on: 37 | 38 | * [RTriangle](https://cran.r-project.org/package=RTriangle) 39 | which is licensed [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0). 40 | * [polyclip](https://cran.r-project.org/package=polyclip) which is licensed under [BSL](https://www.boost.org/LICENSE_1_0.txt) 41 | 42 | ## Installation 43 | 44 | You can install from [GitHub](https://github.com/coolbutuseless/triangular) with: 45 | 46 | ``` r 47 | # install.package('remotes') 48 | remotes::install_github('coolbutuseless/triangular') 49 | ``` 50 | 51 | 52 | ## Polygon with a Hole in it 53 | 54 | #### `ggplot2` rendering of polygon with a hole in it 55 | 56 | ```{r} 57 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 | # polygons_df - data.frame of polygon vertices with group/subgroups 59 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 60 | polygons_df <- df <- data.frame( 61 | x = c(4, 8, 8, 4, 6, 7, 7, 6), 62 | y = c(4, 4, 8, 8, 6, 6, 7, 7), 63 | group = c(1, 1, 1, 1, 1, 1, 1, 1), 64 | subgroup = c(1, 1, 1, 1, 2, 2, 2, 2) 65 | ) 66 | 67 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 68 | # How 'ggplot2' handles this case 69 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | ggplot(polygons_df) + 71 | geom_polygon(aes(x, y, group=group, subgroup=subgroup)) + 72 | geom_path(aes(x, y, group = interaction(group, subgroup))) + 73 | theme_bw() + 74 | coord_equal() + 75 | labs(title = "ggplot2 rendering of original polygon(s)") 76 | ``` 77 | 78 | 79 | #### `{triangular}` decomposition of a polygon with a hole 80 | 81 | ```{r} 82 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 83 | # Turn the polygon data.frame into individual triangles 84 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 85 | triangles_df <- triangular::decompose(polygons_df) 86 | 87 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 88 | # Plot the triangles 89 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 90 | ggplot(triangles_df) + 91 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 92 | theme_bw() + 93 | coord_equal() + 94 | labs(title = "Decomposition into simple tris with {triangular}") + 95 | theme(legend.position = 'none') 96 | ``` 97 | 98 | 99 | 100 | 101 | ## Polygon with Two Holes 102 | 103 | #### `ggplot2` rendering of polygon with two holes 104 | 105 | ```{r} 106 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 107 | # polygon 108 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 109 | polygons_df <- data.frame( 110 | x = c(4, 8, 8, 4, 6, 7, 7, 6, 4.5, 5, 5, 4.5), 111 | y = c(4, 4, 8, 8, 6, 6, 7, 7, 4.5, 4.5, 5, 5), 112 | group = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 113 | subgroup = c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3) 114 | ) 115 | 116 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 117 | # "Native" ggplot2 rendering 118 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 119 | ggplot(polygons_df) + 120 | geom_polygon(aes(x, y, subgroup = subgroup)) + 121 | theme_bw() + 122 | coord_equal() + 123 | labs(title = "ggplot2 rendering of original polygon(s)") 124 | ``` 125 | 126 | 127 | #### `{triangular}` decomposition of a polygon with two holes 128 | 129 | ```{r} 130 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 131 | # Decompose into triangles 132 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 133 | triangles_df <- triangular::decompose(polygons_df) 134 | 135 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 136 | # Plot the `triangular` decomposition into triangles 137 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 138 | ggplot(triangles_df) + 139 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 140 | theme_bw() + 141 | coord_equal() + 142 | labs(title = "Decomposition into simple tris with {triangular}") + 143 | theme(legend.position = 'none') 144 | ``` 145 | 146 | 147 | ## Two Polygons with One Hole Each 148 | 149 | #### `ggplot2` rendering of muliple polygons with holes 150 | 151 | ```{r} 152 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 153 | # Two polygons with one hole each 154 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 155 | polygons_df <- data.frame( 156 | x = c(1, 4, 4, 1, 2, 3, 3, 2, 5, 8, 8, 5, 6, 7, 7, 6), 157 | y = c(1, 1, 4, 4, 2, 2, 3, 3, 5, 5, 8, 8, 6, 6, 7, 7), 158 | group = c(1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2), 159 | subgroup = c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4), 160 | fill = rep(c('tomato', 'skyblue'), each = 8) 161 | ) 162 | 163 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 164 | # "Native" ggplot2 rendering 165 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 166 | ggplot(polygons_df) + 167 | geom_polygon(aes(x, y, subgroup = subgroup, fill = I(fill))) + 168 | theme_bw() + 169 | coord_equal() + 170 | labs(title = "ggplot2 rendering of original polygon(s)") 171 | ``` 172 | 173 | 174 | #### triangular decomposition (Two Polygons with One Hole Each) 175 | 176 | ```{r} 177 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 178 | # Decompose into triangles 179 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 180 | triangles_df <- triangular::decompose(polygons_df) 181 | 182 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 183 | # Plot the `triangular` decomposition into triangles 184 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 185 | ggplot(triangles_df) + 186 | geom_polygon(aes(x, y, fill = I(fill), group = idx), colour = 'grey20', size = 0.15) + 187 | theme_bw() + 188 | coord_equal() + 189 | labs(title = "Decomposition into simple tris with {triangular}") + 190 | theme(legend.position = 'none') 191 | ``` 192 | 193 | 194 | ## Polygon with duplicated vertex 195 | 196 | This happens sometimes with `ggplot2::geom_density()` 197 | 198 | #### `ggplot2` rendering of polygon with duplicated vertices 199 | 200 | ```{r error = TRUE} 201 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 202 | # Polygon with duplicated vertex 203 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 204 | polygons_df <- data.frame( 205 | x = c(1, 2, 2, 1, 2, 3, 3, 2), 206 | y = c(1, 1, 2, 2, 2, 2, 3, 3), 207 | group = c(1, 1, 1, 1, 2, 2, 2, 2), 208 | subgroup = 1, 209 | fill = rep(c('tomato', 'skyblue'), each = 4) 210 | ) 211 | 212 | 213 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 214 | # "Native" ggplot2 rendering 215 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 216 | ggplot(polygons_df) + 217 | geom_polygon(aes(x, y, group = group, fill = I(fill))) + 218 | theme_bw() + 219 | coord_equal() + 220 | labs(title = "ggplot2 rendering of original polygon(s)") 221 | ``` 222 | 223 | 224 | 225 | 226 | #### `triangular` decomposition - (Polygon with duplicated vertex) 227 | 228 | 229 | ```{r} 230 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 231 | # Decompose into triangles 232 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 233 | triangles_df <- triangular::decompose(polygons_df) 234 | 235 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 236 | # Plot the `triangular` decomposition into triangles 237 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 238 | ggplot(triangles_df) + 239 | geom_polygon(aes(x, y, fill = I(fill), group = idx), colour = 'grey20', size = 0.15) + 240 | theme_bw() + 241 | coord_equal() + 242 | labs(title = "Decomposition into simple tris with {triangular}") + 243 | theme(legend.position = 'none') 244 | ``` 245 | 246 | 247 | 248 | ## Polygon from Random Points 249 | 250 | #### `ggplot2` rendering of a Polygon made from Random Points) 251 | 252 | ```{r} 253 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 254 | # 10 random points 255 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 256 | set.seed(1) 257 | polygons_df <- data.frame( 258 | x = runif(10), 259 | y = runif(10), 260 | group = 1, 261 | subgroup = 1 262 | ) 263 | 264 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 265 | # "Native" ggplot2 rendering 266 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 267 | ggplot(polygons_df) + 268 | geom_polygon(aes(x, y), fill = 'skyblue') + 269 | geom_path(aes(x, y, group = interaction(group, subgroup))) + 270 | theme_bw() + 271 | coord_equal() + 272 | labs(title = "ggplot2 rendering of original polygon(s)") 273 | ``` 274 | 275 | 276 | #### `triangular` decomposition - (Polygon from Random Points) 277 | 278 | 279 | ```{r} 280 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 281 | # Decompose into triangles 282 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 283 | triangles_df <- triangular::decompose(polygons_df) 284 | 285 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 286 | # Plot the `triangular` decomposition into triangles 287 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 288 | ggplot(triangles_df) + 289 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 290 | theme_bw() + 291 | coord_equal() + 292 | labs(title = "Decomposition into simple tris with {triangular}") + 293 | theme(legend.position = 'none') 294 | ``` 295 | 296 | 297 | 298 | 299 | ## Triforce 300 | 301 | Triforce 302 | ------------------------------------------------------------------------------ 303 | 304 | 305 | ```{r} 306 | d <- sqrt(3)/2 307 | 308 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 309 | # Single triangle with a triangular hole 310 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 311 | polygons_df <- df <- data.frame( 312 | x = c(0, 1, 0.5, 0.25, 0.5, 0.75), 313 | y = c(0, 0, d , d/2 , 0 , d/2), 314 | subgroup = c(1, 1, 1 , 2, 2, 2), 315 | group = 1L 316 | ) 317 | 318 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 319 | # "Native" ggplot2 rendering 320 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 321 | ggplot(polygons_df) + 322 | geom_polygon(aes(x, y, subgroup = subgroup)) + 323 | geom_path(aes(x, y, group = interaction(group, subgroup))) + 324 | theme_bw() + 325 | coord_equal() + 326 | labs(title = "ggplot2 rendering of original polygon") 327 | ``` 328 | 329 | 330 | ```{r} 331 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 332 | # decompose into simple triangular pieces i.e. without holes or self-intersections 333 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 334 | triangles_df <- triangular::decompose(polygons_df) 335 | 336 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 337 | # Plot the decomposition into triangles 338 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 339 | ggplot(triangles_df) + 340 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 341 | theme_bw() + 342 | coord_equal() + 343 | labs(title = "{triangular} decomposition of single polygon (with hole)\n into multiple simple polygons") + 344 | theme(legend.position = 'none') 345 | 346 | ``` 347 | 348 | 349 | 350 | 351 | 352 | 353 | ## Logo 354 | 355 | #### `ggplot2` rendering nested hexagons 356 | 357 | ```{r} 358 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 359 | # polygons_df - data.frame of polygon vertices with group/subgroups 360 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 361 | r <- 5 362 | hex_df <- data.frame( 363 | x = r * cos(seq(30, 360, 60) * pi/180), 364 | y = r * sin(seq(30, 360, 60) * pi/180), 365 | group = 1, 366 | subgroup = 1 367 | ) 368 | 369 | hole_df <- data.frame( 370 | x = 2 * cos(seq(30, 360, 60) * pi/180) + 1, 371 | y = 2 * sin(seq(30, 360, 60) * pi/180) + 1, 372 | group = 1, 373 | subgroup = 2 374 | ) 375 | 376 | polygons_df <- rbind(hex_df, hole_df) 377 | 378 | 379 | 380 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 381 | # How 'ggplot2' handles this case 382 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 383 | ggplot(polygons_df) + 384 | geom_polygon(aes(x, y, group=group, subgroup=subgroup)) + 385 | geom_path(aes(x, y, group = interaction(group, subgroup))) + 386 | theme_bw() + 387 | coord_equal() + 388 | labs(title = "ggplot2 rendering of original polygon(s)") 389 | ``` 390 | 391 | 392 | #### `{triangular}` decomposition of nested hexagons 393 | 394 | ```{r} 395 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 396 | # Turn the polygon data.frame into individual triangles 397 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 398 | triangles_df <- triangular::decompose(polygons_df) 399 | 400 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 401 | # Plot the triangles 402 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 403 | p <- ggplot(triangles_df) + 404 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 405 | theme_void() + 406 | coord_equal() + 407 | theme(legend.position = 'none') + 408 | scale_fill_viridis_d(option = 'D') + 409 | annotate('text', x = 1, y = 1.4, label = 't\nr i\na n g\nu l a r', 410 | size = 5.5, family = 'mono', fontface = 'bold') 411 | 412 | ggsave("man/figures/logo.png", plot = p, width = 5, height = 5) 413 | 414 | p 415 | ``` 416 | 417 | 418 | 419 | ## Related Software 420 | 421 | * [silicate](https://cran.r-project.org/package=silicate) + [anglr](https://cran.r-project.org/package=anglr) do all of what triangular does (and more!), with a 422 | much more configurable interface (that I did not need) 423 | * [decido](https://cran.r-project.org/package=decido) does fast ear-cutting triangulation. 424 | Unfortunately, this doesn't 425 | deal nicely with self-intersecting polygons. 426 | 427 | ## Acknowledgements 428 | 429 | * [Michael Sumner](https://twitter.com/mdsumner) on twitter for his in-depth technical advice on 430 | graphics and and triangles in R 431 | * R Core for developing and maintaining such a wonderful language. 432 | * CRAN maintainers, for patiently shepherding packages onto CRAN and maintaining 433 | the repository 434 | 435 | 436 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # triangular 5 | 6 | 7 | 8 | ![](https://img.shields.io/badge/cool-useless-green.svg) [![Lifecycle: 9 | experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental) 10 | 11 | 12 | `triangular` decomposes complex polygons into sets of triangles and 13 | works with: 14 | 15 | - polygons with holes 16 | - self-intersecting polygons 17 | - polygons with duplicated vertices 18 | 19 | ## Licensing 20 | 21 | While the code in this package is licensed under 22 | [MIT](https://mit-license.org/), this package relies heavily on: 23 | 24 | - [RTriangle](https://cran.r-project.org/package=RTriangle) which is 25 | licensed [CC 26 | BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0). 27 | - [polyclip](https://cran.r-project.org/package=polyclip) which is 28 | licensed under [BSL](https://www.boost.org/LICENSE_1_0.txt) 29 | 30 | ## Installation 31 | 32 | You can install from 33 | [GitHub](https://github.com/coolbutuseless/triangular) with: 34 | 35 | ``` r 36 | # install.package('remotes') 37 | remotes::install_github('coolbutuseless/triangular') 38 | ``` 39 | 40 | ## Polygon with a Hole in it 41 | 42 | #### `ggplot2` rendering of polygon with a hole in it 43 | 44 | ``` r 45 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 46 | # polygons_df - data.frame of polygon vertices with group/subgroups 47 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | polygons_df <- df <- data.frame( 49 | x = c(4, 8, 8, 4, 6, 7, 7, 6), 50 | y = c(4, 4, 8, 8, 6, 6, 7, 7), 51 | group = c(1, 1, 1, 1, 1, 1, 1, 1), 52 | subgroup = c(1, 1, 1, 1, 2, 2, 2, 2) 53 | ) 54 | 55 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 56 | # How 'ggplot2' handles this case 57 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 | ggplot(polygons_df) + 59 | geom_polygon(aes(x, y, group=group, subgroup=subgroup)) + 60 | geom_path(aes(x, y, group = interaction(group, subgroup))) + 61 | theme_bw() + 62 | coord_equal() + 63 | labs(title = "ggplot2 rendering of original polygon(s)") 64 | ``` 65 | 66 | 67 | 68 | #### `{triangular}` decomposition of a polygon with a hole 69 | 70 | ``` r 71 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 | # Turn the polygon data.frame into individual triangles 73 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | triangles_df <- triangular::decompose(polygons_df) 75 | 76 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 77 | # Plot the triangles 78 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 79 | ggplot(triangles_df) + 80 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 81 | theme_bw() + 82 | coord_equal() + 83 | labs(title = "Decomposition into simple tris with {triangular}") + 84 | theme(legend.position = 'none') 85 | ``` 86 | 87 | 88 | 89 | ## Polygon with Two Holes 90 | 91 | #### `ggplot2` rendering of polygon with two holes 92 | 93 | ``` r 94 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 95 | # polygon 96 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 97 | polygons_df <- data.frame( 98 | x = c(4, 8, 8, 4, 6, 7, 7, 6, 4.5, 5, 5, 4.5), 99 | y = c(4, 4, 8, 8, 6, 6, 7, 7, 4.5, 4.5, 5, 5), 100 | group = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 101 | subgroup = c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3) 102 | ) 103 | 104 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 105 | # "Native" ggplot2 rendering 106 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 107 | ggplot(polygons_df) + 108 | geom_polygon(aes(x, y, subgroup = subgroup)) + 109 | theme_bw() + 110 | coord_equal() + 111 | labs(title = "ggplot2 rendering of original polygon(s)") 112 | ``` 113 | 114 | 115 | 116 | #### `{triangular}` decomposition of a polygon with two holes 117 | 118 | ``` r 119 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120 | # Decompose into triangles 121 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 122 | triangles_df <- triangular::decompose(polygons_df) 123 | 124 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 125 | # Plot the `triangular` decomposition into triangles 126 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 127 | ggplot(triangles_df) + 128 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 129 | theme_bw() + 130 | coord_equal() + 131 | labs(title = "Decomposition into simple tris with {triangular}") + 132 | theme(legend.position = 'none') 133 | ``` 134 | 135 | 136 | 137 | ## Two Polygons with One Hole Each 138 | 139 | #### `ggplot2` rendering of muliple polygons with holes 140 | 141 | ``` r 142 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 143 | # Two polygons with one hole each 144 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 145 | polygons_df <- data.frame( 146 | x = c(1, 4, 4, 1, 2, 3, 3, 2, 5, 8, 8, 5, 6, 7, 7, 6), 147 | y = c(1, 1, 4, 4, 2, 2, 3, 3, 5, 5, 8, 8, 6, 6, 7, 7), 148 | group = c(1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2), 149 | subgroup = c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4), 150 | fill = rep(c('tomato', 'skyblue'), each = 8) 151 | ) 152 | 153 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 154 | # "Native" ggplot2 rendering 155 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 156 | ggplot(polygons_df) + 157 | geom_polygon(aes(x, y, subgroup = subgroup, fill = I(fill))) + 158 | theme_bw() + 159 | coord_equal() + 160 | labs(title = "ggplot2 rendering of original polygon(s)") 161 | ``` 162 | 163 | 164 | 165 | #### triangular decomposition (Two Polygons with One Hole Each) 166 | 167 | ``` r 168 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 169 | # Decompose into triangles 170 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 171 | triangles_df <- triangular::decompose(polygons_df) 172 | 173 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 174 | # Plot the `triangular` decomposition into triangles 175 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 176 | ggplot(triangles_df) + 177 | geom_polygon(aes(x, y, fill = I(fill), group = idx), colour = 'grey20', size = 0.15) + 178 | theme_bw() + 179 | coord_equal() + 180 | labs(title = "Decomposition into simple tris with {triangular}") + 181 | theme(legend.position = 'none') 182 | ``` 183 | 184 | 185 | 186 | ## Polygon with duplicated vertex 187 | 188 | This happens sometimes with `ggplot2::geom_density()` 189 | 190 | #### `ggplot2` rendering of polygon with duplicated vertices 191 | 192 | ``` r 193 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 194 | # Polygon with duplicated vertex 195 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 196 | polygons_df <- data.frame( 197 | x = c(1, 2, 2, 1, 2, 3, 3, 2), 198 | y = c(1, 1, 2, 2, 2, 2, 3, 3), 199 | group = c(1, 1, 1, 1, 2, 2, 2, 2), 200 | subgroup = 1, 201 | fill = rep(c('tomato', 'skyblue'), each = 4) 202 | ) 203 | 204 | 205 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 206 | # "Native" ggplot2 rendering 207 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 208 | ggplot(polygons_df) + 209 | geom_polygon(aes(x, y, group = group, fill = I(fill))) + 210 | theme_bw() + 211 | coord_equal() + 212 | labs(title = "ggplot2 rendering of original polygon(s)") 213 | ``` 214 | 215 | 216 | 217 | #### `triangular` decomposition - (Polygon with duplicated vertex) 218 | 219 | ``` r 220 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 221 | # Decompose into triangles 222 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 223 | triangles_df <- triangular::decompose(polygons_df) 224 | 225 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 226 | # Plot the `triangular` decomposition into triangles 227 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 228 | ggplot(triangles_df) + 229 | geom_polygon(aes(x, y, fill = I(fill), group = idx), colour = 'grey20', size = 0.15) + 230 | theme_bw() + 231 | coord_equal() + 232 | labs(title = "Decomposition into simple tris with {triangular}") + 233 | theme(legend.position = 'none') 234 | ``` 235 | 236 | 237 | 238 | ## Polygon from Random Points 239 | 240 | #### `ggplot2` rendering of a Polygon made from Random Points) 241 | 242 | ``` r 243 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 244 | # 10 random points 245 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 246 | set.seed(1) 247 | polygons_df <- data.frame( 248 | x = runif(10), 249 | y = runif(10), 250 | group = 1, 251 | subgroup = 1 252 | ) 253 | 254 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 255 | # "Native" ggplot2 rendering 256 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 257 | ggplot(polygons_df) + 258 | geom_polygon(aes(x, y), fill = 'skyblue') + 259 | geom_path(aes(x, y, group = interaction(group, subgroup))) + 260 | theme_bw() + 261 | coord_equal() + 262 | labs(title = "ggplot2 rendering of original polygon(s)") 263 | ``` 264 | 265 | 266 | 267 | #### `triangular` decomposition - (Polygon from Random Points) 268 | 269 | ``` r 270 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 271 | # Decompose into triangles 272 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 273 | triangles_df <- triangular::decompose(polygons_df) 274 | 275 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 276 | # Plot the `triangular` decomposition into triangles 277 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 278 | ggplot(triangles_df) + 279 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 280 | theme_bw() + 281 | coord_equal() + 282 | labs(title = "Decomposition into simple tris with {triangular}") + 283 | theme(legend.position = 'none') 284 | ``` 285 | 286 | 287 | 288 | ## Triforce 289 | 290 | ## Triforce 291 | 292 | ``` r 293 | d <- sqrt(3)/2 294 | 295 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 296 | # Single triangle with a triangular hole 297 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 298 | polygons_df <- df <- data.frame( 299 | x = c(0, 1, 0.5, 0.25, 0.5, 0.75), 300 | y = c(0, 0, d , d/2 , 0 , d/2), 301 | subgroup = c(1, 1, 1 , 2, 2, 2), 302 | group = 1L 303 | ) 304 | 305 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 306 | # "Native" ggplot2 rendering 307 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 308 | ggplot(polygons_df) + 309 | geom_polygon(aes(x, y, subgroup = subgroup)) + 310 | geom_path(aes(x, y, group = interaction(group, subgroup))) + 311 | theme_bw() + 312 | coord_equal() + 313 | labs(title = "ggplot2 rendering of original polygon") 314 | ``` 315 | 316 | 317 | 318 | ``` r 319 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 320 | # decompose into simple triangular pieces i.e. without holes or self-intersections 321 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 322 | triangles_df <- triangular::decompose(polygons_df) 323 | 324 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 325 | # Plot the decomposition into triangles 326 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 327 | ggplot(triangles_df) + 328 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 329 | theme_bw() + 330 | coord_equal() + 331 | labs(title = "{triangular} decomposition of single polygon (with hole)\n into multiple simple polygons") + 332 | theme(legend.position = 'none') 333 | ``` 334 | 335 | 336 | 337 | ## Logo 338 | 339 | #### `ggplot2` rendering nested hexagons 340 | 341 | ``` r 342 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 343 | # polygons_df - data.frame of polygon vertices with group/subgroups 344 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 345 | r <- 5 346 | hex_df <- data.frame( 347 | x = r * cos(seq(30, 360, 60) * pi/180), 348 | y = r * sin(seq(30, 360, 60) * pi/180), 349 | group = 1, 350 | subgroup = 1 351 | ) 352 | 353 | hole_df <- data.frame( 354 | x = 2 * cos(seq(30, 360, 60) * pi/180) + 1, 355 | y = 2 * sin(seq(30, 360, 60) * pi/180) + 1, 356 | group = 1, 357 | subgroup = 2 358 | ) 359 | 360 | polygons_df <- rbind(hex_df, hole_df) 361 | 362 | 363 | 364 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 365 | # How 'ggplot2' handles this case 366 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 367 | ggplot(polygons_df) + 368 | geom_polygon(aes(x, y, group=group, subgroup=subgroup)) + 369 | geom_path(aes(x, y, group = interaction(group, subgroup))) + 370 | theme_bw() + 371 | coord_equal() + 372 | labs(title = "ggplot2 rendering of original polygon(s)") 373 | ``` 374 | 375 | 376 | 377 | #### `{triangular}` decomposition of nested hexagons 378 | 379 | ``` r 380 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 381 | # Turn the polygon data.frame into individual triangles 382 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 383 | triangles_df <- triangular::decompose(polygons_df) 384 | 385 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 386 | # Plot the triangles 387 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 388 | p <- ggplot(triangles_df) + 389 | geom_polygon(aes(x, y, fill = as.factor(idx)), colour = 'grey20', size = 0.15) + 390 | theme_void() + 391 | coord_equal() + 392 | theme(legend.position = 'none') + 393 | scale_fill_viridis_d(option = 'D') + 394 | annotate('text', x = 1, y = 1.4, label = 't\nr i\na n g\nu l a r', 395 | size = 5.5, family = 'mono', fontface = 'bold') 396 | 397 | ggsave("man/figures/logo.png", plot = p, width = 5, height = 5) 398 | 399 | p 400 | ``` 401 | 402 | 403 | 404 | ## Related Software 405 | 406 | - [silicate](https://cran.r-project.org/package=silicate) + 407 | [anglr](https://cran.r-project.org/package=anglr) do all of what 408 | triangular does (and more\!), with a much more configurable 409 | interface (that I did not need) 410 | - [decido](https://cran.r-project.org/package=decido) does fast 411 | ear-cutting triangulation. 412 | Unfortunately, this doesn’t deal nicely with self-intersecting 413 | polygons. 414 | 415 | ## Acknowledgements 416 | 417 | - [Michael Sumner](https://twitter.com/mdsumner) on twitter for his 418 | in-depth technical advice on graphics and and triangles in R 419 | - R Core for developing and maintaining such a wonderful language. 420 | - CRAN maintainers, for patiently shepherding packages onto CRAN and 421 | maintaining the repository 422 | -------------------------------------------------------------------------------- /man/assign_unique_vertex_indices.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/decompose.R 3 | \name{assign_unique_vertex_indices} 4 | \alias{assign_unique_vertex_indices} 5 | \title{Assign unique vertex indices to all points} 6 | \usage{ 7 | assign_unique_vertex_indices(polygons_df) 8 | } 9 | \arguments{ 10 | \item{polygons_df}{data.frame containing multiple polygons 11 | distinguished by group and subgroup} 12 | } 13 | \value{ 14 | inpput wit new 'vidx' and 'dupe' columns 15 | } 16 | \description{ 17 | This will be used later to de-dupe the input data for RTriangle::triangulate() 18 | } 19 | -------------------------------------------------------------------------------- /man/create_S.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/decompose.R 3 | \name{create_S} 4 | \alias{create_S} 5 | \title{Create the S matrix for RTriangle::pslg() for a single polygon} 6 | \usage{ 7 | create_S(polygon_df) 8 | } 9 | \arguments{ 10 | \item{polygon_df}{data.frame containing a single polygon. Must have 'vidx' column} 11 | } 12 | \description{ 13 | pslg = Planar Straight Line Graph object. A collection of vertices and segments. 14 | } 15 | \details{ 16 | The 'S' matrix is: A 2-column matrix of segments in which each row is a 17 | segment. Segments are edges whose endpoints are vertices in the PSLG, and 18 | whose presence in any mesh generated from the PSLG is enforced. Each 19 | segment refers to the indices in V of the endpoints of the segment. By 20 | default the segments are not specified (NA), in which case the convex 21 | hull of the vertices are taken to be the segments. Any vertices outside 22 | the region enclosed by the segments are eaten away by the triangulation 23 | algorithm. If the segments do not enclose a region the whole triangulation 24 | may be eaten away. 25 | } 26 | -------------------------------------------------------------------------------- /man/decompose.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/decompose.R 3 | \name{decompose} 4 | \alias{decompose} 5 | \title{Decompose complex polygons into triangles} 6 | \usage{ 7 | decompose(polygons_df) 8 | } 9 | \arguments{ 10 | \item{polygons_df}{polygon data.frame with \code{x,y} coordinates, 'group' 11 | column denoting coordinates which belong to the same group, 12 | and 'subgroup' indicating holes within that polygon. It is assumed 13 | that any other column values are consistent across the entire group 14 | e.g. 'fill'} 15 | } 16 | \value{ 17 | data.frame with \code{x}, \code{y} coordinates of the vertices of the 18 | resulting triangles. Also includes \code{idx} numbering the triangles. 19 | } 20 | \description{ 21 | This function wraps \code{RTriangle::triangulate()} to make it easier to 22 | call for my use case, and returns results appropriate for plotting in 23 | \code{ggplot2}. 24 | } 25 | \details{ 26 | By combining \code{polyclip} and \code{RTriangle} packages, this package 27 | will successfully decompose into triangles the following polygon types: 28 | 29 | \itemize{ 30 | \item{Simple polygons} 31 | \item{Polyons with holes} 32 | \item{Multiple polygons with holes} 33 | \item{Polygons with self-intersection} 34 | \item{Polygons with duplicated vertices} 35 | } 36 | } 37 | \examples{ 38 | \dontrun{ 39 | # Polygon with a hole 40 | polygons_df <- df <- data.frame( 41 | x = c(4, 8, 8, 4, 6, 7, 7, 6), 42 | y = c(4, 4, 8, 8, 6, 6, 7, 7), 43 | group = c(1, 1, 1, 1, 1, 1, 1, 1), 44 | subgroup = c(1, 1, 1, 1, 2, 2, 2, 2) 45 | ) 46 | triangles_df <- triangular::decompose(polygons_df) 47 | ggplot(triangles_df) + 48 | geom_polygon(aes(x, y, fill = as.factor(idx))) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /man/decompose_single.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/decompose.R 3 | \name{decompose_single} 4 | \alias{decompose_single} 5 | \title{Inner function to decompose a single polygon 'group'} 6 | \usage{ 7 | decompose_single(polygons_df) 8 | } 9 | \arguments{ 10 | \item{polygons_df}{polygon data.frame with \code{x,y} coordinates, 'group' 11 | column denoting coordinates which belong to the same group, 12 | and 'subgroup' indicating holes within that polygon. It is assumed 13 | that any other column values are consistent across the entire group 14 | e.g. 'fill'} 15 | } 16 | \description{ 17 | Inner function to decompose a single polygon 'group' 18 | } 19 | -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-10-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-10-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-11-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-11-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-12-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-12-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-12-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-13-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-13-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-13-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-13-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-14-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-14-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-15-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-15-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-2-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-3-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-4-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-5-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-6-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-7-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-8-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-9-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/README-unnamed-chunk-9-1.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbutuseless/triangular/a90deee7d93c36c1f25ad9bb3d92a25436e16137/man/figures/logo.png -------------------------------------------------------------------------------- /man/simplify_polygon.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/decompose.R 3 | \name{simplify_polygon} 4 | \alias{simplify_polygon} 5 | \title{Simplify a single polygon} 6 | \usage{ 7 | simplify_polygon(polygon_df) 8 | } 9 | \arguments{ 10 | \item{polygon_df}{data.frame containing a single polygon (x, y)} 11 | } 12 | \description{ 13 | Simplify a single polygon 14 | } 15 | -------------------------------------------------------------------------------- /man/simplify_polygons.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/decompose.R 3 | \name{simplify_polygons} 4 | \alias{simplify_polygons} 5 | \title{Simplify a polygon made up of groups and subgroups} 6 | \usage{ 7 | simplify_polygons(polygons_df) 8 | } 9 | \arguments{ 10 | \item{polygons_df}{data.frame containing multiple polygons 11 | distinguished by group and subgroup} 12 | } 13 | \description{ 14 | Simplify a polygon made up of groups and subgroups 15 | } 16 | --------------------------------------------------------------------------------