Package {mascarade}


Type: Package
Title: Generating Cluster Masks for Single-Cell Dimensional Reduction Plots
Version: 0.4.1
Description: Implements a procedure to automatically generate 2D masks for clusters on dimensional reduction plots from methods like t-SNE (t-distributed stochastic neighbor embedding) or UMAP (uniform manifold approximation and projection), with a focus on single-cell RNA-sequencing data.
Imports: data.table, spatstat.geom (≥ 3.7-3), spatstat.explore, lifecycle, ggplot2, polyclip, polylabelr, ggforce, vctrs, rlang, cli, systemfonts, Rcpp
LinkingTo: Rcpp, BH
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Depends: R (≥ 3.5)
Suggests: testthat (≥ 3.1.5), rmarkdown, knitr, patchwork, Seurat, scales, SeuratObject, svglite, colorrepel (≥ 0.5.0)
Config/testthat/edition: 3
VignetteBuilder: knitr
URL: https://alserglab.github.io/mascarade/
BugReports: https://github.com/alserglab/mascarade/issues
RoxygenNote: 7.3.3
NeedsCompilation: yes
Packaged: 2026-07-21 23:18:48 UTC; runner
Author: Alexey Sergushichev [aut, cre]
Maintainer: Alexey Sergushichev <alsergbox@gmail.com>
Repository: CRAN
Date/Publication: 2026-07-21 23:50:07 UTC

Build the box-fit R-tree for the cluster polygons

Description

Constructs a Boost.Geometry R-tree over the cluster polygon envelopes (and keeps the polygons) so the candidate and polish kernels can answer "does this label box overlap any cluster?" quickly. Rebuilt once per placement; the mask itself is not.

Usage

buildBoxFit(polysx, polysy)

Arguments

polysx, polysy

Lists of parallel numeric x/y vectors, one ring per cluster.

Value

An external-pointer (⁠XPtr<BoxFit>⁠) handle to the box-fit structure.


Effective length used to rank label candidates

Description

Per candidate, the length the optimizer minimises after the conflict counts: the base leader length, plus the arc of the leader inside any FOREIGN cluster (routes leaders around clusters), plus OVERFLOW_WEIGHT times how far the box overflows the viewport (steers labels in-bounds).

Usage

effectiveLength(
  len,
  ex,
  ey,
  tx,
  ty,
  lab,
  polysx,
  polysy,
  cxmin,
  cxmax,
  cymin,
  cymax,
  xlo,
  xhi,
  ylo,
  yhi
)

Arguments

len

Numeric base leader length per candidate.

ex, ey

Numeric leader start (anchor) per candidate.

tx, ty

Numeric leader target (pole) per candidate.

lab

Integer 1-indexed own-cluster of each candidate.

polysx, polysy

Lists of parallel numeric x/y vectors, one ring per cluster.

cxmin, cxmax, cymin, cymax

Numeric padded box extents per candidate.

xlo, xhi, ylo, yhi

Numeric viewport bounds (already inset by label.buffer).

Value

Numeric vector of effective lengths, one per candidate.


Example data with UMAP points from PBMC3K dataset.

Description

The object is a list with three elements:

  1. dims – matrix of UMAP coordinates of the cells,

  2. clusters – vector of cell population annotations,

  3. features – matrix withgene expression for several genes.


Generate ggplot2 layers for a labeled cluster mask

Description

Convenience helper that returns a list of ggplot2 components that draws polygon-like outlines and places cluster labels. The plotting limits are expanded (via limits.expand) to provide extra room for labels.

Usage

fancyMask(
  maskTable,
  ratio = NULL,
  limits.expand = ifelse(label, 0.1, 0.05),
  linewidth = 1,
  shape.expand = linewidth * unit(-1, "pt"),
  cols = "auto",
  label = TRUE,
  label.largest = TRUE,
  label.fontsize = 10,
  label.buffer = unit(2, "mm"),
  label.hardpad = unit(0, "pt"),
  label.softpad = unit(6, "pt"),
  label.fontface = "plain",
  label.margin = margin(2, 2, 2, 2, "pt"),
  label.width = NULL,
  simp_ratio = 0.001,
  con.type = "ledge"
)

Arguments

maskTable

A data.frame of mask coordinates. The first two columns are interpreted as x/y coordinates (in that order). Must contain at least the columns cluster (a factor) and group (grouping identifier passed to geom_mark_shape()).

ratio

Optional aspect ratio passed to ggplot2::coord_cartesian(). Use 1 for equal scaling. Default is NULL (no fixed ratio).

limits.expand

Numeric scalar giving the fraction of the x/y range to expand on both sides when setting plot limits. Default is 0.1 with labels and 0.05 with no labels.

linewidth

Line width passed to geom_mark_shape() for the outline. Default is 1.

shape.expand

Expansion or contraction applied to the marked shapes, passed to geom_mark_shape(expand = ...). Default is unit(-linewidth, "pt").

cols

Color specification for cluster outlines (and labels). One of:

  • "auto" (default) — inspects the plot at the time fancyMask() is added with +. If a layer maps colour to a discrete (non-numeric) variable, the mask joins that scale via aes(colour = cluster) so colours stay in sync regardless of ⁠scale_color_*()⁠ order. Otherwise (continuous colour, constant colour, or no colour aesthetic) explicit colours from scales::hue_pal() are baked in and the plot's scale system is left untouched.

  • "inherit" — always maps colour as an aesthetic (aes(colour = cluster)), unconditionally joining whatever colour scale is present. Useful when you want to force scale sharing; will error if the existing scale is continuous.

  • A palette function that accepts a single integer n and returns n colors (e.g., scales::hue_pal(), rainbow).

  • A single color string — applied to every cluster.

  • An unnamed character vector of length equal to the number of clusters — colors are assigned to clusters in factor-level order.

  • A named character vector — names must match cluster levels; order does not matter.

label

Boolean flag whether the labels should be displayed.

label.largest

Boolean flag. When TRUE (default), only the largest part of each cluster is labelled; smaller disconnected parts are drawn but not labelled. When FALSE, all parts are labelled. Ignored when label = FALSE.

label.fontsize

Label font size passed to geom_mark_shape(). Default is 10.

label.buffer

Polygon padding passed to geom_mark_shape(): cluster polygons are dilated by this distance and labels are kept out of the dilated zone, so each label keeps a gap from its cluster outline. Default unit(2, "mm"); unit(0, "mm") disables.

label.hardpad

Hard box clearance passed to geom_mark_shape(): each label box is grown by this padding for all placement decisions, so labels keep at least this gap from each other. Default unit(0, "pt") (the label margin usually suffices; raise it mainly for con.type = "box").

label.softpad

Extra box spacing the polish step aims for, on top of label.hardpad, passed to geom_mark_shape(). Default unit(6, "pt").

label.fontface

Label font face passed to geom_mark_shape(). Default is "plain".

label.margin

Label margin passed to geom_mark_shape(). Default is margin(2, 2, 2, 2, "pt").

label.width

Soft target width for wrapping labels, passed to geom_mark_shape(). A grid unit (e.g. unit(30, "mm")); labels are balanced across lines to keep line widths even and close to this width, without a short dangling line. NULL (default) leaves labels unwrapped. See geom_mark_shape() for details.

simp_ratio

Fraction of the polygon bounding-box area used to simplify cluster polygons before label placement: small inward (concave) vertices whose cut-off area is below simp_ratio * bbox_area are removed, which speeds up the box-fit and leader-routing geometry (both scale with vertex count). The simplified polygon encloses the original, so labels never overlap the real cluster. Larger values simplify more; set to 0 to disable. Default 0.001.

con.type

Leader / label-mark style passed to geom_mark_shape(): one of "ledge", "line", "box", or "none" (see the geom_mark_shape() Details). Default "ledge".

Details

The first two columns of maskTable are used as x/y coordinates. Cluster labels are taken from maskTable$cluster. Shapes are grouped by maskTable$group.

Value

A list of ggplot2 components suitable for adding to a plot with +, containing a ggplot2::coord_cartesian() specification and a geom_mark_shape() layer.

See Also

Examples

data("exampleMascarade")
maskTable <- generateMask(dims=exampleMascarade$dims,
                          clusters=exampleMascarade$clusters)
library(ggplot2)
basePlot <- ggplot(do.call(cbind, exampleMascarade)) +
    geom_point(aes(x=UMAP_1, y=UMAP_2, color=GNLY)) +
    scale_color_gradient2(low = "#404040", high="red") +
    theme_classic()

basePlot + fancyMask(maskTable, ratio=1, cols=scales::hue_pal())


Visible leader end (first mask-boundary hit)

Description

For each label, the leader runs from its box anchor (ex, ey) to the cluster pole (tx, ty), which lies inside the cluster. The drawn leader stops at the mask boundary: the first point where the anchor->pole segment crosses the label's own cluster ring. When the segment does not cross the ring the leader runs all the way to the pole.

Usage

firstLeaderHit(ex, ey, tx, ty, polysx, polysy)

Arguments

ex, ey

Numeric leader-start (anchor) coordinates, one per label.

tx, ty

Numeric pole (leader target) coordinates, one per label.

polysx, polysy

Lists of parallel numeric x/y vectors, one ring per label (its own cluster).

Value

A list with numeric bx, by: the visible leader end, one per label.


Continuous force-directed label polish

Description

Pattern-search descent on the (squared) EFFECTIVE length under a hard conflict guard. The effective length of a label is its centre-to-pole leader length, plus the arc of the leader that runs inside any foreign cluster (routing leaders around clusters), plus a soft viewport overflow penalty – the same quantity the upstream effectiveLength() ranking minimises. A box-box spacing penalty is added on top. Starting from a conflict-free layout the search only accepts conflict-free neighbours (free-space check via the BoxFit R-tree), so feasibility is preserved. Overflow is a SOFT term folded into the effective length, not a hard clip, so an off-panel label can be walked back in-bounds and a label leaves the panel only when that lowers total energy. The leader anchor rule mirrors R's .anchorPoint(), so both the conflict guard and the foreign-cluster arc match the drawn geometry.

Usage

forcePolish(
  boxfit,
  cx0,
  cy0,
  hw,
  hh,
  tx,
  ty,
  polysx,
  polysy,
  pad,
  xlo,
  xhi,
  ylo,
  yhi,
  iters,
  step,
  MU,
  pad_tgt,
  stepmin,
  con_type,
  sq = TRUE
)

Arguments

boxfit

External pointer from buildBoxFit() (the cluster keep-out).

cx0, cy0

Numeric starting label-centre coordinates.

hw, hh

Numeric per-label box half-sizes.

tx, ty

Numeric per-label pole (leader target).

polysx, polysy

Lists of parallel numeric x/y vectors, one true mask ring per cluster (aligned with the labels), used for the foreign-cluster arc term.

pad

Numeric hard box clearance.

xlo, xhi, ylo, yhi

Numeric viewport bounds.

iters

Integer iteration count.

step

Numeric initial pattern-search step.

MU

Numeric weight of the box-box spacing penalty.

pad_tgt

Numeric target inter-box spacing.

stepmin

Numeric smallest step tried before abandoning a direction.

con_type

Integer leader style: 0 = "ledge" (corner), otherwise "line"/"box"/"none".

sq

Logical; if TRUE, the length term uses the squared distance.

Value

A list with numeric cx, cy: the polished label centres.


Generate mask for clusters on 2D dimensional reduction plots

Description

Internally the function rasterizes and smoothes the density plots.

Usage

generateMask(
  dims,
  clusters,
  gridSize = 200,
  expand = 0.005,
  minDensity = lifecycle::deprecated(),
  smoothSigma = NA,
  minSize = 10,
  kernel = lifecycle::deprecated(),
  type = lifecycle::deprecated()
)

Arguments

dims

matrix of point coordinates. Rows are points, columns are dimensions. Only the first two columns are used.

clusters

vector of cluster annotations. Should be the same length as the number of rows in dims.

gridSize

target width and height of the raster used internally

expand

distance used to expand borders, represented as a fraction of sqrt(width*height). Default: 1/200.

minDensity

Deprecated. Doesn't do anything.

smoothSigma

Deprecated. Parameter controlling smoothing and joining close cells into groups, represented as a fraction of sqrt(width*height). Increasing this parameter can help dealing with sparse regions.

minSize

Groups of less than minSize points are ignored, unless it is the only group for a cluster

kernel

Deprecated. Doesn't do anything.

type

Deprecated. Doesn't do anything.

Value

data.table with points representing the mask borders. Each individual border line corresponds to a single level of group column. Cluster assignment is in cluster column. Within each cluster, parts are ordered by decreasing polygon area so that part index 1 is always the largest disconnected component.

Examples

data("exampleMascarade")
maskTable <- generateMask(dims=exampleMascarade$dims,
                          clusters=exampleMascarade$clusters)
data <- data.frame(exampleMascarade$dims,
                   cluster=exampleMascarade$clusters,
                   exampleMascarade$features)
library(ggplot2)
ggplot(data, aes(x=UMAP_1, y=UMAP_2)) +
    geom_point(aes(color=cluster)) +
    geom_path(data=maskTable, aes(group=group)) +
    coord_fixed() +
    theme_classic()

Generates mask from a Seurat object. Requires SeuratObject package.

Description

Generates mask from a Seurat object. Requires SeuratObject package.

Usage

generateMaskSeurat(
  object,
  reduction = NULL,
  group.by = NULL,
  gridSize = 200,
  expand = 0.005,
  minSize = 10
)

Arguments

object

Seurat object

reduction

character vector specifying which reduction to use (default: DefaultDimReduc(object))

group.by

character vector specifying which field to use for clusters (default: "ident")

gridSize

target width and height of the raster used internally

expand

distance used to expand borders, represented as a fraction of sqrt(width*height). Default: 1/200.

minSize

Groups of less than minSize points are ignored, unless it is the only group for a cluster

Value

data.table with points representing the mask borders. Each individual border line corresponds to a single level of group column. Cluster assignment is in cluster column.

Examples

# only run if Seurat is installed
if (require("Seurat")) {
    data("pbmc_small")
    maskTable <- generateMaskSeurat(pbmc_small)

    library(ggplot2)
    # not the best plot, see vignettes for better examples
    DimPlot(pbmc_small) +
        geom_path(data=maskTable, aes(x=tSNE_1, y=tSNE_2, group=group))
}

Annotate areas with polygonal shapes

Description

This geom lets you annotate sets of points via polygonal shapes. Unlike other ⁠ggforce::geom_mark_*⁠ functions, geom_mark_shape should be explicitly provided with the shape coordinates. As in ggforce::geom_shape, the polygon can be expanded/contracted and corners can be rounded, which is controlled by expand and radius parameters.

Usage

geom_mark_shape(
  mapping = NULL,
  data = NULL,
  stat = "identity",
  position = "identity",
  expand = 0,
  radius = 0,
  label.margin = margin(2, 2, 2, 2, "mm"),
  label.width = NULL,
  label.minwidth = 0,
  label.hjust = 0,
  label.fontsize = 12,
  label.family = "",
  label.lineheight = 1,
  label.fontface = c("bold", "plain"),
  label.fill = "white",
  label.colour = "black",
  label.buffer = unit(10, "mm"),
  label.hardpad = unit(0, "pt"),
  label.softpad = unit(6, "pt"),
  con.colour = "black",
  con.size = 0.5,
  con.type = "ledge",
  con.linetype = 1,
  con.border = "one",
  con.cap = unit(3, "mm"),
  con.arrow = NULL,
  simp_ratio = 0.001,
  ...,
  na.rm = FALSE,
  show.legend = NA,
  inherit.aes = TRUE
)

Arguments

mapping

Set of aesthetic mappings created by aes(). If specified and inherit.aes = TRUE (the default), it is combined with the default mapping at the top level of the plot. You must supply mapping if there is no plot mapping.

data

The data to be displayed in this layer. There are three options:

If NULL, the default, the data is inherited from the plot data as specified in the call to ggplot().

A data.frame, or other object, will override the plot data. All objects will be fortified to produce a data frame. See fortify() for which variables will be created.

A function will be called with a single argument, the plot data. The return value must be a data.frame, and will be used as the layer data. A function can be created from a formula (e.g. ~ head(.x, 10)).

stat

The statistical transformation to use on the data for this layer. When using a ⁠geom_*()⁠ function to construct a layer, the stat argument can be used the override the default coupling between geoms and stats. The stat argument accepts the following:

  • A Stat ggproto subclass, for example StatCount.

  • A string naming the stat. To give the stat as a string, strip the function name of the stat_ prefix. For example, to use stat_count(), give the stat as "count".

  • For more information and other ways to specify the stat, see the layer stat documentation.

position

A position adjustment to use on the data for this layer. This can be used in various ways, including to prevent overplotting and improving the display. The position argument accepts the following:

  • The result of calling a position function, such as position_jitter(). This method allows for passing extra arguments to the position.

  • A string naming the position adjustment. To give the position as a string, strip the function name of the position_ prefix. For example, to use position_jitter(), give the position as "jitter".

  • For more information and other ways to specify the position, see the layer position documentation.

expand

A numeric or unit vector of length one, specifying the expansion amount. Negative values will result in contraction instead. If the value is given as a numeric it will be understood as a proportion of the plot area width.

radius

As expand but specifying the corner radius.

label.margin

The margin around the annotation boxes, given by a call to ggplot2::margin().

label.width

Soft target width for wrapping the label (and description). A grid unit (e.g. unit(30, "mm")). The text is balanced across lines so line widths are even and close to this width, avoiding a short dangling line; a line may slightly exceed it to prevent an orphan, and an over-long single word is never broken. It is a soft cap: the box shrinks to fit the wrapped text (never forced to this exact width). NULL (default) leaves the label unwrapped.

label.minwidth

The minimum width to provide for the description. If the size of the label exceeds this, the description is allowed to fill as much as the label.

label.hjust

The horizontal justification for the annotation. If it contains two elements the first will be used for the label and the second for the description.

label.fontsize

The size of the text for the annotation. If it contains two elements the first will be used for the label and the second for the description.

label.family

The font family used for the annotation. If it contains two elements the first will be used for the label and the second for the description.

label.lineheight

The height of a line as a multipler of the fontsize. If it contains two elements the first will be used for the label and the second for the description.

label.fontface

The font face used for the annotation. If it contains two elements the first will be used for the label and the second for the description.

label.fill

The fill colour for the annotation box. Use "inherit" to use the fill from the enclosure or "inherit_col" to use the border colour of the enclosure.

label.colour

The text colour for the annotation. If it contains two elements the first will be used for the label and the second for the description. Use "inherit" to use the border colour of the enclosure or "inherit_fill" to use the fill colour from the enclosure.

label.buffer

Polygon padding: cluster polygons are dilated by this distance and labels are kept out of the dilated zone, leaving a gap between each label and its cluster outline. A grid unit; unit(0, "mm") disables it. Default unit(10, 'mm').

label.hardpad

Hard box clearance: each label box is grown by this padding for all placement decisions (seed slots, label-label and label-leader conflict tests, and the polish), so labels keep at least this gap from each other. A grid unit. Defaults to unit(0, 'pt') – the label margin usually gives enough separation; raise it mainly for con.type = 'box', where the drawn box outlines would otherwise touch.

label.softpad

Soft box spacing the polish step additionally aims for, on top of label.hardpad (it does not tighten the hard conflict tests). A grid unit. Default unit(6, 'pt').

con.colour

The colour for the line connecting the annotation to the mark. Use "inherit" to use the border colour of the enclosure or "inherit_fill" to use the fill colour from the enclosure.

con.size

The width of the connector. Use "inherit" to use the border width of the enclosure.

con.type

Leader / label-mark style: one of "ledge", "line", "box", or "none" (see Details). Default "ledge".

con.linetype

The linetype of the connector. Use "inherit" to use the border linetype of the enclosure.

con.border

The bordertype of the connector. Either "one" (to draw a line on the horizontal side closest to the mark), "all" (to draw a border on all sides), or "none" (not going to explain that one).

con.cap

The distance before the mark that the line should stop at.

con.arrow

An arrow specification for the connection using grid::arrow() for the end pointing towards the mark.

simp_ratio

Fraction of the polygon bounding-box area used to simplify cluster polygons before label placement (removes small inward vertices; the simplified polygon encloses the original, so labels never overlap the real shape). Speeds up placement geometry. Larger values simplify more; 0 disables. Default 0.001.

...

Other arguments passed on to layer()'s params argument. These arguments broadly fall into one of 4 categories below. Notably, further arguments to the position argument, or aesthetics that are required can not be passed through .... Unknown arguments that are not part of the 4 categories below are ignored.

  • Static aesthetics that are not mapped to a scale, but are at a fixed value and apply to the layer as a whole. For example, colour = "red" or linewidth = 3. The geom's documentation has an Aesthetics section that lists the available options. The 'required' aesthetics cannot be passed on to the params. Please note that while passing unmapped aesthetics as vectors is technically possible, the order and required length is not guaranteed to be parallel to the input data.

  • When constructing a layer using a ⁠stat_*()⁠ function, the ... argument can be used to pass on parameters to the geom part of the layer. An example of this is stat_density(geom = "area", outline.type = "both"). The geom's documentation lists which parameters it can accept.

  • Inversely, when constructing a layer using a ⁠geom_*()⁠ function, the ... argument can be used to pass on parameters to the stat part of the layer. An example of this is geom_area(stat = "density", adjust = 0.5). The stat's documentation lists which parameters it can accept.

  • The key_glyph argument of layer() may also be passed on through .... This can be one of the functions described as key glyphs, to change the display of the layer in the legend.

na.rm

If FALSE, the default, missing values are removed with a warning. If TRUE, missing values are silently removed.

show.legend

logical. Should this layer be included in the legends? NA, the default, includes if any aesthetics are mapped. FALSE never includes, and TRUE always includes. It can also be a named logical vector to finely select the aesthetics to display.

inherit.aes

If FALSE, overrides the default aesthetics, rather than combining with them. This is most useful for helper functions that define both data and aesthetics and shouldn't inherit behaviour from the default plot specification, e.g. borders().

Details

con.type selects how each label is connected to its cluster:

Value

A ggplot2 layer (ggplot2::layer) that adds polygonal shape annotations to a plot.

Aesthetics

geom_mark_shape understand the following aesthetics (required aesthetics are in bold):

Annotation

All ⁠geom_mark_*⁠ allow you to put descriptive textboxes connected to the mark on the plot, using the label and description aesthetics. The textboxes are automatically placed close to the mark, but without obscuring any of the datapoints in the layer. The placement is dynamic so if you resize the plot you'll see that the annotation might move around as areas become big enough or too small to fit the annotation. If there's not enough space for the annotation without overlapping data it will not get drawn. In these cases try resizing the plot, change the size of the annotation, or decrease the buffer region around the marks.

Filtering

Often marks are used to draw attention to, or annotate specific features of the plot and it is thus not desirable to have marks around everything. While it is possible to simply pre-filter the data used for the mark layer, the ⁠geom_mark_*⁠ geoms also comes with a dedicated filter aesthetic that, if set, will remove all rows where it evalutates to FALSE. There are multiple benefits of using this instead of prefiltering. First, you don't have to change your data source, making your code more adaptable for exploration. Second, the data removed by the filter aesthetic is remembered by the geom, and any annotation will take care not to overlap with the removed data.

Examples

library(ggplot2)
shape1 <- data.frame(
    x = c(0, 3, 3, 2, 2, 1, 1, 0),
    y = c(0, 0, 3, 3, 1, 1, 3, 3),
    label = "U-shape",
    description = "two prongs on a base"
)
shape2 <- data.frame(
    x = c(0, 3, 3, 0)+4,
    y = c(0, 0, 3, 3),
    label = "square",
    description = "four equal sides"
)
shape3 <- data.frame(
    x = c(0, 1.5, 3, 1.5)+8,
    y = c(1.5, 0, 1.5, 3),
    label = "diamond",
    description = "a square on its corner"
)
shapes <- rbind(shape1, shape2, shape3)

# Label only
ggplot(shapes, aes(x=x, y=y, label=label, color=label, fill=label)) +
    geom_mark_shape() +
    ylim(0, 5)

# Label with a secondary description line
ggplot(shapes, aes(x=x, y=y, label=label, description=description,
                   color=label, fill=label)) +
    geom_mark_shape() +
    ylim(0, 5)



Hungarian (Jonker-Volgenant) assignment

Description

O(n^3) minimum-cost assignment on a square cost matrix. Used by the boundary seed (one solve per column) to match labels to stacked slots.

Usage

hungarian(cost)

Arguments

cost

Square numeric cost matrix (cost[i, j] = cost of assigning row i to column j).

Value

Integer vector res where res[i] is the 0-indexed column assigned to row i, minimising the total cost.


Build the label + leader grobs for a mark

Description

Draw-time worker for makeContent.shape_enc(): dilates the cluster polygons by buffer (the box keep-out), calls my_place_labels() for the placement, positions the label box grobs and builds the leader polylines (anchor -> visible mask-boundary end, plus the horizontal ledge for con_type == "ledge").

Usage

my_make_label(
  labels,
  dims,
  polygons,
  ghosts,
  buffer,
  con_type,
  con_cap,
  con_gp,
  arrow,
  simp_ratio = 0.001,
  hardpad = unit(0, "pt"),
  softpad = unit(0, "pt")
)

Arguments

labels

List of label-box grobs (one per mark part).

dims

List of measured label box sizes c(w, h) in mm.

polygons

List of cluster polygons (list(x, y)) in mm.

ghosts

Points to avoid (currently unused by the placer).

buffer

Grid unit: the label.buffer polygon padding / box keep-out.

con_type

Leader style: "ledge", "line", "box", or "none".

con_cap

Numeric gap (mm) left between the leader end and the cluster.

con_gp

A gpar for the connectors (per drawn label).

arrow

Optional grid::arrow for the connectors.

simp_ratio

Numeric polygon-simplification fraction (see simplify_outer()).

hardpad

Grid unit: the label.hardpad hard box clearance.

softpad

Grid unit: the label.softpad extra polish-only target box spacing.

Value

A gList-ready list: the positioned label grobs followed by the connector grob.


Place labels at draw time (boundary-seed placer)

Description

Runs at draw time in the panel's millimetre space: builds the poles and the box-fit R-tree from the cluster polygons (cheap, ~20 ms for ~40 clusters; the expensive mask is not recomputed) and calls placeLabels(). The box-fit keep-out uses the dilated polygons while poles, leader ends and foreign-routing use the true ones, so leaders reach the real outline.

Usage

my_place_labels(
  rects,
  polygons,
  polygons_pad,
  bounds,
  simp_ratio = 0.001,
  con_type = "ledge",
  buffer = 0,
  hardpad = 0,
  softpad = 0
)

Arguments

rects

List of measured label box sizes c(w, h) in mm (a zeroed entry = not drawn).

polygons

List of true cluster polygons (list(x, y)) in mm.

polygons_pad

List of the same polygons dilated by label.buffer (the box keep-out).

bounds

Numeric c(width, height) of the panel in mm.

simp_ratio

Numeric polygon-simplification fraction (see simplify_outer()).

con_type

Leader style: "ledge", "line", "box", or "none".

buffer

Numeric label.buffer in mm; the overflow viewport is inset by it.

hardpad

Numeric label.hardpad in mm: hard box clearance folded into every placement rectangle (seed slots, sweeps and polish alike).

softpad

Numeric label.softpad in mm: extra target box spacing the polish aims for, on top of hardpad.

Details

Note: each cluster polygon here is a single list(x, y) ring; any mask holes are resolved upstream in generateMask(), so this layer treats every polygon as one simple ring.

Value

A list, one entry per input label: the placed centre c(x, y) in mm (NULL if not drawn), carrying attr(., "leaders") with c(ex, ey, bx, by, corner) per drawn label.


Single-move conflict sweep

Description

Multi-pass Gauss-Seidel refinement. Each pass reorders labels by their current conflict vector (box-box, leader-leader, leader-box, then leader length) descending, then moves each to its lexicographically best candidate versus the current others (including staying put). Stops when a pass makes no change.

Usage

oneMoveSweepKernel(
  cxmin,
  cxmax,
  cymin,
  cymax,
  ex,
  ey,
  tx,
  ty,
  len,
  rows,
  init,
  maxpass
)

Arguments

cxmin, cxmax, cymin, cymax

Numeric padded box extents, one per candidate.

ex, ey

Numeric leader start (anchor) per candidate.

tx, ty

Numeric leader target (pole) per candidate.

len

Numeric ranking length per candidate.

rows

List of integer vectors: the 0-indexed candidate rows available to each label.

init

Integer vector: the starting candidate index per label.

maxpass

Integer cap on the number of sweeps.

Value

Integer vector: the chosen candidate index per label.


Radial free-space label candidates

Description

From each cluster pole, marches ndir rays outward and emits a candidate box centre wherever a box of the label's size (plus pad) is cluster-free (a BoxFit R-tree query): at the near edge of every free interval and every intfill along wide ones. Candidates may fall partly outside the viewport – the effective-length overflow term ranks them, so a crowded label can take a minimally-clipped edge slot instead of the far seed.

Usage

radialCandidates(
  boxfit,
  poi,
  hw,
  hh,
  pad,
  ndir,
  step,
  rstart,
  rmax,
  intfill,
  dedup
)

Arguments

boxfit

External pointer from buildBoxFit().

poi

K x 2 matrix of cluster poles (the ray origins).

hw, hh

Numeric per-label box half-sizes.

pad

Numeric hard box clearance added around each box.

ndir

Integer number of rays per pole.

step

Numeric radial step along each ray.

rstart, rmax

Numeric first and last radius searched.

intfill

Numeric spacing of extra candidates along a wide free interval.

dedup

Numeric grid size for de-duplicating nearby candidates.

Value

A data.frame with integer label (1-indexed) and numeric cx, cy.


Enclosing polygon simplification

Description

Greedily removes small concave (inward) vertices from a polygon: a vertex is dropped when the triangle it cuts off has area below max_area. Because only concave vertices are removed the simplified polygon ENCLOSES the original, so the box-fit keep-out built from it stays conservative. Used to cut vertex counts before placement.

Usage

simplify_outer(poly, max_area, min_vertices = 4L)

Arguments

poly

A list with numeric x, y (the polygon vertices).

max_area

Numeric area threshold; vertices whose cut-off triangle is smaller are removed.

min_vertices

Integer floor on the number of vertices kept.

Value

A list with simplified numeric x, y.


Two-move conflict / length refinement

Description

Per label (heaviest leader first), picks the lexicographically best move (by change in box-box, leader-leader, leader-box conflicts, then change in length) and applies it if it improves. A CONFLICT-FREE label uses a length branch-and-bound: candidates are length-ascending and a shorter c1 needs a partner only when exactly one other label currently sits in that slot (that label is the partner – it need not itself be in conflict), with the length bound pruning the length-increasing tail – the exact per-step optimum. A CONFLICTED label drops the pruning and searches ALL pairs of candidates (this label's against every other label's), driving out conflicts present in the input; only conflicted labels pay this cost.

Usage

twoMoveSweepKernel(
  cxmin,
  cxmax,
  cymin,
  cymax,
  ex,
  ey,
  tx,
  ty,
  len,
  rows,
  init,
  maxpass,
  sq
)

Arguments

cxmin, cxmax, cymin, cymax

Numeric padded box extents, one per candidate.

ex, ey

Numeric leader start (anchor) per candidate.

tx, ty

Numeric leader target (pole) per candidate.

len

Numeric ranking length per candidate.

rows

List of integer vectors: the 0-indexed candidate rows available to each label.

init

Integer vector: the starting candidate index per label.

maxpass

Integer cap on the number of passes.

sq

Logical; if TRUE the length objective uses the squared length.

Value

Integer vector: the chosen candidate index per label.