Plot Overlay with ggdeck()¶
The ggdeck() function enables multivariate comparison by synchronized overlaying of independent plots. This allows you to combine separate plot objects—each with its own coordinate system and geometries—into a unified view.
In [1]:
%useLatestDescriptors
%use dataframe
%use lets-plot
In [2]:
LetsPlot.getInfo()
Out[2]:
1. Delhi Mean Temperature Chart¶
In [3]:
var climateData = DataFrame.readCSV("https://raw.githubusercontent.com/JetBrains/lets-plot-docs/refs/heads/master/data/delhi_climate.csv")
climateData.head(3)
Out[3]:
In [4]:
climateData.schema().print()
In [5]:
val temperaturePlot = letsPlot(climateData.toMap()) +
geomLine() {
x = "date"
y = "meantemp"
color = "meantemp"
} +
scaleColorGradient2(
low = "#0571b0",
mid = "#f7f7f7",
high = "#ca0020",
midpoint = 30
) +
labs(y = "Mean Temperature [°C]") +
theme(
axisTextY = elementText(color = "#0571b0"),
axisTitleY = elementText(color = "#0571b0")
)
temperaturePlot
Out[5]:
2. Fictional 'Daily Ice Cream Cost' Data¶
In [6]:
fun calculateDailyCost(t: Double): Double {
val baseCost = 1.5
var surge = 0.0
if (t > 30.0) {
// Sharp surge above 30 degrees
surge = 0.15 * (t - 30.0).pow(2)
}
return baseCost + surge
}
In [7]:
// Synthesize daily cost data and aggregate by month
// 1. Map the daily cost to a new column
val costDataTemp = climateData.add("daily_cost") {
calculateDailyCost(it["meantemp"] as Double)
}
// 2. Group by Month-Start date and then aggregate
val costData = costDataTemp
.add("month_start") {
val date = it["date"] as kotlinx.datetime.LocalDate
kotlinx.datetime.LocalDate(date.year, date.month, 1)
}
.groupBy("month_start")
.aggregate {
val dailyCosts = "daily_cost"<Double>()
dailyCosts.mean() into "avg_daily_cost"
dailyCosts.min() into "min_daily_cost"
dailyCosts.max() into "max_daily_cost"
}
costData.head(3)
Out[7]:
3. 'Daily Cost' Plot with Y-Axis on the Right¶
In [8]:
val costPlot = letsPlot(costData.toMap()) +
geomBar(stat = Stat.identity, fill = "#F39C12", alpha = 0.4) {
x = "month_start"
y = "avg_daily_cost"
} +
geomPointRange(color = "#A04000", fatten = 3.0, size = 0.8) {
x = "month_start"
y = "avg_daily_cost"
ymin = "min_daily_cost"
ymax = "max_daily_cost"
} +
labs(y = "Daily Ice Cream Cost per Kid [\$]") +
theme(
axisTextY = elementText(color = "#A04000"),
axisTitleY = elementText(color = "#A04000")
) +
scaleYContinuous(position = "right")
costPlot
Out[8]:
4. Composing the 'Temperature' and 'Daily Cost' Plots into a Deck¶
In [9]:
ggdeck(listOf(
temperaturePlot + theme().legendPositionNone(),
costPlot
)) +
theme(
plotTitle = elementText(hjust = 0.5, size = 21)
) +
ggtitle("Delhi Mean Temperature & Ice Cream Cost") +
ggsize(800, 400)
Out[9]:
5. Managing Tooltips in Composites¶
Since ggdeck() overlays independent plots, multiple tooltips may trigger simultaneously. This can lead to visual clutter and overlapping labels, especially on the axes.
To maintain clarity:
- Silence background layers: Disable tooltips on secondary geometries to reduce hover noise.
- Remove redundant axis labels: Suppress axis tooltips on overlaid plots to avoid duplication.
- Use anchored tooltips: Consolidate information into a fixed position.
In [10]:
// Configure tooltips on the 'Daily Cost' plot
val costPlot2 = letsPlot(costData.toMap()) +
geomBar(stat = Stat.identity, fill = "#F39C12", alpha = 0.4,
tooltips = tooltipsNone // <-- Disable all tooltips on bars
) {
x = "month_start"
y = "avg_daily_cost"
} +
geomPointRange(color = "#A04000", fatten = 3.0, size = 0.8,
tooltips = layerTooltips() // <-- Consolidate tooltips in a single panel
.title("@month_start")
.line("Avg | ^y")
.line("Min | ^ymin")
.line("Max | ^ymax")
.format("^Y", "\${.2f}")
.format("@month_start", "%b")
.anchor("top_right") // <-- Anchor to the top-right corner
) {
x = "month_start"
y = "avg_daily_cost"
ymin = "min_daily_cost"
ymax = "max_daily_cost"
} +
theme(axisTooltipX = "blank") + // <-- Suppress redundant X-axis tooltips
labs(y = "Daily Ice Cream Cost per Kid [\$]") +
theme(
axisTextY = elementText(color = "#A04000"),
axisTitleY = elementText(color = "#A04000")
) +
scaleYContinuous(position = "right")
costPlot2 + ggsize(800, 400)
Out[10]:
6. Assembling the Updated 'Daily Cost' Plot into a Final Deck¶
In [11]:
ggdeck(listOf(
temperaturePlot + theme().legendPositionNone(),
costPlot2
)) +
theme(
plotTitle = elementText(hjust = 0.5, size = 21)
) +
ggtitle("Delhi Mean Temperature & Ice Cream Cost") +
ggsize(800, 400)
Out[11]: