Skip to content

Commit

Permalink
feat: added option to converge with different speed; realted to #47 #51
Browse files Browse the repository at this point in the history
  • Loading branch information
m-jahn committed Nov 4, 2024
1 parent 45a46b0 commit 2f296ad
Show file tree
Hide file tree
Showing 30 changed files with 358 additions and 148 deletions.
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -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")),
Expand Down Expand Up @@ -54,5 +54,5 @@ VignetteBuilder:
knitr
Encoding: UTF-8
LazyData: true
RoxygenNote: 7.2.3
RoxygenNote: 7.3.2
SystemRequirements: C++17
13 changes: 10 additions & 3 deletions R/allocate.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,20 @@ breaking <- function(
# (and stabilizes the algorithm generally)
# 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
}

Expand Down Expand Up @@ -117,6 +123,7 @@ allocate <- function(
names, s, w, outer, target,
maxIteration,
error_tol,
convergence,
min_target = 0.01,
debug = FALSE,
debugCell = FALSE)
Expand Down Expand Up @@ -208,7 +215,7 @@ allocate <- function(

} else {

w <- adjustWeights(w, unlist(areas), target)
w <- adjustWeights(w, unlist(areas), target, convergence)
s <- shiftSites(s, k)
w <- shiftWeights(s, w)
}
Expand Down
9 changes: 9 additions & 0 deletions R/voronoiTreemap.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -144,6 +151,7 @@ voronoiTreemap <- function(
shape = "rectangle",
maxIteration = 100,
error_tol = 0.01,
convergence = "intermediate",
seed = NULL,
positioning = "regular",
verbose = FALSE,
Expand Down Expand Up @@ -305,6 +313,7 @@ voronoiTreemap <- function(
target = weights,
maxIteration = maxIteration,
error_tol = error_tol,
convergence = convergence,
outer = sfpoly,
debug = debug
)
Expand Down
44 changes: 36 additions & 8 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -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")
```
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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",
Expand Down
74 changes: 66 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
WeightedTreemaps
================
Michael Jahn, David Leslie, Ahmadou Dicko, Paul Murrell
2024-01-07
2024-11-04

<!-- include logo-->

Expand All @@ -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.

<img src="images/unnamed-chunk-2-1.png" width="50%" style="display: block; margin: auto;" />
<img src="images/fig_example-1.png" width="50%" style="display: block; margin: auto;" />

## News

Expand Down Expand Up @@ -148,7 +148,9 @@ Draw the treemap.
drawTreemap(tm, label_size = 2.5, label_color = "white")
```

<img src="images/unnamed-chunk-7-1.png" width="50%" style="display: block; margin: auto;" />
<img src="images/fig_cars_basic-1.png" width="50%" style="display: block; margin: auto;" />

### Drawing options

The `voronoiTreemap()` and `drawTreemap()` functions are separated in
order to allow drawing of the same treemap object in different ways.
Expand Down Expand Up @@ -183,7 +185,63 @@ drawTreemap(tm, title = "treemap 4", label_size = 2,
title_color = "black", legend = TRUE)
```

<img src="images/unnamed-chunk-8-1.png" width="100%" style="display: block; margin: auto;" />
<img src="images/fig_cars_colors-1.png" width="100%" style="display: block; margin: auto;" />
\### 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.
```

<img src="images/fig_cars_conv-1.png" width="100%" style="display: block; margin: auto;" />

### Positioning of cells

Expand Down Expand Up @@ -236,7 +294,7 @@ drawTreemap(tm3, title = "positioning = 'clustered'", border_size = 3,
add = TRUE, layout = c(1,3), position = c(1, 3))
```

<img src="images/unnamed-chunk-10-1.png" width="100%" style="display: block; margin: auto;" />
<img src="images/fig_pos-1.png" width="100%" style="display: block; margin: auto;" />

### Custom initial shapes

Expand Down Expand Up @@ -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))
```

<img src="images/unnamed-chunk-13-1.png" width="100%" style="display: block; margin: auto;" />
<img src="images/fig_shapes-1.png" width="100%" style="display: block; margin: auto;" />

### Advanced example for Voronoi treemaps

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -559,7 +617,7 @@ drawTreemap(tm,
)
```

<img src="images/unnamed-chunk-23-1.png" width="100%" style="display: block; margin: auto;" />
<img src="images/fig_sunburst-1.png" width="100%" style="display: block; margin: auto;" />

## References and other treemap packages

Expand Down
Binary file added images/fig_cars_basic-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/fig_cars_colors-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/fig_cars_conv-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/fig_example-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/fig_pos-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
Binary file removed images/unnamed-chunk-10-1.png
Binary file not shown.
Binary file removed images/unnamed-chunk-2-1.png
Binary file not shown.
Binary file removed images/unnamed-chunk-7-1.png
Binary file not shown.
Binary file removed images/unnamed-chunk-8-1.png
Binary file not shown.
9 changes: 9 additions & 0 deletions man/voronoiTreemap.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 25 additions & 1 deletion vignettes/WeightedTreemaps.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand Down
Loading

0 comments on commit 2f296ad

Please sign in to comment.