Reproducing Manuscript Figures

Introduction

This vignette demonstrates how to reproduce the figures presented in our manuscript ‘CEV: A Tool for Standardized Cancer Evolution Visualization’ using the CEV package. We will walk through the generation of each figure using example datasets provided with the package. The code snippets below show how to create customized phylogenetic trees, CCF heatmaps, and other visualizations that highlight different aspects of tumor evolution. Users can easily adapt these examples to their own datasets by following the same workflow and customization options.

Figure 1: Multi-sample Analysis

Mutation-to-subclone assignment data.
ID SNV.id CCF clone.id chr pos
LN1 10_102747966_G_A 1.08 MRCA 10 102747966
LN2 10_102747966_G_A 1.25 MRCA 10 102747966
LN3 10_102747966_G_A 1.35 MRCA 10 102747966
R1 10_102747966_G_A 1.16 MRCA 10 102747966
R2 10_102747966_G_A 0.87 MRCA 10 102747966
R3 10_102747966_G_A 1.19 MRCA 10 102747966
Example phylogeny data for a single tumour sample.
parent label length.1 length.2 spatial CP node.id node.col
MRCA NA MRCA 145 2 Shared 1.00 MRCA #a90000
4 MRCA 4 49 NA Metastasis 0.96 4 #f48004
13 4 13 7 NA Metastasis 0.77 13 #05f8f2
24 13 24 6 NA Metastasis 0.23 24 #f43ded

1B. CCF Heatmap

plt <- create.cluster.heatmap(
    filename = 'figures/1B_CCF-heatmap.png',
    DF = snv,
    ccf.limits = c(0, 1),
    clone.colours = clone.col,
    ylab.label = 'Sample',
    ylab.cex = 1.2,
    yaxis.cex = 1,
    y.spacing = -0.2,
    height = 7,
    width = 11
    );

1C. CCF Summary Heatmap

# Calculate median CCF per sample
median.ccf <- aggregate(CCF ~ ID + clone.id, data = snv, FUN = median);
names(median.ccf)[names(median.ccf) == 'CCF'] <- 'median.ccf.per.sample';

# Calculate total unique SNV.id per sample
total.nsnv <- aggregate(
    SNV.id ~ ID + clone.id,
    data = snv,
    FUN = function(x) length(unique(x))
    );
names(total.nsnv)[names(total.nsnv) == 'SNV.id'] <- 'total.nsnv';

# Merge the results back to the original data frame
snv <- merge(snv, median.ccf, by = c('ID', 'clone.id'));
snv <- merge(snv, total.nsnv, by = c('ID', 'clone.id'));

create.ccf.summary.heatmap(
    filename = 'figures/1C_CCF-summary-heatmap.png',
    DF = snv,
    ccf.limits = c(0, 1),
    clone.colours = clone.col,
    clone.order = levels(snv$clone.id),
    sample.order = levels(snv$ID),
    y.spacing = c(-0.5, -2.5),
    xlab.axis.padding = c(0, 0, -2),
    plot.objects.heights = c(0.3, 1, 0.25),
    height = 7,
    width = 11
    );

1D. Clone Genome Distribution Plot

selected.clone <- c('1', subset(multi.phylogeny, spatial %in% c('Shared', 'Metastasis'))$label)
sub.dt <- droplevels(snv[snv$clone.id %in% selected.clone, ]);

create.clone.genome.distribution.plot(
    filename = 'figures/1D_clone-genome-distribution.png',
    snv.df = sub.dt,
    clone.order = levels(sub.dt$clone.id),
    clone.colours = clone.col,
    legend.y = 0.47,
    legend.x = 0.1,
    y.spacing = -0.5,
    alpha = 0.6,
    ylab.axis.padding = c(-1),
    width = 26,
    );
## [1] "Plotting clone distribution across the genome for sample: all"
## list()

1G. Multi-sample Phylogenetic Tree

create.phylogenetic.tree(
    filename = 'figures/1G_multi-sample-phylogeny.png',
    phy.dt,
    add.normal = TRUE,
    scale1 = 2,
    horizontal.padding = 1,
    height = 7,
    width = 7
    );
## SRCGrob[GRID.SRCGrob.317]

1G. Single-sample Phylogenetic Tree

create.phylogenetic.tree(
    filename = 'figures/1H_single-sample-phylogeny.png',
    single.dt,
    add.normal = TRUE,
    polygon.scale = 2.5,
    height = 5,
    width = 3
    );

## SRCGrob[GRID.SRCGrob.318]

Figure 2: Phylogenetic tree features

2A. Sample-specific subclones

sample.list <- c('LN1', 'LN2', 'R2', 'R7');
cp.dt <- aggregate(CCF ~ clone.id + ID, data = snv, FUN = function(x) median(x, na.rm = TRUE))
names(cp.dt) <- c('label', 'ID', 'node.size');

for (s in sample.list) {
    temp.dt <- merge(
        x = phy.dt,
        y = cp.dt[cp.dt$ID == s, ],
        by = 'label',
        all.x = TRUE
        );
    temp.dt$node.id <- temp.dt$label;
    idx <- !temp.dt$node.size > 0;
    temp.dt$edge.type.1[idx] <- 'dotted';
    temp.dt$edge.width.1[idx] <- 1;
    temp.dt$node.size[idx] <- 0.8;
    temp.dt$node.col[idx] <- 'white';
    temp.dt$node.label.col[idx] <- 'grey';
    temp.dt$border.type[idx] <- 'dotted';
    temp.dt$label[temp.dt$node.size <= 0.5] <- '';

    create.phylogenetic.tree(
        filename = paste0('figures/2A_', s, '-phylogeny.png'),
        temp.dt,
        add.normal = TRUE,
        scale1 = 2,
        height = 7,
        width = 7
        );
    }
| | | |

2B. Annotated single-sample phylogenetic tree

text.data <- data.frame(
    node = c('MRCA', 'MRCA', 4, 13, 'MRCA'),
    name = c('chr1q gain', 'chr12q loss', 'chr2p gain', 'MYC SNV', 'TP53 SNV'),
    col = c('blue', 'red', 'blue', 'black', 'black'),
    fontface = c(rep('plain', 3), 'bold', 'bold')
    );

create.phylogenetic.tree(
    filename = 'figures/2B_annot-single-sample-phylogeny.png',
    single.dt,
    add.normal = TRUE,
    polygon.colour.scheme = c(NA, single.dt$node.col),
    node.text = text.data,
    node.text.line.dist = 0.6,
    polygon.scale = 2.5,
    height = 5,
    width = 3
    );

## SRCGrob[GRID.SRCGrob.328]

2C. Mutation timeline tree

create.phylogenetic.tree(
    filename = 'figures/2C_mutation-timeline-phylogeny.png',
    single.dt,
    scale1 = 3,
    yaxis1.label = 'Number of SNVs',
    horizontal.padding = -0.3,
    node.text.line.dist = 0.2,
    node.text.cex = 1.2,
    add.normal = TRUE,
    node.text = text.data,
    polygon.scale = 2.5,
    height = 10,
    width = 6
    );

2D. Radial mode with 2 edges

Tree dataframe for customizing 2 edges.
parent label length.1 length.2 node.col edge.col.1 edge.col.2 edge.type.2
MRCA NA MRCA 145 2 #a90000 steelblue black dotted
2 MRCA 2 28 NA #f42404 steelblue black dotted
11 2 11 24 NA #16a149 steelblue black dotted
4 MRCA 4 49 NA #f48004 steelblue black dotted
5 3 5 11 NA #f2a123 steelblue black dotted
6 5 6 6 1 #d7c454 steelblue black dotted
7 3 7 10 NA #90de3a steelblue black dotted
8 3 8 5 NA #5ecf1c steelblue black dotted
9 7 9 5 NA #37a400 steelblue black dotted
10 3 10 6 2 #158800 steelblue black dotted
3 2 3 22 2 #df6b0b steelblue black dotted
12 4 12 9 NA #1ecfa9 steelblue black dotted
13 4 13 7 NA #05f8f2 steelblue black dotted
14 8 14 7 NA #00daff steelblue black dotted
15 4 15 5 NA #00abff steelblue black dotted
16 11 16 21 1 #007dff steelblue black dotted
17 3 17 36 NA #004aff steelblue black dotted
18 7 18 17 1 #1922ff steelblue black dotted
19 16 19 7 NA #5703ff steelblue black dotted
20 10 20 13 NA #8600ff steelblue black dotted
21 6 21 8 NA #ab19ff steelblue black dotted
22 9 22 17 NA #bd4cff steelblue black dotted
23 17 23 8 NA #de56fc steelblue black dotted
24 13 24 6 NA #f43ded steelblue black dotted
25 18 25 14 NA #ff00d1 steelblue black dotted
create.phylogenetic.tree(
    filename = 'figures/2D_radial-phylogeny.png',
    phy.dt,
    add.normal = TRUE,
    scale1 = 1.5,
    scale2 = 1.5,
    yaxis1.label = 'Number of SNVs',
    yaxis2.label = 'Number of CNAs',
    scale.bar = TRUE,
    scale.bar.coords = c(0.25, 0.85),
    ylab.cex = 1.4,
    yaxis.cex = 1.3,
    scale.size.1 = 20,
    height = 10,
    width = 8
    );

2E. Dendrogram mode with 2 edges

dend.dt <- phy.dt;
dend.dt$mode <- 'dendrogram';
dend.dt$spread <- 1.3;

create.phylogenetic.tree(
    filename = 'figures/2E_dendrogram-phylogeny.png',
    dend.dt,
    add.normal = TRUE,
    scale1 = 1.5,
    scale2 = 1.5,
    yaxis1.label = 'Number of SNVs',
    yaxis2.label = 'Number of CNAs',
    scale.bar = TRUE,
    scale.bar.coords = c(0.2, 0.85),
    ylab.cex = 1.4,
    yaxis.cex = 1.3,
    scale.size.1 = 20,
    height = 11,
    width = 8
    );
## SRCGrob[GRID.SRCGrob.360]

2F. Mixed mode with 2 edges

mixed.dt <- update.descendant.property(
    phy.dt,
    parent.id = c('3', '4'),
    property = 'mode',
    value = 'dendrogram'
    );

mixed.dt <- update.descendant.property(
    mixed.dt,
    parent.id = '7',
    property = 'mode',
    value = 'radial'
    );
mixed.dt$spread[mixed.dt$label == '4'] <- 1.7;

create.phylogenetic.tree(
    filename = 'figures/2F_mixed-phylogeny.png',
    mixed.dt,
    add.normal = TRUE,
    scale1 = 1.5,
    scale2 = 1.5,
    yaxis1.label = 'Number of SNVs',
    yaxis2.label = 'Number of CNAs',
    scale.bar = TRUE,
    scale.bar.coords = c(0.25, 0.85),
    ylab.cex = 1.4,
    yaxis.cex = 1.3,
    scale.size.1 = 20,
    height = 11,
    width = 8
    );
## SRCGrob[GRID.SRCGrob.373]

2G. 500 node phylogentic tree

phy.500 <- phylogeny.500;
phy.500$node.id <- phy.500$label;
phy.500$length.1 <- 1;
phy.500$length.2 <- 0;
phy.500$mode <- 'dendrogram';

node.list <- setNames(
    c('navy', 'steelblue', 'orchid', 'maroon', 'seagreen', 'tomato'),
    c('1', '7', '26', '53', '125', '204')
    );

for (i in seq_along(node.list)) {
    idx <- which(phy.500$label == names(node.list)[i]);
    phy.500[idx, 'node.col'] <- node.list[i];
    phy.500[idx, 'connector.col'] <- node.list[i];

    phy.500 <- update.descendant.property(
        phy.500,
        parent.id = names(node.list)[i],
        property = 'edge.col.1',
        value = node.list[i]
        );
    }

phy.500[which(phy.500$edge.col.1 == 'tomato'), 'edge.type.1'] <- 'dotted';
phy.500[which(phy.500$edge.col.1 == 'seagreen'), 'length.2'] <- 1;
phy.500[which(phy.500$edge.col.1 == 'seagreen'), 'edge.col.2'] <- 'palegreen3';
phy.500[!(phy.500$label %in% names(node.list)), 'draw.node'] <- FALSE;
phy.500$label <- '';

create.phylogenetic.tree(
    filename = 'figures/2G_phylogeny-500.png',
    phy.500,
    scale2 = 0.1,
    horizontal.padding = 10,
    add.normal = TRUE,
    width = 23,
    height = 9
    );

## SRCGrob[GRID.SRCGrob.374]