如何在自组织映射(SOM)的每个单元内绘制折线图?
在六边形SOM单元内嵌入谱线图的实现方案
我明白你现在的困境——自己画了SOM的六边形网格,但不知道怎么把每个神经元的谱线图塞进去。其实核心是在基础绘图的每个六边形区域内创建子绘图窗口,下面给你一步步的提示和代码修改方案:
核心思路
每个六边形都有自己的坐标范围,我们需要在绘制完六边形后,定位到这个范围内部,然后绘制对应的谱线数据。这里推荐用grid包的视口(viewport)方案,它比纯基础绘图更灵活,不容易搞乱绘图参数。
步骤1:提前计算全局谱线范围(关键)
为了让所有神经元的谱线图比例一致,方便对比,先计算所有编码数据的y轴范围:
y_range <- range(som_obj$codes)
步骤2:修改循环,在每个六边形内添加谱线图
我们可以利用grid和gridBase包实现基础绘图与网格视口的结合,精准控制子图位置。修改你的plotCluster函数如下:
library(grid) library(gridBase) plotCluster <- function(som_obj, cutree.obj , col_palette){ if (som_obj$grid$topo != "hexagonal"){ stop("function assumes hexagonal SOM") } # 提前计算谱线的全局y范围,保证所有子图比例一致 y_range <- range(som_obj$codes) n_vars <- ncol(som_obj$codes) Hexagon <- function (x, y, unitcell = 1, col = "grey", border=NA) { polygon(c(x, x, x + unitcell/2, x + unitcell, x + unitcell, x + unitcell/2), c(y + unitcell * 0.125, y + unitcell * 0.875, y + unitcell * 1.125, y + unitcell * 0.875, y + unitcell * 0.125, y - unitcell * 0.125), col = col, border=border) } # 初始化基础绘图 plot(0, 0, type = "n", axes = FALSE, xlim=c(0, som_obj$grid$xdim), ylim=c(0, som_obj$grid$ydim), xlab="", ylab= "", asp=1, main= "Clusters with Spectra") if(!is.null(col_palette)){ ColorCode = col_palette[cutree.obj] } else{ ColorCode <- as.factor(cutree.obj) } offset <- 0.5 #offset for the hexagons when moving up a row ind <- 1 unitcell <- 1 for (row in 1:som_obj$grid$ydim) { for (column in 0:(som_obj$grid$xdim - 1)) { # 绘制六边形 Hexagon(column + offset, row - 1, col = ColorCode[ind]) # ---------- 新增:绘制内部谱线图 ---------- # 计算当前六边形的中心坐标与子图尺寸(留边距避免贴边框) x_center <- column + offset + unitcell/2 y_center <- (row - 1) + unitcell*0.5 x_width <- unitcell * 0.8 y_height <- unitcell * 0.9 # 创建网格视口,定位到当前六边形中心 pushViewport(viewport(x = x_center, y = y_center, width = x_width, height = y_height, just = c("center", "center"))) # 切换到基础绘图的视口环境 vps <- baseViewports() pushViewport(vps$inner, vps$figure, vps$plot) # 绘制谱线:隐藏坐标轴、边框,用全局y范围统一比例 par(mar = c(0, 0, 0, 0), bg = NA) plot(1:n_vars, som_obj$codes[ind, ], type = "l", axes = FALSE, xlab = "", ylab = "", xlim = c(1, n_vars), ylim = y_range, col = "black", lwd = 1) # 弹出嵌套的视口,回到全局绘图环境 popViewport(3) popViewport() # ---------------------------------------- ind <- ind +1 } offset <- ifelse(offset == 0.5, 0, 0.5) # 修正原offset判断逻辑,保证六边形交错排列 } }
关键细节解释
- 全局y范围:
y_range确保所有谱线图的纵轴刻度一致,不会因为单个神经元的数据导致比例失调,方便对比不同神经元的谱线趋势。 - 视口定位:
viewport的坐标参数对应六边形中心,设置子图尺寸时留边距,避免谱线贴到六边形边框影响美观。 - 参数重置:
par(mar = c(0,0,0,0))去掉子图的边距,bg = NA保证子图背景透明,不覆盖六边形的聚类颜色。 - offset修正:原代码的
offset <- ifelse(offset, 0, 0.5)逻辑有问题,改成判断是否等于0.5,才能正确实现六边形的交错排列。
替代方案:纯基础绘图实现
如果不想依赖grid包,也可以用par(new=TRUE)在当前画布上叠加子图,核心是计算六边形边界后设置新的绘图窗口:
# 在绘制完Hexagon后添加: x_min <- column + offset x_max <- column + offset + unitcell y_min <- (row -1) - unitcell*0.125 y_max <- (row -1) + unitcell*1.125 old_par <- par(no.readonly = TRUE) on.exit(par(old_par)) par(new = TRUE) plot.window(xlim = c(x_min, x_max), ylim = c(y_min, y_max), asp = 1) # 把谱线的x轴映射到六边形的x范围 x_vals <- seq(x_min, x_max, length.out = n_vars) lines(x_vals, som_obj$codes[ind, ], col = "black", lwd = 1) par(new = FALSE)
这种方法代码更简单,但容易因为绘图参数叠加出现问题(比如坐标轴残留),所以更推荐grid的视口方案。
内容的提问来源于stack exchange,提问作者Olorin.G
相关产品推荐
相关产品推荐

