Plot Tags¶

Plot tags are short labels attached to a plot or composite figure. Tags are typically used for:

  • Figure numbering (Figure 1)
  • Panel labeling (A, B, C, ...)
  • Hierarchical figure organization
  • Annotating composite plots

The tag text is specified via labs(tag = "...") and can be customized via plotTag* parameters in theme().

In [1]:
%useLatestDescriptors
%use lets-plot
%use dataframe
In [2]:
LetsPlot.getInfo()
Out[2]:
Lets-Plot Kotlin API v.4.13.0. Frontend: Notebook with dynamically loaded JS. Lets-Plot JS v.4.9.0.
Outputs: Web (HTML+JS), Kotlin Notebook (Swing), Static SVG (hidden)

Default Appearance¶

In [3]:
val mpgDf = DataFrame.readCSV("https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/mpg2.csv")
val mpg = mpgDf.toMap()

letsPlot(mpg) {
    x = "vehicle weight (lbs.)"
    y = "miles per gallon"
    color = "miles per gallon"
} +
geomPoint(size = 7) +
scaleColorViridis(option = "B") +
ggsize(500, 280) +
labs(
    color = "MPG",
    tag = "Figure 1"   // <---
)
Out[3]:
2,000 3,000 4,000 5,000 10 20 30 40 Figure 1 miles per gallon vehicle weight (lbs.) MPG 10 20 30 40

Annotating Composite Plots¶

Each subplot is labeled using a tag for easy reference.

In [4]:
val rng = java.util.Random(7)
val n = 500
val x = List(n) { rng.nextGaussian() }
val y = x.map { xi -> xi * 0.7 + rng.nextGaussian() * 0.8 }
val grp = x.zip(y).map { (xi, yi) -> if (xi + yi > 0) "G1" else "G2" }
val r = x.zip(y).map { (xi, yi) -> kotlin.math.sqrt(xi * xi + yi * yi) }

val df = mapOf("x" to x, "y" to y, "grp" to grp, "r" to r)

val base = letsPlot(df) { x = "x"; y = "y" } +
    ggsize(320, 240) +
    theme(
        plotTag = elementText(size = 38, color = "light_sky_blue"),
        plotTagLocation = "panel",
        plotTagPosition = listOf(0.15, 0.85)
    )

// A: correlation pattern (plain scatter + smooth)
val pA = base +
    geomPoint(alpha = 0.35) +
    geomSmooth(method = "lm", se = true) +
    labs(title = "Trend", subtitle = "Linear relationship", tag = "A")

// B: group separation (color by group)
val pB = base +
    geomPoint(alpha = 0.5) { color = "grp" } +
    labs(title = "Groups", subtitle = "Two classes split by x+y", color = "Group", tag = "B")

// C: density / magnitude feature (map r to size)
val pC = base +
    geomPoint(alpha = 0.35) { size = "r" } +
    labs(title = "Magnitude", subtitle = "Point size ~ distance from origin", size = "r", tag = "C")

gggrid(listOf(pA, pB, pC), ncol = 3, align = true, hspace = 10) + ggsize(1020, 320)
Out[4]:
-2 0 2 -2 0 2 A Trend Linear relationship y x -2 0 2 -2 0 2 B Groups Two classes split by x+y y x Group G1 G2 -2 0 2 -2 0 2 C Magnitude Point size ~ distance from origin y x r 1 2 3 4

Annotating Composite Plots and the Composite Figure¶

You can add tags to mark subplots and also the composite plot itself.

In [5]:
val subplotTheme = theme(
    plotTag = elementText(size = 32, color = "gray"),
    plotTagLocation = "plot",
    plotTagPosition = "top-left",
    plotTagSuffix = ")",
    title = elementText(hjust = 0.05)
)

gggrid(listOf(pA + subplotTheme, pB + subplotTheme, pC + subplotTheme), ncol = 3, align = true, hspace = 10) +
    theme(
        plotTag = elementText(size = 24, color = "gray"),
        plotTagLocation = "margin",
        plotTagPosition = "bottom"
    ) +
    labs(tag = "Figure 1") +
    ggsize(1020, 320)
Out[5]:
Figure 1 -2 0 2 -2 0 2 A) Trend Linear relationship y x -2 0 2 -2 0 2 B) Groups Two classes split by x+y y x Group G1 G2 -2 0 2 -2 0 2 C) Magnitude Point size ~ distance from origin y x r 1 2 3 4

plotTagLocation¶

Defines where the tag is placed relative to the plot:

  • "plot" - inside the full plot area
  • "panel" - inside the data panel
  • "margin" - in the outer margin; the layout is adjusted to prevent overlapping with other plot elements
In [6]:
val base = letsPlot(mpg) { x = "number of cylinders"; y = "miles per gallon" } +
    geomBoxplot() +
    labs(tag = "A1") +
    themeGray() +
    theme(
        plotTag = elementText(color = "blue", size = 24),
        plotBackground = elementRect(color = "light_gray", size = 1)
    ) +
    ggsize(360, 200)

val pPlot = base + theme(plotTagLocation = "plot") +
    labs(subtitle = "tagLocation = \"plot\"")

val pPanel = base + theme(plotTagLocation = "panel") +
    labs(subtitle = "tagLocation = \"panel\"")

val pMargin = base + theme(plotTagLocation = "margin") +
    labs(subtitle = "tagLocation = \"margin\"")

gggrid(listOf(pPlot, pPanel, pMargin), ncol = 3, hspace = 6) + ggsize(1000, 320)
Out[6]:
4 6 8 10 20 30 40 A1 tagLocation = "plot" miles per gallon number of cylinders 4 6 8 10 20 30 40 A1 tagLocation = "panel" miles per gallon number of cylinders 4 6 8 10 20 30 40 A1 tagLocation = "margin" miles per gallon number of cylinders

plotTagPosition¶

Defines the anchor point of the tag inside the selected alignment area.

Accepted values:

  • Named positions:
    "top-left" (default), "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"

  • Numeric position:
    listOf(x, y) - normalized coordinates

    • (0, 0) = bottom-left
    • (1, 1) = top-right

When plotTagLocation = "margin", only predefined position names are supported.
Use hjust and vjust in elementText() to fine-tune alignment along the corresponding edge.

In [7]:
val pieData = mapOf(
    "name" to listOf("a", "b", "c", "d", "b"),
    "value" to listOf(40, 90, 10, 50, 20)
)

val base = letsPlot(pieData) +
    geomPie(stat = Stat.identity) { slice = "value"; fill = "name" } +
    themeVoid() +
    theme(
        plotTag = elementText(size = 24, color = "red"),
        plotBackground = elementRect(color = "light_gray", size = 1)
    )

val p1 = base + labs(title = "margin + top", tag = "A") +
    theme(plotTagLocation = "margin", plotTagPosition = "top")

val p2 = base + labs(title = "panel + [0.8, 0.1]", tag = "B") +
    theme(plotTagLocation = "panel", plotTagPosition = listOf(0.8, 0.1))

val p3 = base + labs(title = "plot + left", tag = "C") +
    theme(plotTagLocation = "plot", plotTagPosition = "left")

val p4 = base + labs(title = "margin + right", tag = "D") +
    theme(plotTagLocation = "margin", plotTagPosition = "right")

gggrid(listOf(p1, p2, p3, p4), ncol = 2, align = true, hspace = 16, vspace = 12) + ggsize(600, 400)
Out[7]:
A margin + top name a b c d B panel + [0.8, 0.1] name a b c d C plot + left name a b c d D margin + right name a b c d

plotTagPrefix / plotTagSuffix¶

To avoid repeating common parts of a tag, use plotTagPrefix and plotTagSuffix.

In [8]:
gggrid(listOf(base + labs(tag = "1"), base + labs(tag = "2"))) +
    theme(
        plotTagLocation = "margin",
        plotTagPosition = "bottom",
        plotTagPrefix = "Figure ",   // <---
        plotTagSuffix = "."          // <---
    ) +
    ggsize(400, 200)
Out[8]:
Figure 1. name a b c d Figure 2. name a b c d

Fine Tuning¶

When using plotTagLocation = "plot" or "panel", other plot elements may overlap the tag, or the tag may overlap them.
The "margin" option reserves space for the tag and you can tune how exactly other elements are relocated.

In [9]:
val xs = (0 until 10).flatMap { i -> (0 until 7).map { i } }
val ys = (0 until 10).flatMap { (0 until 7).toList() }
val vs = xs.zip(ys).map { (xi, yi) -> kotlin.math.sin(xi / 1.4) + kotlin.math.cos(yi / 1.1) }
val grid = mapOf("x" to xs, "y" to ys, "v" to vs)

val base = letsPlot(grid) { x = "x"; y = "y" } +
    geomTile { fill = "v" } +
    scaleFillGradient(low = "steelblue", high = "firebrick") +
    theme(
        plotMargin = listOf(14, 14, 14, 14),
        plotTag = elementText(size = 18),
        plotBackground = elementRect(color = "lightgray", size = 2)
    )

// 1. Default: plot + top-left (no layout shift)
val p1 = base +
    labs(
        title = "Default: plot + top-left",
        subtitle = "No layout shift (tag over plot area).",
        tag = "Tag"
    ) +
    theme(
        plotTagLocation = "plot",
        plotTagPosition = "top-left"
    )

// 2. margin + top-left (shifts both directions)
val p2 = base +
    labs(
        title = "margin + top-left",
        subtitle = "Shifts BOTH: top + left margins reserved.",
        tag = "Tag"
    ) +
    theme(
        plotTagLocation = "margin",
        plotTagPosition = "top-left"
    )

// 3. margin + top, hjust=0 (vertical shift only)
val p3 = base +
    labs(
        title = "margin + top, hjust=0",
        subtitle = "Shifts VERTICAL only.",
        tag = "Tag"
    ) +
    theme(
        plotTag = elementText(hjust = 0.0),
        plotTagLocation = "margin",
        plotTagPosition = "top"
    )

// 4. margin + left, vjust=1 (horizontal shift only)
val p4 = base +
    labs(
        title = "margin + left, vjust=1",
        subtitle = "Shifts HORIZONTAL only.",
        tag = "Tag"
    ) +
    theme(
        plotTag = elementText(vjust = 1.0),
        plotTagLocation = "margin",
        plotTagPosition = "left"
    )

gggrid(listOf(p1, p2, p3, p4), ncol = 2, align = true, hspace = 12, vspace = 12) + ggsize(760, 560)
Out[9]:
0 5 10 0 2 4 6 Tag Default: plot + top-left No layout shift (tag over plot area). y x v -1 0 1 0 5 10 0 2 4 6 Tag margin + top-left Shifts BOTH: top + left margins reserved. y x v -1 0 1 0 5 10 0 2 4 6 Tag margin + top, hjust=0 Shifts VERTICAL only. y x v -1 0 1 0 5 10 0 2 4 6 Tag margin + left, vjust=1 Shifts HORIZONTAL only. y x v -1 0 1