Arrange text labels inside a polygon

polygon_label_fill(
  sp,
  labels,
  color = "black",
  border = NA,
  ref_sp = NULL,
  fontsize = 10,
  cex = 1,
  degrees = 0,
  dither_cex = 0.04,
  dither_color = 0.07,
  dither_degrees = 0,
  scale_width = -0.15,
  apply_n_scale = TRUE,
  buffer_w = 0,
  buffer_h = 0,
  label_method = c("hexagonal", "nonaligned", "regular", "random", "stratified",
    "clustered"),
  layout_degrees = -20,
  draw_buffer = FALSE,
  buffer_fill = "#FFFFFF77",
  buffer_border = "red",
  draw_points = FALSE,
  draw_labels = TRUE,
  seed = NULL,
  plot_style = c("base", "gg", "none"),
  verbose = FALSE,
  ...
)

Arguments

sp

object sp::SpatialPolygons

labels

character vector of labels, the length defines the number of coordinate positions to return.

color

vector of R compatible colors which defines the color of each label.

border

vector or NA with colors to define the border around each label.

ref_sp

object sp::SpatialPolygons used as a reference to compare the size of sp when apply_n_scale=TRUE. In general, fewer labels are placed more toward the center; also in general, this effect is applied less for smaller polygons.

fontsize

numeric value indicating the font size in points.

cex

numeric value used to resize all text labels by multiplying the font size.

degrees

numeric vector indicating the angle in degrees to rotate each text label, where positive values rotate in clockwise direction.

dither_cex

numeric or NULL, where a numeric value is applied to cex in random fashion to provide some visual heterogeneity in the cex for item labels. When dither_cex=0 or dither_cex=NULL then no adjustment is performed.

dither_color

numeric or NULL, where a numeric value is use to adjust each color slightly lighter or darker via jamba::makeColorDarker(). The effect is to make adjacent labels visibly different but in a subtle way.

dither_degrees

numeric or NULL, where a numeric value is used to adjust the text angle slightly more or less that the degrees value.

scale_width

numeric value or NULL, where a numeric value indicates the relative size of the polygon to use as a buffer around the polygon, and should be given as negative values. For example scale_width=-0.1 will create a buffer at 10% the size of the polygon.

apply_n_scale

logical indicating whether to adjust the polygon buffer based upon the number of labels, specifically so that few labels (1, 2, or 3 labels) have much higher buffer and therefore are positioned more central inside the polygon.

buffer_w, buffer_h

numeric width and height, respectively, used for additional buffer inside each polygon. These values are appropriate when the width of text label is known. The buffer polygon derived from scale_width and apply_n_scale is moved left, right, up, down, and the intersection of these operations is used with sp::spsample() to define label positions. In this way, labels should fit inside the original polygon without overlapping the boundary. This function does not define default values, because the actual text label width is dependent upon the diplay device properties at the time the plot is drawn, and this device may not even be open when this function is called.

label_method

character string indicating the layout type used by sp::spsample().

draw_buffer

logical indicating whether the buffer polygon should be drawn, intended for visual review of the processing.

buffer_fill, buffer_border

color values used when draw_buffer=TRUE.

draw_points

logical indicating whether to draw points at each coordinate position, intended for visual review of the processing.

draw_labels

logical indicating whether to draw text labels which is performed using gridtext::richtext_grob() inside a base R plot.

seed

numeric or NULL, where a numeric value is passed to set.seed() to make the dither_cex process reproducible. Set seed=NULL to disable this step.

plot_style

character string indicating the expected output plot style: "base" uses base R graphics, gridtext::richtext_grob(); "gg" uses ggplot2 style; "none" does not plot anything.

verbose

logical indicating whether to print verbose output.

Value

list that contains: items_df as a data.frame of item label coordinates; and g_labels as output from gridtext::richtext_grob() whose coordinates are defined as "native", or g_labels=NULL when plot_style="gg"; scale_width with the numeric value used; and sp_buffer with the sp::SpatialPolygons object representing the buffer region used for item labels.

Details

This function takes a vector of text labels, and will arrange those labels to fill the inside of a polygon. Currently the method uses sp::spsample() which has a few algorithms to generate point positions inside a polygon, and these positions are used to anchor text labels. Currently this method does no overlap detection.

The primary method to avoid overlap is to use label_method="hexagon" and degrees=20, since hexagonal layout tends to have points at roughly 0 and 60 degrees from one another, and 20 degree rotation tends to allow text labels to nestle beside each other without much overlap.

Examples

sp <- sp_ellipses(3, 3, xradius=1.2, yradius=3, rotation_degrees=15)
words <- jamba::unvigrep("[0-9]",
   jamba::vigrep("[a-zA-Z]", 
      unique(unlist(
      strsplit(as.character(packageDescription("venndir")),
      '[", _()<>:;/\n\t.@&=]+')))));
words <- words[nchar(words) > 2];
plot(sp, col="gold", border="gold4", lwd=2);
polygon_label_fill(sp=sp,
   degrees=-10,
   labels=words,
   dither_color=0.2,
   color="red2",
   cex=1.2)


plot(sp, col="gold", border="gold4", lwd=2);
polygon_label_fill(sp=sp,
   degrees=0,
   draw_buffer=FALSE,
   layout_degrees=45/2,
   buffer_w=0.4,
   label_method="regular",
   labels=jamba::mixedSort(words),
   dither_color=0.2,
   dither_cex=0.2,
   dither_degrees=0,
   color="red2",
   cex=1.2)


# iterate various options for reducing label overlap
par("mfrow"=c(2, 4));
for (lm in c("hexagonal", "regular")) {
for (ld in c(0, -45/2, -30, -45)) {
plot(sp, col="gold", border="gold4", lwd=2);
id <- ifelse(ld == 0, -20,
   ifelse(ld == -45, 15, 0));
polygon_label_fill(sp=sp,
   degrees=id,
   layout_degrees=ld,
   buffer_w=0.4,
   label_method=lm,
   #labels=seq_along(words),
   #labels=rep("word", length(words)),
   labels=paste0("word", seq_along(words)),
   dither_color=0,
   dither_cex=0,
   dither_degrees=0,
   color="navy",
   cex=0.7)
title(main=paste0("layout_degrees=",
   format(ld, digits=2),
   "\nlabel_method='", lm, "'",
   "\ndegrees=", id));
}
}

par("mfrow"=c(1, 1));

plot(sp, col="gold", border="gold4", lwd=2);
polygon_label_fill(sp=sp,
   degrees=-10,
   scale_width=-0.3,
   draw_buffer=TRUE,
   labels=words, dither_color=0.2, color="red2", cex=1.2)


setlist <- make_venn_test(100, 3);
vo <- venndir(setlist, return_items=TRUE, font_cex=0.01, proportional=FALSE);
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:9 
#>         overlap_set text        x        y vjust hjust halign rot padding    r
#> 9 set_A&set_B&set_C    1 4.999814 5.248994   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 9     pt #262626FF     0.14         NA       NA       1       1           pt
#>   num
#> 9   9
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:7 
#>   overlap_set text        x        y vjust hjust halign rot padding    r r_unit
#> 7 set_A&set_B    8 4.999106 6.748495   0.5   0.5    0.5   0    0.03 0.03     pt
#>   label_col fontsize border_col box_fill box_lty box_lwd padding_unit num
#> 7 #262626FF     0.14         NA       NA       1       1           pt   7
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:8 
#>   overlap_set text        x        y vjust hjust halign rot padding    r r_unit
#> 8 set_A&set_C    7 3.773922 4.632696   0.5   0.5    0.5   0    0.03 0.03     pt
#>   label_col fontsize border_col box_fill box_lty box_lwd padding_unit num
#> 8 #262626FF     0.14         NA       NA       1       1           pt   8
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:1 
#>   overlap_set      text       x        y vjust hjust halign rot padding    r
#> 1       set_A **set_A** 1.90869 6.955722   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 1     pt #262626FF     0.14         NA       NA       1       2           pt
#>   num
#> 1   1
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:4 
#>   overlap_set text        x        y vjust hjust halign rot padding    r r_unit
#> 4       set_A   16 3.019531 6.238855   0.5   0.5    0.5   0    0.03 0.03     pt
#>   label_col fontsize border_col box_fill box_lty box_lwd padding_unit num
#> 4 #262626FF     0.14         NA       NA       1       1           pt   4
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:5 
#>   overlap_set text        x        y vjust hjust halign rot padding    r r_unit
#> 5       set_B    7 6.980469 6.238855   0.5   0.5    0.5   0    0.03 0.03     pt
#>   label_col fontsize border_col box_fill box_lty box_lwd padding_unit num
#> 5 #262626FF     0.14         NA       NA       1       1           pt   5
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:2 
#>   overlap_set      text        x        y vjust hjust halign rot padding    r
#> 2       set_B **set_B** 8.091055 6.956222   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 2     pt #262626FF     0.14         NA       NA       1       2           pt
#>   num
#> 2   2
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:6 
#>   overlap_set text        x       y vjust hjust halign rot padding    r r_unit
#> 6       set_C    7 5.002174 3.08082   0.5   0.5    0.5   0    0.03 0.03     pt
#>   label_col fontsize border_col box_fill box_lty box_lwd padding_unit num
#> 6 #262626FF     0.14         NA       NA       1       1           pt   6
#> ##  (18:06:55) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:3 
#>   overlap_set      text        x        y vjust hjust halign rot padding    r
#> 3       set_C **set_C** 5.003101 1.700411   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 3     pt #262626FF     0.14         NA       NA       1       2           pt
#>   num
#> 3   3

# labels inside each venn overlap polygon
venn_spdf <- vo$venn_spdf;
label_df <- vo$label_df;
for (i in seq_len(nrow(venn_spdf))) {
   j <- match(venn_spdf$label[i], label_df$overlap_set);
   if (length(unlist(label_df[j,"items"])) > 0) {
   polygon_label_fill(sp=venn_spdf[i,],
      ref_sp=venn_spdf,
      color=venn_spdf$border[i],
      scale_width=-0.1,
      draw_buffer=TRUE,
      labels=unlist(label_df[j,"items"]));
   }
}


# same example as above but using proportional circles
vo <- venndir(setlist, font_cex=0.01, proportional=TRUE);
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:9 
#>         overlap_set text         x         y vjust hjust halign rot padding
#> 9 set_A&set_B&set_C    1 0.1564435 0.5133692   0.5   0.5    0.5   0    0.03
#>      r r_unit label_col fontsize border_col box_fill box_lty box_lwd
#> 9 0.03     pt #262626FF     0.14         NA       NA       1       1
#>   padding_unit num
#> 9           pt   9
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:7 
#>   overlap_set text         x         y vjust hjust halign rot padding    r
#> 7 set_A&set_B    8 0.5991818 -1.205417   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 7     pt #262626FF     0.14         NA       NA       1       1           pt
#>   num
#> 7   7
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:8 
#>   overlap_set text         x        y vjust hjust halign rot padding    r
#> 8 set_A&set_C    7 -1.454183 1.014376   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 8     pt #262626FF     0.14         NA       NA       1       1           pt
#>   num
#> 8   8
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:4 
#>   overlap_set text         x         y vjust hjust halign rot padding    r
#> 4       set_A   16 -2.414462 -1.780493   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 4     pt #262626FF     0.14         NA       NA       1       1           pt
#>   num
#> 4   4
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:1 
#>   overlap_set      text         x         y vjust hjust halign rot padding    r
#> 1       set_A **set_A** -4.163439 -2.986351   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 1     pt #262626FF     0.14         NA       NA       1       2           pt
#>   num
#> 1   1
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:5 
#>   overlap_set text        x          y vjust hjust halign rot padding    r
#> 5       set_B    7 2.844281 -0.8726355   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 5     pt #262626FF     0.14         NA       NA       1       1           pt
#>   num
#> 5   5
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:2 
#>   overlap_set      text        x         y vjust hjust halign rot padding    r
#> 2       set_B **set_B** 4.116615 -1.123098   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 2     pt #262626FF     0.14         NA       NA       1       2           pt
#>   num
#> 2   2
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:6 
#>   overlap_set text         x        y vjust hjust halign rot padding    r
#> 6       set_C    7 -1.064277 3.172044   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 6     pt #262626FF     0.14         NA       NA       1       1           pt
#>   num
#> 6   6
#> ##  (18:06:56) 07Jul2023:   draw_gridtext_groups(): grob_group_roundrect(),  k:3 
#>   overlap_set      text         x        y vjust hjust halign rot padding    r
#> 3       set_C **set_C** -1.380893 4.465791   0.5   0.5    0.5   0    0.03 0.03
#>   r_unit label_col fontsize border_col box_fill box_lty box_lwd padding_unit
#> 3     pt #262626FF     0.14         NA       NA       1       2           pt
#>   num
#> 3   3

# labels inside each venn overlap polygon
venn_spdf <- vo$venn_spdf;
label_df <- vo$label_df;
for (i in seq_len(nrow(venn_spdf))) {
   j <- match(venn_spdf$label[i], label_df$overlap_set);
   if (length(unlist(label_df[j,"items"])) > 0) {
   polygon_label_fill(sp=venn_spdf[i,],
      ref_sp=venn_spdf,
      color=venn_spdf$border[i],
      scale_width=-0.1,
      draw_buffer=TRUE,
      labels=unlist(label_df[j,"items"]));
   }
}