估计阅读时长: 9 分钟

https://github.com/rsharp-lang/ggplot

之前在阅读一篇单细胞组学数据分析的文献,觉得在文献之中有一些三维散点图用于展示降维聚类结果的效果非常的好看。于是自己在R#语言之中的ggplot程序包的2D绘图的功能基础之上,进行了三维图形数据可视化功能的开发。

  • (A) t-SNE map projecting myeloid cells from BC1-8 patients (all tissues). Cells are colored by Biscuit cluster and cell types are labeled based on bulk RNA-seq correlation-based annotations.
  • (B–E) Projection of myeloid cells on macrophage activation, pDC, and monocyte activation diffusion components, colored by cluster (B), tissue (C), cell type (D), and expression of example lineage-demarcating genes (E).
  • (F) Violin plots showing the density of cells along macrophage activation component and organized by overall density (left panel), tissue type (middle panel), and cluster (right panel). See Figure S7 for other components.
  • (G) Scatterplot of normalized mean expression of M1 and M2 signatures per cell (dot); cells assigned to TAM clusters have been highlighted by cluster.
  • (H) Scatterplot of mean expression of MARCO and CD276 in myeloid clusters; each dot represents a cluster; TAM clusters are marked in red, indicating high expression of both markers in macrophage clusters.
  • (I) Distribution of covariance between MARCO and CD276 across all myeloid clusters. TAM clusters are marked in red and present substantial outliers. See Figure S7F for similar computation on the raw, un-normalized data, verifying the result.
  • (J) Heatmaps showing covariance patterns of select macrophage marker genes in 3 TAM clusters.

Azizi E, Carr AJ, Plitas G, Cornish AE, Konopacki C, Prabhakaran S, Nainys J, Wu K, Kiseliovas V, Setty M, Choi K, Fromme RM, Dao P, McKenney PT, Wasti RC, Kadaveru K, Mazutis L, Rudensky AY, Pe'er D. Single-Cell Map of Diverse Immune Phenotypes in the Breast Tumor Microenvironment. Cell. 2018 Aug 23;174(5):1293-1308.e36. doi: 10.1016/j.cell.2018.05.060. Epub 2018 Jun 28. PMID: 29961579; PMCID: PMC6348010.

背景知识回顾

这篇文章里面所提到的一些基础知识,大家可以阅读之前的博客文章进行学习:

在进行三维散点图的可视化代码以及实现原理讲解之前,我们首先需要获取得到进行DEMO用的数据源进行测试。在这里我们使用之前进行UMAP降维方法所演示的MNIST数据集的三维降维结果来进行DEMO展示。在这里我们回顾一下进行三维的UMAP降维的R#脚本代码:

imports ["dataset", "umap"] from "MLkit";

options(strict = FALSE);

filename = "MNIST-LabelledVectorArray-60000x100.msgpack";
MNIST_LabelledVectorArray = read.mnist.labelledvector(filename, takes = 5000);
tags = rownames(MNIST_LabelledVectorArray);

rownames(MNIST_LabelledVectorArray) = `X${1:nrow(MNIST_LabelledVectorArray)}`;

manifold = umap(MNIST_LabelledVectorArray,
    dimension         = 3,
    numberOfNeighbors = 15,
    localConnectivity = 1,
    KnnIter           = 64,
    bandwidth         = 1,
    debug             = TRUE,
    KDsearch          = FALSE
);

manifold$umap
|> as.data.frame(dimension = ["X", "Y", "Z"])
|> head()
|> print()
;

#         X         Y         Z         class
# <mode>  <double>  <double>  <double>  <string>
# 5        3.9      -0.968    -2.06     "class_5"
# 0        5.42      2.18      0.8      "class_0"
# 4       -2.84     -1.44     -2.32     "class_4"
# 1       -2.83     -4.38      4.38     "class_1"
# 9       -1.86      0.624    -2.36     "class_9"
# 2        2.72     -4.71      0.907    "class_2"

通过ggplot进行三维散点图的绘制

在之前使用ggplot进行散点图绘制的例子之中我们可以了解到:对散点图进行数据源映射的建立,我们只需要通过aes映射函数,将[x,y]二维坐标轴信息从数据框数据源之中进行映射即可进行二维数据可视化分析。那在这里进行三维数据可视化呢,同样也是非常简单的通过原来的aes函数进行数据映射即可。在这里我们只需要在原来的二维数据映射的基础上,添加一个对z轴的数据映射既可以建立一个三维数据渲染框架了:

# create ggplot layers and tweaks via ggplot style options
ggplot(data, aes(x = "X", y = "Y", z = "Z"), padding = "padding:50px 50px 50px 50px;");

Protected Overrides Sub PlotInternal(ByRef g As IGraphics, canvas As GraphicsRegion)
    Dim baseData As ggplotData = base.reader.getMapData(data, environment)

    Call g.Clear(theme.background.TranslateColor)

    If base.reader.isPlain2D Then
        Call plot2D(baseData, g, canvas)
    Else
        Call plot3D(baseData, g, canvas)
    End If
End Sub

ggplot.vb源代码文件之中,我们可以看得到,我们在添加了z轴数据映射之后,已经可以自动的将原来的二维数据渲染框架转换为三维数据渲染框架了。除了在渲染引擎框架的转换之外,在添加layer图层模型的时候,ggplot也会自动进行从原来的2维模型向三维渲染模型的转换操作,例如:

''' <summary>
''' add a ggplot plot layer
''' </summary>
''' <param name="ggplot"></param>
''' <param name="layer"></param>
''' <returns></returns>
<ROperator("+")>
Public Function add_layer(ggplot As ggplot, layer As ggplotLayer) As ggplot
    If Not ggplot.base.reader.isPlain2D Then
        If TypeOf layer Is ggplotScatter Then
            layer = New ggplotScatter3d(layer)
        End If
    End If

    ggplot.layers.Add(layer)
    layer.zindex = ggplot.layers.Count

    Return ggplot
End Function

从上面的底层实现代码中我们可以看得到,我们只需要仍然像绘制二维图表一样,进行geom_point函数的调用,甚至不需要进行任何代码修改,就可以自动的以三维渲染的模式进行散点图的绘制了。我们现在立马添加上geom_point函数添加一个散点图的图层来观察一下结果,发现效果还挺好的:

ggplot(data, aes(x = "X", y = "Y", z = "Z"), padding = "padding:250px 500px 100px 100px;")

   # use scatter points for visual our data
   + geom_point(aes(color = "class"), color = "paper", shape = "triangle", size = 20)
   + ggtitle("Scatter UMAP 3D")
   # use the default white theme from ggplot
   + theme_default()

   # use a 3d camera to rotate the charting plot 
   # and adjust view distance
   + view_camera(angle = [31.5,65,125], fov = 100000)
   ;

在上面所展示的DEMO代码之中,与原先的二维数据可视化渲染代码相比较,基本没有大改动。除了有两个地方会与之前的二维数据可视化代码有一些不一样之外:

  • aes数据源映射函数:与二维数据源相比,在原来的基础上增加了一个对z轴的数据映射选项
  • view_camera函数可以用于创建一个三维摄像机对象,用来调整我们对数据的观察结果,例如进行三维旋转视角的调整,视距的调整以及光源的调整等三维渲染相关的操作。

三维渲染

对于从原来的二维渲染转换为目前所需要的三维渲染,渲染方式存在着非常大的区别。在原来的二维数据渲染代码之中,最主要的工作代码就是一个二维缩放器对原始数据点进行二维点的计算,之后在二位点的缩放计算结果之上进行相应的二维图形绘制即可。但是对于三维数据渲染而言,因为多了一个Z轴数据维度,再加上我们需要对三维模型进行旋转操作,以及将三维数据投影回显示使用的二维平面上的原因。原来的基于二维缩放器进行数据可视化的渲染引擎模式已经完全无法适用了。

在经过了之前的旧代码的升级大改之后,我在ggplot二维渲染引擎框架的基础上构建出了三维数据的渲染框架(当然,图形的最终显示输出任然是基于二维渲染框架的底层代码来完成的)。在新的三维数据渲染引擎框架之中,ggplot程序包目前采用的是首先生成数据的三维数据点模型,然后基于我们之前所提到的三维图形引擎进行数据的三维旋转加上向二维的投影转换这样的一套流程进行三维模型的渲染。

Call populateModels(g, baseData, x, y, z, legends) _
    .IteratesALL _
    .RenderAs3DChart(
        canvas:=g,
        camera:=camera,
        region:=canvas,
        theme:=theme
    )

对于在ggplot之中所有的三维图层模型之中,我们都实现了一个名字叫做Ilayer3d的接口方法用于产生三维数据模型。这样子我们就可以通过ggplot之中的统一的渲染框架统一的进行三维数据模型的生成,然后将所有数据通过Linq管道框架送入到底层的三维渲染引擎之中进行三维渲染操作。

Dim models As Element3D() = elements.ToArray

For Each element As Element3D In models
    Call element.Transform(camera)
Next

' ......
Dim scaleX = d3js.scale.linear.domain(polygon.Select(Function(a) a.X)).range(New Double() {plotRect.Left, plotRect.Right})
Dim scaleY = d3js.scale.linear.domain(polygon.Select(Function(a) a.Y)).range(New Double() {plotRect.Top, plotRect.Bottom})
Dim orders = PainterAlgorithm _
    .OrderProvider(models, Function(e) e.Location.Z) _
    .ToArray

For i As Integer = 0 To models.Length - 1
    Dim index As Integer = orders(i)
    Dim model As Element3D = models(index)

    Call model.Draw(canvas, region, scaleX, scaleY)
Next

如果我们继续往下查找model.Draw函数的代码,我们可以发现底层都是对原先的二维渲染框架的代码的函数调用。对于底层的三维渲染引擎的所有工作代码,大家可以阅读RenderEngine.vb这个源代码文件。

设置主题

通过上面的三维散点图的渲染DEMO,可以看得到结果还是挺不错的。如果你有点嫌弃ggplot程序包之中默认的白色主题样式太平淡了,看起来没有那么酷?没有关系,与其他的数据可视化图形库一样,在R#语言的ggplot程序包之中也内置了几个定义好的绘图样式可以直接进行替换使用。例如我们在这里直接将theme_default默认主题函数替换为看起来更加酷炫一些的黑色主题,只需要更换主题函数为theme_black即可。

#' The black theme for ggplot
#' 
const theme_black as function() {
    ggplot2::theme(
        text = element_text(color = "white"),
        plot.background = "black",
        panel.background = "black",
        panel.grid = "stroke: white; stroke-width: 3px; stroke-dash: dash;",
        axis.line = "stroke: white; stroke-width: 6px; stroke-dash: solid;",
        legend.text = element_text(color = "white", size = 12)
    );
}

# create ggplot layers and tweaks via ggplot style options
ggplot(data, aes(x = "X", y = "Y", z = "Z"), padding = "padding:250px 500px 100px 100px;")
+ geom_point(aes(color = "class"), color = "paper", shape = "triangle", size = 20)
+ view_camera(angle = [31.5,65,125], fov = 100000)
+ ggtitle("Scatter UMAP 3D")
+ theme_black()
;

从上面的DEMO代码可以看得到,更换样式主题就是这么简单

Attachments

No responses yet

Leave a Reply

Your email address will not be published.