Dual-Axis Effect with ggdeck()¶
The ggdeck() function allows for the overlaying of multiple plots into a single view.
By combining a primary plot with one or more blank plots—each configured with its own scale—you can achieve a dual-axis (or multi-axis) effect.
In [1]:
%useLatestDescriptors
%use dataframe
%use lets-plot
In [2]:
LetsPlot.getInfo()
Out[2]:
In [3]:
var data = DataFrame.readCSV("https://raw.githubusercontent.com/JetBrains/lets-plot-docs/refs/heads/master/data/delhi_climate.csv")
data.head(3)
Out[3]:
In [4]:
val basePlot = letsPlot(data.toMap()) +
geomLine {
x = "date"
y = "meantemp"
color = "meantemp"
} +
scaleColorGradient2(
low = "#0571b0",
mid = "#f7f7f7",
high = "#ca0020",
midpoint = 30
) +
labs(y = "°C", color = "°C")
basePlot
Out[4]:
1. Secondary Axis - Fahrenheits¶
In [5]:
// Add the Fahrenheit column via a linear transformation
data = data.add("meantemp_f") {
(meantemp * 9 / 5) + 32
}
data.head(3)
Out[5]:
In [6]:
// Helper to synchronize the Y-axis aesthetic (line, ticks, and text)
fun axisYColor(color: String): theme {
return theme(
axisLineY = elementLine(color = color),
axisTicksY = elementLine(color = color),
axisTextY = elementText(color = color),
axisTitleY = elementText(color = color)
)
}
In [7]:
// Create an empty plot with the y-axis on the right
val fahrenheitPlot = letsPlot(data.toMap()) +
geomBlank {
// Scale mapping without rendering data
x = "date"
y = "meantemp_f"
} +
labs(y = "°F") +
axisYColor("blue") +
scaleYContinuous(position = "right")
fahrenheitPlot + ggsize(400, 200)
Out[7]:
In [8]:
// Combine the primary and secondary plots into a single layout
ggdeck(listOf(basePlot, fahrenheitPlot)) +
ggsize(800, 400) +
ggtitle("Delhi Mean Temperature") +
themeClassic() +
theme(
plotTitle = elementText(hjust = 0.5, size = 21)
)
Out[8]:
2. Show More Temperature Scales - Kelvin and Rankine¶
This section demonstrates an alternative approach by anchoring secondary scales using boundary coordinates to ensure precise axis synchronization with minimal data overhead.
In [9]:
val xMin = data.min("date")
val xMax = data.max("date")
val cMin = data.min("meantemp") as Double
val cMax = data.max("meantemp") as Double
// Perform linear transformations for Kelvin and Rankine boundaries
val fMin = (cMin * 9 / 5) + 32
val fMax = (cMax * 9 / 5) + 32
val kMin = cMin + 273.15
val kMax = cMax + 273.15
val rMin = fMin + 459.67
val rMax = fMax + 459.67
In [10]:
// Option 1: create a lightweight dictionary for scale mapping.
val boundsData = mapOf(
"x" to listOf(xMin, xMax),
"y_kelvin" to listOf(kMin, kMax)
)
val kelvinPlot = letsPlot(boundsData) +
geomBlank {
x = "x"
y = "y_kelvin"
} +
labs(y = "K") +
axisYColor("green") +
scaleYContinuous(position = "right")
kelvinPlot + ggsize(400, 200)
Out[10]:
In [11]:
// Option 2: set the scale limits.
val rankinePlot = letsPlot() +
geomBlank() +
labs(y = "°R") +
axisYColor("red") +
scaleYContinuous(position = "right", limits = Pair(rMin, rMax)) +
scaleXDateTime(limits = Pair(xMin, xMax))
rankinePlot + ggsize(400, 200)
Out[11]:
In [12]:
// Assemble the multi-axis deck.
// Use 'panelInset' to create horizontal spacing between adjacent Y-axes.
ggdeck(
listOf(
basePlot,
fahrenheitPlot,
kelvinPlot +
theme(panelInset = listOf(0.0, 3.0, 0.0, 0.0)), // <-- 3px offset for K axis
rankinePlot +
theme(panelInset = listOf(0.0, 3.0, 0.0, 0.0)) // <-- 3px offset for °R axis
)
) +
ggsize(800, 400) +
ggtitle("Delhi Mean Temperature") +
themeClassic() +
theme(
plotTitle = elementText(hjust = 0.5, size = 21),
panelGrid = elementLine() // <-- Add gridlines to the Classic theme
) +
ggtb()
Out[12]:
3. Layout Optimization — Plot Subtitles Instead of Y-Axis Titles¶
In [13]:
ggdeck(
listOf(
basePlot,
fahrenheitPlot +
labs(subtitle = "°F") + // <-- Subtitle labeling the Y-axis
theme(
axisTitleY = "blank",
plotSubtitle = elementText(hjust = 1.0, color = "blue") // <-- Subtitle right-justification and color
),
kelvinPlot +
labs(subtitle = "K") +
theme(
axisTitleY = "blank",
plotSubtitle = elementText(hjust = 1.0, color = "green"),
panelInset = listOf(0.0, 3.0, 0.0, 0.0)
),
rankinePlot +
labs(subtitle = "°R") +
theme(
axisTitleY = "blank",
plotSubtitle = elementText(hjust = 1.0, color = "red"),
panelInset = listOf(0.0, 3.0, 0.0, 0.0)
)
)
) +
ggsize(800, 400) +
ggtitle("Delhi Mean Temperature") +
themeClassic() +
theme(
plotTitle = elementText(hjust = 0.5, size = 21),
panelGrid = elementLine()
) +
ggtb()
Out[13]: