You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为非基于数据映射的Kotlin Lets-Plot图表添加图例

问题描述

我正尝试为以下图表添加图例,该图表包含两个代表概率质量函数的系列。我希望图例中红色系列标注“observed”,黑色系列标注“theoretical”。以下代码导入Kotlin Lets-Plot相关依赖后即可运行。我能找到的示例均基于数据系列创建图例,尚未找到相关实现思路。

val dataMap = mapOf(
    "estimated" to listOf(0.2, 0.3, 0.4, 0.1),
    "observed" to listOf(1, 2, 3, 4),
    "probability" to listOf(0.19, 0.31, 0.42, 0.08),
    "values" to listOf(1, 2, 3, 4),
)
val pd = positionNudge(.1)
var p = ggplot(dataMap) + theme().legendPositionRight() +
        geomPoint(color = "red", position = pd) {
            x = "observed"
            y = "estimated"
        } +
        geomPoint(color = "black") {
            x = "values"
            y = "probability"
        }
for (i in 1..4) {
    p = p + geomSegment(yend = 0, color = "red", position = pd) {
        x = "observed"
        y = "estimated"
        xend = "observed"
    }
}
for (i in 1..4) {
    p = p + geomSegment(yend = 0, color = "black") {
        x = "values"
        y = "probability"
        xend = "values"
    }
}
p = p + labs(title = "some title", x = "xLabel", y = "yLabel") +
        ggsize(500, 350)

val spec = p.toSpec()
// Export: use PlotHtmlExport utility to generate dynamic HTML (optionally in iframe).
val html = PlotHtmlExport.buildHtmlFromRawSpecs(
    spec, iFrame = true,
    scriptUrl = PlotHtmlHelper.scriptUrl(VersionChecker.letsPlotJsVersion)
)
val tmpDir = File("someDirectory")
if (!tmpDir.exists()) {
    tmpDir.mkdir()
}
val file = File.createTempFile("someFileName", ".html", tmpDir)
FileWriter(file).use {
    it.write(html)
}
val desktop = Desktop.getDesktop()
desktop.browse(file.toURI())
解决方案

要在Lets-Plot中生成图例,核心是利用美学映射(aesthetic mapping),而非直接给元素指定固定颜色。手动设置color = "red"这类参数不会触发图例生成,只有将颜色与数据中的分类变量绑定,才能让图表自动创建对应图例。

实现步骤

  1. 重构数据格式:将原宽格式数据转换为长格式,合并两个系列的x、y值,新增分类列标记系列类型("observed"和"theoretical")。
  2. 绑定颜色映射:在geomPoint和geomSegment中通过aes指定color映射到分类列,再用scaleColorManual自定义颜色与图例标签。
  3. 统一位置调整:给对应系列的图层设置positionNudge,避免元素重叠。

修改后的完整代码

import java.awt.Desktop
import java.io.File
import java.io.FileWriter
import org.jetbrains.letsPlot.geom.geomPoint
import org.jetbrains.letsPlot.geom.geomSegment
import org.jetbrains.letsPlot.ggplot
import org.jetbrains.letsPlot.labs
import org.jetbrains.letsPlot.position.positionNudge
import org.jetbrains.letsPlot.scale.scaleColorManual
import org.jetbrains.letsPlot.theme
import org.jetbrains.letsPlot.export.PlotHtmlExport
import org.jetbrains.letsPlot.export.PlotHtmlHelper
import org.jetbrains.letsPlot.util.VersionChecker

fun main() {
    // 重构为长格式数据:合并两个系列,用type列区分系列类型
    val data = listOf(
        // observed系列数据
        mapOf("x" to 1, "y" to 0.2, "type" to "observed"),
        mapOf("x" to 2, "y" to 0.3, "type" to "observed"),
        mapOf("x" to 3, "y" to 0.4, "type" to "observed"),
        mapOf("x" to 4, "y" to 0.1, "type" to "observed"),
        // theoretical系列数据
        mapOf("x" to 1, "y" to 0.19, "type" to "theoretical"),
        mapOf("x" to 2, "y" to 0.31, "type" to "theoretical"),
        mapOf("x" to 3, "y" to 0.42, "type" to "theoretical"),
        mapOf("x" to 4, "y" to 0.08, "type" to "theoretical")
    )

    val pd = positionNudge(x = 0.1)
    val p = ggplot(data) +
            theme().legendPositionRight() +
            // 映射color到type字段,给observed系列添加位置偏移
            geomPoint(position = pd) {
                x = "x"
                y = "y"
                color = "type"
            } +
            // 线段同样映射color到type字段,observed系列偏移
            geomSegment(yend = 0, position = pd) {
                x = "x"
                y = "y"
                xend = "x"
                color = "type"
            } +
            // 自定义系列颜色与图例标签
            scaleColorManual(
                values = mapOf("observed" to "red", "theoretical" to "black"),
                labels = mapOf("observed" to "observed", "theoretical" to "theoretical")
            ) +
            labs(title = "some title", x = "xLabel", y = "yLabel") +
            ggsize(500, 350)

    val spec = p.toSpec()
    val html = PlotHtmlExport.buildHtmlFromRawSpecs(
        spec, iFrame = true,
        scriptUrl = PlotHtmlHelper.scriptUrl(VersionChecker.letsPlotJsVersion)
    )
    val tmpDir = File("someDirectory")
    if (!tmpDir.exists()) {
        tmpDir.mkdir()
    }
    val file = File.createTempFile("someFileName", ".html", tmpDir)
    FileWriter(file).use {
        it.write(html)
    }
    val desktop = Desktop.getDesktop()
    desktop.browse(file.toURI())
}

关键说明

  • 数据重构:长格式数据让两个系列共享x、y字段,通过type列区分,是实现统一映射的基础。
  • 美学映射:color = "type"告诉Lets-Plot根据type列的值区分颜色,自动生成对应图例。
  • 自定义配置:scaleColorManual可精准指定每个系列的颜色和图例显示标签,完全匹配需求。
  • 位置偏移:仅给observed系列的图层添加positionNudge,避免两个系列的元素重叠。

内容的提问来源于stack exchange,提问作者Manuel Rossetti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 07:40:37