diff --git a/DESCRIPTION b/DESCRIPTION index f427989..a1f3b46 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: WeightedTreemaps Title: Generate and Plot Voronoi or Sunburst Treemaps from Hierarchical Data -Version: 0.1.2 +Version: 0.1.3 Authors@R: c( person("Michael", "Jahn", , "jahn@mpusp.mpg.de", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-3913-153X")), @@ -54,5 +54,5 @@ VignetteBuilder: knitr Encoding: UTF-8 LazyData: true -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.2 SystemRequirements: C++17 diff --git a/NEWS.md b/NEWS.md index 0ae50e7..e47e338 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,14 @@ +# WeightedTreemaps 0.1.3 + +- fixed links to external packages +- several bug fixes and improvements to catch errors when parsing polygons +- added option to converge with different speed + # WeightedTreemaps 0.1.2 +- updated README +- vignette: reduced example sizes to improve building time + # WeightedTreemaps 0.1.1 - The package was prepared for release on CRAN diff --git a/R/allocate.R b/R/allocate.R index 47789c7..dcba34a 100644 --- a/R/allocate.R +++ b/R/allocate.R @@ -1,7 +1,7 @@ #' @importFrom stats rnorm #' @importFrom dplyr %>% #' @importFrom sf st_area -#' +#' cellError <- function(a, target) { normA <- a / sum(a) diff <- abs(normA - target) @@ -14,9 +14,9 @@ breaking <- function( debug = FALSE, error_tol, prevError) { - + if (max) { - + # Stop when largest individual cell error is less than 1% # (the default) err <- cellError(a, target) @@ -29,7 +29,7 @@ breaking <- function( prevError <- err } else { - + normA <- a / sum(a) diff <- abs(normA - target) # Stop when *change* in *total* cell error is tiny @@ -46,7 +46,7 @@ breaking <- function( )) stopping <- abs(sum(diff) - prevError) < 0.001 prevError <- sum(diff) - + } list( stopping = stopping, @@ -59,16 +59,22 @@ breaking <- function( # adjust by multiple of average absolute weights # This avoids problem of getting stuck at a tiny weight # (and stabilizes the algorithm generally) -# difference to original implementation: adjustment of maximal +# difference to original implementation: adjustment of maximal # step change of weights to prevent crashing of algorithm -adjustWeights <- function(w, a, target) { +adjustWeights <- function(w, a, target, convergence) { # OPTION: avoid extreme scaling values -> squareroot function # to buffer strong difference between computed area and target # and to buffer the global weight increase # these increase stability but also computation time: normA <- a / sum(a) scaling <- ((target - normA) / target) - scaling <- ifelse(scaling < -1, scaling/sqrt(abs(scaling)), scaling) + if (convergence == "slow") { + scaling <- ifelse(scaling < -1, -log10(abs(scaling)), scaling) + } else if (convergence == "intermediate") { + scaling <- ifelse(scaling < -1, -log(abs(scaling)), scaling) + } else if (convergence == "fast") { + scaling <- ifelse(scaling < -1, scaling/sqrt(abs(scaling)), scaling) + } w + sqrt(mean(abs(w))) * scaling } @@ -115,34 +121,52 @@ shiftWeights <- function(s, w) { # just give up after 'maxIteration's allocate <- function( names, s, w, outer, target, - maxIteration, + maxIteration, error_tol, - debug = FALSE, + convergence, + min_target = 0.01, + debug = FALSE, debugCell = FALSE) { count <- 1 prevError <- 1 - + + # check for extremely small cell size compared to theoretical average + target_fc <- target * length(target) + too_small <- target_fc < min_target + if (any(too_small)) { + message(paste0( + "Found extremely small cell (<", round(min_target * 100, 1), "% of average size);\n", + "inflating cell size to prevent failure when calculating polygons." + )) + correction <- ifelse( + too_small, + min_target/length(target), + -min_target/length(target) * sum(too_small)/sum(!too_small) + ) + target <- target + correction + } + repeat { - + # if all weights are identical the CGAL algorithm often fails # in this case we introduce a bit of random variation if (length(unique(w)) == 1) { w <- w * rnorm(length(w), mean = 1, sd = 0.01) } - + # call to awv function, the additively weighted voronoi tesselation, # wrapped within a trycatch statement to catch errors and start over k <- tryCatch(awv(s, w, outer, debug, debugCell), - error = function(e) { print(e); NULL} + error = function(e) { message(e); NULL} ) if (is.null(k)) { return(NULL) } areas <- lapply(k, sf::st_area) - + # if debug=TRUE, every iteration is drawn to the viewport - # this can be very time and resource consuming and should be used + # this can be very time and resource consuming and should be used # with care. The result resembles the final treemap but is an overlay of # many iterations if (debug) { @@ -156,7 +180,7 @@ allocate <- function( lwd = 2, col = grey(0.5), fill = grey(1, alpha=0.33) ) - + info <- rbind( area = round(unlist(areas) / sum(unlist(areas)), 4), @@ -167,31 +191,31 @@ allocate <- function( colnames(info) <- names print(info) } - + stop_cond <- breaking( - unlist(areas), - target, + unlist(areas), + target, debug = debug, error_tol = error_tol, prevError = prevError) - + # if stop condition is fulfilled, return result in form of # list of polygons and metadata if (count == maxIteration || stop_cond$stopping) { - + res <- lapply(1:length(names), function(i) { list( name = names[i], poly = k[[i]], - site = c(s$x[[i]], s$y[[i]]), - weight = w[i], area = unlist(areas)[i], + site = c(s$x[[i]], s$y[[i]]), + weight = w[i], area = unlist(areas)[i], target = target[i], count = count) }) %>% setNames(names) return(res) - + } else { - - w <- adjustWeights(w, unlist(areas), target) + + w <- adjustWeights(w, unlist(areas), target, convergence) s <- shiftSites(s, k) w <- shiftWeights(s, w) } diff --git a/R/drawTreemap.R b/R/drawTreemap.R index 78e2d35..733512f 100644 --- a/R/drawTreemap.R +++ b/R/drawTreemap.R @@ -23,7 +23,7 @@ #' Default is to use the lowest level cells for Voronoi treemaps and all levels #' for sunburst treemaps. #' @param color_palette (character) A character vector of colors used to fill cells. -#' The default is to use \code{\link{rainbow_hcl}} from package \code{colorspace} +#' The default is to use \code{\link[colorspace]{rainbow_hcl}} from #' @param border_level (numeric) A numeric vector representing the hierarchical level that should be #' used for drawing cell borders, or NULL to omit drawing borders, The default is #' that all borders are drawn. diff --git a/R/tesselation.R b/R/tesselation.R index 00ccf08..08d1331 100644 --- a/R/tesselation.R +++ b/R/tesselation.R @@ -11,7 +11,7 @@ # Generate one iteration of the Additively Weighted Voronoi diagram awv <- function( s, w, region, debug = FALSE, - debugCell = FALSE) + debugCell = FALSE) { # combine X, Y coordinates and weights as input for # C++ tesselation function @@ -30,27 +30,29 @@ awv <- function( tidyCell <- function(cell, tolerance) { - + # if cell touches the border at two points, we need to close it # this is not necessary if cell touches border at 4 points (like a stripe) if (sum( - cell$border$x %in% c(4000, -4000), + cell$border$x %in% c(4000, -4000), cell$border$y %in% c(4000, -4000)) == 2 ) { - - closeCell(cell$border, cell$vertex, tol = tolerance) - + poly <- closeCell(cell$border, cell$vertex, tol = tolerance) } else { - # return a list of the polygon - list( + poly <- list( x = cell$border$x, y = cell$border$y, end = "boundary" ) - + if (sf::st_is_valid(convertCell(poly[1:2]))) { + return(poly) + } else { + message("Found invalid polygon (self-intersection)") + return(NULL) + } } - + } # SIDES @@ -97,7 +99,7 @@ antiSide <- function(corner) { closeClock <- function(x, y, start, end, scale = 2000) { cornerX <- c(-2 * scale, 2 * scale, 2 * scale,-2 * scale) cornerY <- c(2 * scale, 2 * scale,-2 * scale,-2 * scale) - + side <- end repeat { corner <- clockCorner(side) @@ -116,7 +118,7 @@ closeClock <- function(x, y, start, end, scale = 2000) { closeAnti <- function(x, y, start, end, scale = 2000) { cornerX <- c(-2 * scale, 2 * scale, 2 * scale,-2 * scale) cornerY <- c(2 * scale, 2 * scale,-2 * scale,-2 * scale) - + side <- end repeat { corner <- antiCorner(side) @@ -155,23 +157,22 @@ closeCell <- function(cell, vertex, tol, scale = 2000) { # If not, do second one (and check that vertex is "inside" that result!) x <- cell$x y <- cell$y - + # ASSUME that both first and last vertices are on boundary! N <- length(x) startSide <- side(x[1], y[1]) endSide <- side(x[N], y[N]) - + # exit if not both end points lie on boundary if (length(startSide) != 1 | length(endSide) != 1) { return(NULL) } - + # Start and end on same side if (startSide == endSide) { - - cell <- list(x = x, y = y) + if (sp::point.in.polygon(vertex[1], vertex[2], - cell$x, cell$y) == 0) { + cell$x, cell$y) == 0) { boundRect <- to_sfpoly(list( x = c(-2 * scale,-2 * scale, 2 * scale, 2 * scale), y = c(-2 * scale, 2 * scale, 2 * scale,-2 * scale) @@ -179,16 +180,13 @@ closeCell <- function(cell, vertex, tol, scale = 2000) { # "Subtract" smallCell from bound rect to get largeCell cellPoly <- to_sfpoly(cell) cellPoly <- sf::st_difference(boundRect, cellPoly) - - pts <- to_coords(cellPoly) + cell <- to_coords(cellPoly) if (sp::point.in.polygon(vertex[1], vertex[2], - cell$x, cell$y) == 0) { + cell$x, cell$y) == 0) { stop("Failed to close cell") } } - } else { - cell <- closeClock(x, y, startSide, endSide) if (sp::point.in.polygon(vertex[1], vertex[2], cell$x, cell$y) == 0) { @@ -222,6 +220,10 @@ trimCells <- function(cells, region) { if (inherits(poly, "MULTIPOLYGON")) { poly <- suppressWarnings(sf::st_cast(poly, to = "POLYGON")) } + if (inherits(poly, "GEOMETRYCOLLECTION")) { + valid <- which(sapply(poly, function(x) inherits(x, "POLYGON")))[1] + poly <- poly[[valid]] + } poly }) } @@ -235,21 +237,21 @@ samplePoints <- function(ParentPoly, n, seed, positioning) { if (!is.null(seed)) { set.seed(seed) } - - # This loop keeps repeating until the correct number of coordinates + + # This loop keeps repeating until the correct number of coordinates # is sampled. The reason is that sp::spsample() does not always sample # the correct number of coordinates, but too few or too many repeat { - + sampled <- tryCatch({ points <- sp::spsample( sp::Polygon(coords = ParentPoly), - n = n, + n = n, type = ifelse(positioning == "random", "random", "nonaligned") ) points@coords}, error = function(e) NULL ) - + if (is.null(sampled) || nrow(sampled) != n) { next } else { diff --git a/R/voronoiTreemap.R b/R/voronoiTreemap.R index 17507ca..a7ca65c 100644 --- a/R/voronoiTreemap.R +++ b/R/voronoiTreemap.R @@ -49,6 +49,13 @@ #' area. The default is 0.01 (or 1 \%) of the total parental area. Note: this #' is is different from a relative per-cell error, where 1 \% would be more #' strict. +#' @param convergence (character) One of "slow", "intermediate", or "fast". +#' Intermediate (default) and fast try to adjust cell weights stronger such +#' that the algorithm converges faster towards the final size of the cell. +#' However this comes at the price of stability, with a larger number of +#' polygons possibly being misformed, e.g. by having self-intersections. +#' Set convergence to "slow" if you experience problems to calculate treemaps +#' with very unequal cell sizes or very large treemaps. #' @param seed (integer) The default seed is NULL, which will lead to a new #' random sampling of cell coordinates for each tesselation. If you want #' a reproducible arrangement of cells, set seed to an arbitrary number. @@ -144,6 +151,7 @@ voronoiTreemap <- function( shape = "rectangle", maxIteration = 100, error_tol = 0.01, + convergence = "intermediate", seed = NULL, positioning = "regular", verbose = FALSE, @@ -305,6 +313,7 @@ voronoiTreemap <- function( target = weights, maxIteration = maxIteration, error_tol = error_tol, + convergence = convergence, outer = sfpoly, debug = debug ) diff --git a/README.Rmd b/README.Rmd index d1b3dfa..4d56c44 100644 --- a/README.Rmd +++ b/README.Rmd @@ -51,7 +51,7 @@ tm <- voronoiTreemap( ``` -```{r, fig.height = 5, fig.width = 5, out.width = "50%", fig.align = 'center', echo = FALSE} +```{r fig_example, fig.height = 5, fig.width = 5, out.width = "50%", fig.align = 'center', echo = FALSE} # draw treemap drawTreemap(tm, label_size = 2.5, label_color = "white", title = "An example") ``` @@ -131,14 +131,15 @@ tm <- voronoiTreemap( Draw the treemap. -```{r, fig.width = 5, fig.height = 5, out.width = "50%", fig.align = 'center'} +```{r fig_cars_basic, fig.width = 5, fig.height = 5, out.width = "50%", fig.align = 'center'} drawTreemap(tm, label_size = 2.5, label_color = "white") ``` +### Drawing options The `voronoiTreemap()` and `drawTreemap()` functions are separated in order to allow drawing of the same treemap object in different ways. Computation of treemaps with thousands of cells can be very time and resource consuming (around 5-10 minutes for a 2000-cell treemap on a regular desktop computer). With the `drawTreemap()` function, we can not only plot the same treemap in different ways but also combine several treemaps on one page using the `layout` and `position` arguments. The most important style element is color. Coloring can be based on cell category, cell size, or both, using the `color_type` argument. By default, the highest hierarchical level is used for coloring but that can be customized using the `color_level` argument. -```{r, fig.width = 9, fig.height = 9, out.width = "100%", fig.align = 'center', warning = FALSE} +```{r fig_cars_colors, fig.width = 9, fig.height = 9, out.width = "100%", fig.align = 'center', warning = FALSE} drawTreemap(tm, title = "treemap 1", label_size = 2, color_type = "categorical", color_level = 1, layout = c(2, 2), position = c(1, 1), legend = TRUE) @@ -158,7 +159,34 @@ drawTreemap(tm, title = "treemap 4", label_size = 2, add = TRUE, layout = c(2, 2), position = c(2, 2), title_color = "black", legend = TRUE) ``` - +### Convergence time + +The expansion of cells towards a certain target size is a non-deterministic process. During each iteration, cell size is adjusted using weights, but the final result can only be measured after a cell (polygon) was created. Is it too small compared to the target area, it will get a higher weight for the next iteration, and *vice versa*. The adjustment of weights can be controlled by the `convergence` parameter ("slow", "intermediate", "fast"). Faster convergence will adjust weights more strongly and attempts to reach the target size with fewer iterations. However this procedure increases the probability of obtaining problematic polygons with for example self-intersections or holes. Compare the following treemaps generated with identical input except for the `convergence`. + +```{r fig_cars_conv, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center'} +convergence <- c("slow", "intermediate", "fast") + +for (i in 1:3) { + tm <- voronoiTreemap( + data = mtcars, + levels = c("gear", "car_name"), + cell_size = "wt", + shape = "rounded_rect", + seed = 123, + convergence = convergence[i], + verbose = TRUE + ) + drawTreemap( + tm, + title = paste0("convergence = ", convergence[i]), + label_size = 2.5, + label_color = "white", + layout = c(1, 3), + position = c(1, i), + add = ifelse(i == 1, FALSE, TRUE) + ) +} +``` ### Positioning of cells @@ -192,7 +220,7 @@ tm3 <- voronoiTreemap( ``` -```{r, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE} +```{r fig_pos, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE} drawTreemap(tm1, title = "positioning = 'random'", border_size = 3, layout = c(1,3), position = c(1, 1)) @@ -237,7 +265,7 @@ tm3 <- voronoiTreemap(data = df, levels = "A", ``` -```{r, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE} +```{r fig_shapes, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE} drawTreemap(tm1, layout = c(1,3), position = c(1, 1)) drawTreemap(tm2, add = TRUE, layout = c(1,3), position = c(1, 2)) drawTreemap(tm3, add = TRUE, layout = c(1,3), position = c(1, 3)) @@ -278,7 +306,7 @@ tm <- voronoiTreemap( Generating and plotting of treemaps are two processes separated on purpose. Computing treemaps can be time-consuming and to recalculate them every time just for changing a color gradient or label size is inefficient. Once a treemap is computed, it can be drawn in different ways as the following example shows. First we can generate custom color palettes using `colorspace`s `hclwizard`. Just browse to the `Export` and then the `R` tab and copy the code to your script. ```{r, message = FALSE, error = FALSE, results = 'hide'} -# outcomment to run interactive wizard: +# remove comment to run interactive wizard: #hclwizard() custom_pal_1 <- sequential_hcl( @@ -432,7 +460,7 @@ tm <- sunburstTreemap( Draw treemaps with different graphical parameters -```{r, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE} +```{r fig_sunburst, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE} # draw treemap with default options drawTreemap(tm, title = "A sunburst treemap", diff --git a/README.md b/README.md index 61e8ec7..cdb9b08 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ WeightedTreemaps ================ Michael Jahn, David Leslie, Ahmadou Dicko, Paul Murrell -2024-01-07 +2024-11-04 @@ -25,7 +25,7 @@ commit](https://img.shields.io/github/last-commit/m-jahn/WeightedTreemaps) Generate and plot **Voronoi treemaps** or **Sunburst treemaps** from hierarchical data. - + ## News @@ -148,7 +148,9 @@ Draw the treemap. drawTreemap(tm, label_size = 2.5, label_color = "white") ``` - + + +### Drawing options The `voronoiTreemap()` and `drawTreemap()` functions are separated in order to allow drawing of the same treemap object in different ways. @@ -183,7 +185,63 @@ drawTreemap(tm, title = "treemap 4", label_size = 2, title_color = "black", legend = TRUE) ``` - + +\### Convergence time + +The expansion of cells towards a certain target size is a +non-deterministic process. During each iteration, cell size is adjusted +using weights, but the final result can only be measured after a cell +(polygon) was created. Is it too small compared to the target area, it +will get a higher weight for the next iteration, and *vice versa*. The +adjustment of weights can be controlled by the `convergence` parameter +(“slow”, “intermediate”, “fast”). Faster convergence will adjust weights +more strongly and attempts to reach the target size with fewer +iterations. However this procedure increases the probability of +obtaining problematic polygons with for example self-intersections or +holes. Compare the following treemaps generated with identical input +except for the `convergence`. + +``` r +convergence <- c("slow", "intermediate", "fast") + +for (i in 1:3) { + tm <- voronoiTreemap( + data = mtcars, + levels = c("gear", "car_name"), + cell_size = "wt", + shape = "rounded_rect", + seed = 123, + convergence = convergence[i], + verbose = TRUE + ) + drawTreemap( + tm, + title = paste0("convergence = ", convergence[i]), + label_size = 2.5, + label_color = "white", + layout = c(1, 3), + position = c(1, i), + add = ifelse(i == 1, FALSE, TRUE) + ) +} +#> Level 1 tesselation: 6.87 % mean error, 10.3 % max error, 100 iterations. +#> Level 2 tesselation: 0.33 % mean error, 0.97 % max error, 63 iterations. +#> Level 2 tesselation: 0.58 % mean error, 0.98 % max error, 48 iterations. +#> Level 2 tesselation: 0.54 % mean error, 0.98 % max error, 71 iterations. +#> Treemap successfully created. +#> Level 1 tesselation: 3.15 % mean error, 4.73 % max error, 100 iterations. +#> Level 2 tesselation: 0.25 % mean error, 0.96 % max error, 71 iterations. +#> Level 2 tesselation: 0.45 % mean error, 0.98 % max error, 52 iterations. +#> Level 2 tesselation: 0.56 % mean error, 0.95 % max error, 64 iterations. +#> Treemap successfully created. +#> Level 1 tesselation: 0.64 % mean error, 0.96 % max error, 97 iterations. +#> Level 2 tesselation: 0.36 % mean error, 0.97 % max error, 93 iterations. +#> Level 2 tesselation: 0.45 % mean error, 1 % max error, 57 iterations. +#> Level 2 tesselation: 0.54 % mean error, 0.98 % max error, 70 iterations. +#> Treemap successfully created. +``` + + ### Positioning of cells @@ -236,7 +294,7 @@ drawTreemap(tm3, title = "positioning = 'clustered'", border_size = 3, add = TRUE, layout = c(1,3), position = c(1, 3)) ``` - + ### Custom initial shapes @@ -284,7 +342,7 @@ drawTreemap(tm2, add = TRUE, layout = c(1,3), position = c(1, 2)) drawTreemap(tm3, add = TRUE, layout = c(1,3), position = c(1, 3)) ``` - + ### Advanced example for Voronoi treemaps @@ -339,7 +397,7 @@ palettes using `colorspace`s `hclwizard`. Just browse to the `Export` and then the `R` tab and copy the code to your script. ``` r -# outcomment to run interactive wizard: +# remove comment to run interactive wizard: #hclwizard() custom_pal_1 <- sequential_hcl( @@ -559,7 +617,7 @@ drawTreemap(tm, ) ``` - + ## References and other treemap packages diff --git a/cran-comments.md b/cran-comments.md index e2f10e7..b0486d2 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -10,32 +10,18 @@ - ubuntu-latest (release) - ubuntu-latest (oldrel-1) -### with `rhub::check_for_cran()` +### with `rhub::rhub_check()` -- Fedora Linux, R-devel, clang, gfortran -- Debian Linux, R-release, GCC +- MacOS arm64 latest, R-* (any version) - Windows Server 2022, R-devel, 64 bit -- Ubuntu Linux 20.04.1 LTS, R-release, GCC -- Debian Linux, R-devel, GCC ASAN/UBSAN +- Ubuntu 22.04.5 LTS, R-devel, GCC 14 +- Ubuntu 22.04.5 LTS, R-devel, Clang ASAN/UBSAN ## R CMD check results There were no ERRORs or WARNINGs. -There was 1 NOTE: - -``` -checking installed package size ... NOTE - installed size is 9.6Mb - sub-directories of 1Mb or more: - doc 1.1Mb - libs 7.7Mb -``` - -Installed package size exceeding 5 MB is caused by the compiled function `voronoiDiagram.o`. The size of this file can not be reduced. - -There were "additional issues" brought up with from ASAN/UBSAN sanitizer checks. -These were fixed in the current version. +The Clang ASAN test throws 1 error, "AddressSanitizer: alloc-dealloc-mismatch (operator new vs free)", related to the upstream dependency libgeos. This is most likely a false-positive, see https://github.com/r-hub/rhub/issues/598. ## Downstream dependencies diff --git a/images/fig_cars_basic-1.png b/images/fig_cars_basic-1.png new file mode 100644 index 0000000..70e69e9 Binary files /dev/null and b/images/fig_cars_basic-1.png differ diff --git a/images/fig_cars_colors-1.png b/images/fig_cars_colors-1.png new file mode 100644 index 0000000..e55e40b Binary files /dev/null and b/images/fig_cars_colors-1.png differ diff --git a/images/fig_cars_conv-1.png b/images/fig_cars_conv-1.png new file mode 100644 index 0000000..5de5794 Binary files /dev/null and b/images/fig_cars_conv-1.png differ diff --git a/images/fig_example-1.png b/images/fig_example-1.png new file mode 100644 index 0000000..9a7c796 Binary files /dev/null and b/images/fig_example-1.png differ diff --git a/images/fig_pos-1.png b/images/fig_pos-1.png new file mode 100644 index 0000000..a8b36d7 Binary files /dev/null and b/images/fig_pos-1.png differ diff --git a/images/unnamed-chunk-13-1.png b/images/fig_shapes-1.png similarity index 100% rename from images/unnamed-chunk-13-1.png rename to images/fig_shapes-1.png diff --git a/images/unnamed-chunk-23-1.png b/images/fig_sunburst-1.png similarity index 100% rename from images/unnamed-chunk-23-1.png rename to images/fig_sunburst-1.png diff --git a/images/unnamed-chunk-10-1.png b/images/unnamed-chunk-10-1.png deleted file mode 100644 index 405edac..0000000 Binary files a/images/unnamed-chunk-10-1.png and /dev/null differ diff --git a/images/unnamed-chunk-2-1.png b/images/unnamed-chunk-2-1.png deleted file mode 100644 index 52ff999..0000000 Binary files a/images/unnamed-chunk-2-1.png and /dev/null differ diff --git a/images/unnamed-chunk-7-1.png b/images/unnamed-chunk-7-1.png deleted file mode 100644 index a8981e0..0000000 Binary files a/images/unnamed-chunk-7-1.png and /dev/null differ diff --git a/images/unnamed-chunk-8-1.png b/images/unnamed-chunk-8-1.png deleted file mode 100644 index 7df13a0..0000000 Binary files a/images/unnamed-chunk-8-1.png and /dev/null differ diff --git a/man/drawTreemap.Rd b/man/drawTreemap.Rd index ea7ad9a..0258b5b 100644 --- a/man/drawTreemap.Rd +++ b/man/drawTreemap.Rd @@ -52,7 +52,7 @@ Default is to use the lowest level cells for Voronoi treemaps and all levels for sunburst treemaps.} \item{color_palette}{(character) A character vector of colors used to fill cells. -The default is to use \code{\link{rainbow_hcl}} from package \code{colorspace}} +The default is to use \code{\link[colorspace]{rainbow_hcl}} from} \item{border_level}{(numeric) A numeric vector representing the hierarchical level that should be used for drawing cell borders, or NULL to omit drawing borders, The default is diff --git a/man/voronoiTreemap.Rd b/man/voronoiTreemap.Rd index c7e5a10..a0054e0 100644 --- a/man/voronoiTreemap.Rd +++ b/man/voronoiTreemap.Rd @@ -15,6 +15,7 @@ voronoiTreemap( shape = "rectangle", maxIteration = 100, error_tol = 0.01, + convergence = "intermediate", seed = NULL, positioning = "regular", verbose = FALSE, @@ -71,6 +72,14 @@ area. The default is 0.01 (or 1 \%) of the total parental area. Note: this is is different from a relative per-cell error, where 1 \% would be more strict.} +\item{convergence}{(character) One of "slow", "intermediate", or "fast". +Intermediate (default) and fast try to adjust cell weights stronger such +that the algorithm converges faster towards the final size of the cell. +However this comes at the price of stability, with a larger number of +polygons possibly being misformed, e.g. by having self-intersections. +Set convergence to "slow" if you experience problems to calculate treemaps +with very unequal cell sizes or very large treemaps.} + \item{seed}{(integer) The default seed is NULL, which will lead to a new random sampling of cell coordinates for each tesselation. If you want a reproducible arrangement of cells, set seed to an arbitrary number.} diff --git a/vignettes/WeightedTreemaps.R b/vignettes/WeightedTreemaps.R index 3a6fd7a..35e8709 100644 --- a/vignettes/WeightedTreemaps.R +++ b/vignettes/WeightedTreemaps.R @@ -48,6 +48,30 @@ mtcars$car_name = gsub(" ", "\n", row.names(mtcars)) # add = TRUE, layout = c(2, 2), position = c(2, 2), # title_color = "black", legend = TRUE) +## ----fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', eval = FALSE---- +# convergence <- c("slow", "intermediate", "fast") +# +# for (i in 1:3) { +# tm <- voronoiTreemap( +# data = mtcars, +# levels = c("gear", "car_name"), +# cell_size = "wt", +# shape = "rounded_rect", +# seed = 123, +# convergence = convergence[i], +# verbose = TRUE +# ) +# drawTreemap( +# tm, +# title = paste0("convergence = ", convergence[i]), +# label_size = 2.5, +# label_color = "white", +# layout = c(1, 3), +# position = c(1, i), +# add = ifelse(i == 1, FALSE, TRUE) +# ) +# } + ## ----message = FALSE, error = FALSE, results = 'hide', eval = FALSE----------- # # set seed to obtain same df every time # set.seed(123) @@ -131,7 +155,7 @@ tm <- sunburstTreemap( levels = c("A", "B") ) -## ----fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE---- +## ----fig_sunburst, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE---- # draw treemap with default options drawTreemap(tm, title = "A sunburst treemap", diff --git a/vignettes/WeightedTreemaps.Rmd b/vignettes/WeightedTreemaps.Rmd index 17e65f9..18241ef 100644 --- a/vignettes/WeightedTreemaps.Rmd +++ b/vignettes/WeightedTreemaps.Rmd @@ -82,7 +82,9 @@ Draw the treemap. drawTreemap(tm, label_size = 2.5, label_color = "white") ``` - + + +### Drawing options The `voronoiTreemap()` and `drawTreemap()` functions are separated in order to allow drawing of the same treemap object in different ways. Computation of treemaps with thousands of cells can be very time and resource consuming (around 5-10 minutes for a 2000-cell treemap on a regular desktop computer). With the `drawTreemap()` function, we can not only plot the same treemap in different ways but also combine several treemaps on one page using the `layout` and `position` arguments. The most important style element is color. Coloring can be based on cell category, cell size, or both, using the `color_type` argument. By default, the highest hierarchical level is used for coloring but that can be customized using the `color_level` argument. @@ -107,7 +109,38 @@ drawTreemap(tm, title = "treemap 4", label_size = 2, title_color = "black", legend = TRUE) ``` - + + +### Convergence time + +The expansion of cells towards a certain target size is a non-deterministic process. During each iteration, cell size is adjusted using weights, but the final result can only be measured after a cell (polygon) was created. Is it too small compared to the target area, it will get a higher weight for the next iteration, and *vice versa*. The adjustment of weights can be controlled by the `convergence` parameter ("slow", "intermediate", "fast"). Faster convergence will adjust weights more strongly and attempts to reach the target size with fewer iterations. However this procedure increases the probability of obtaining problematic polygons with for example self-intersections or holes. Compare the following treemaps generated with identical input except for the `convergence`. + +```{r, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', eval = FALSE} +convergence <- c("slow", "intermediate", "fast") + +for (i in 1:3) { + tm <- voronoiTreemap( + data = mtcars, + levels = c("gear", "car_name"), + cell_size = "wt", + shape = "rounded_rect", + seed = 123, + convergence = convergence[i], + verbose = TRUE + ) + drawTreemap( + tm, + title = paste0("convergence = ", convergence[i]), + label_size = 2.5, + label_color = "white", + layout = c(1, 3), + position = c(1, i), + add = ifelse(i == 1, FALSE, TRUE) + ) +} +``` + + ### Positioning of cells @@ -152,7 +185,7 @@ drawTreemap(tm3, title = "positioning = 'clustered'", border_size = 3, add = TRUE, layout = c(1,3), position = c(1, 3)) ``` - + ### Custom initial shapes @@ -194,7 +227,7 @@ drawTreemap(tm2, add = TRUE, layout = c(1,3), position = c(1, 2)) drawTreemap(tm3, add = TRUE, layout = c(1,3), position = c(1, 3)) ``` - + ### Sunburst treemaps @@ -224,7 +257,7 @@ tm <- sunburstTreemap( Draw treemaps with different graphical parameters -```{r, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE} +```{r fig_sunburst, fig.width = 12, fig.height = 4, out.width = "100%", fig.align = 'center', warning = FALSE} # draw treemap with default options drawTreemap(tm, title = "A sunburst treemap", diff --git a/vignettes/WeightedTreemaps.html b/vignettes/WeightedTreemaps.html index 3a8f785..c215cdd 100644 --- a/vignettes/WeightedTreemaps.html +++ b/vignettes/WeightedTreemaps.html @@ -12,7 +12,7 @@ - + WeightedTreemaps @@ -51,7 +51,7 @@