Skip to content

Commit

Permalink
Merge pull request #852 from opencb/TASK-5401
Browse files Browse the repository at this point in the history
TASK-5401 - Split breakpoint annotation for Gene and Gene Feature Overlap in sample variant browser
  • Loading branch information
jmjuanes authored Feb 8, 2024
2 parents b73c5f4 + 1240a75 commit c389fe7
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1017,36 +1017,38 @@ export default class VariantInterpreterGridFormatter {
`;
}

static geneFeatureOverlapFormatter(variant, opencgaSession) {
static rearrangementFeatureOverlapFormatter(variant, genes, opencgaSession) {
if (variant?.annotation?.consequenceTypes) {
const overlaps = [];
(variant.annotation.consequenceTypes || []).forEach(ct => {
if (Array.isArray(ct.exonOverlap) && ct.exonOverlap?.length > 0) {
ct.exonOverlap.map(exon => {
overlaps.push({
geneName: ct.geneName || "",
transcript: ct.transcript || ct.ensemblTranscriptId || "",
feature: `exon (${exon.number || "-"})`,
});
});
} else if (Array.isArray(ct.sequenceOntologyTerms) && ct.sequenceOntologyTerms?.length > 0) {
ct.sequenceOntologyTerms.forEach(term => {
if (term.name === "intron_variant") {
overlaps.push({
geneName: ct.geneName || "",
transcript: ct.transcript || ct.ensemblTranscriptId || "",
feature: "intron",
});
} else if (term.name === "5_prime_UTR_variant" || term.name === "3_prime_UTR_variant") {
(variant.annotation.consequenceTypes || [])
.filter(ct => genes.has(ct.geneName || ct.geneId || ""))
.forEach(ct => {
if (Array.isArray(ct.exonOverlap) && ct.exonOverlap?.length > 0) {
ct.exonOverlap.map(exon => {
overlaps.push({
geneName: ct.geneName || "",
geneName: ct.geneName || ct.geneId || "",
transcript: ct.transcript || ct.ensemblTranscriptId || "",
feature: `${term.name.charAt(0)}'-UTR`,
feature: `exon (${exon.number || "-"})`,
});
}
});
}
});
});
} else if (Array.isArray(ct.sequenceOntologyTerms) && ct.sequenceOntologyTerms?.length > 0) {
ct.sequenceOntologyTerms.forEach(term => {
if (term.name === "intron_variant") {
overlaps.push({
geneName: ct.geneName || ct.geneId || "",
transcript: ct.transcript || ct.ensemblTranscriptId || "",
feature: "intron",
});
} else if (term.name === "5_prime_UTR_variant" || term.name === "3_prime_UTR_variant") {
overlaps.push({
geneName: ct.geneName || ct.geneId || "",
transcript: ct.transcript || ct.ensemblTranscriptId || "",
feature: `${term.name.charAt(0)}'-UTR`,
});
}
});
}
});

if (overlaps.length > 0) {
const maxDisplayedOverlaps = 3;
Expand Down Expand Up @@ -1092,4 +1094,26 @@ export default class VariantInterpreterGridFormatter {
return "-";
}

static rearrangementGeneFormatter(variants, genesByVariant, opencgaSession) {
const separator = `<div style="background-color:currentColor;height:1px;margin-top:4px;margin-bottom:4px;opacity:0.2"></div>`;
return variants
.map((variant, index) => {
let resultHtml = "-";
const genes = Array.from(genesByVariant[variant.id] || []);

if (genes.length > 0) {
const genesLinks = genes.map(gene => {
const tooltip = VariantGridFormatter.getGeneTooltip(gene, opencgaSession?.project?.organism?.assembly);
return `
<a class="gene-tooltip" tooltip-title="Links" tooltip-text="${tooltip}">${gene}</a>
`;
});
resultHtml = genesLinks.join(" ");
}

return `<div><b>Variant ${index + 1}</b>: ${resultHtml}</div>`;
})
.join(separator);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ export default class VariantInterpreterRearrangementGrid extends LitElement {
this.review = false;
this.variantsReview = null;

// OpenCGA returns the same genes in both variants of the rearrangement
// This map is used to assign the correct genes to each variant
this.genesByVariant = {};

// Set colors
// consequenceTypesImpact;
// eslint-disable-next-line no-undef
Expand Down Expand Up @@ -204,6 +208,58 @@ export default class VariantInterpreterRearrangementGrid extends LitElement {
return pairs;
}

generateGenesMapFromVariants(variants, offset = 5000) {
this.genesByVariant = {};
const genesList = new Set();
(variants || []).forEach(variant => {
if (variant?.annotation?.consequenceTypes) {
variant.annotation.consequenceTypes.forEach(ct => {
if (ct.geneName || ct.geneId) {
genesList.add(ct.geneName || ct.geneId);
}
});
}
});

// Request gene info to cellbase
if (genesList.size > 0) {
const genesIds = Array.from(genesList);
return this.opencgaSession.cellbaseClient
.getGeneClient(genesIds.join(","), "info", {
"include": "id,name,chromosome,start,end",
})
.then(response => {
// 1. Map each gene with it's correct position
const genes = new Map();
genesIds.forEach((geneId, index) => {
if (response?.responses?.[index]?.results?.[0]) {
genes.set(geneId, response.responses[index].results[0]);
}
});

// 2. Assign genes to each variant
variants.forEach(variant => {
const id = variant.id;
this.genesByVariant[id] = new Set();
(variant?.annotation?.consequenceTypes || []).forEach(ct => {
const gene = genes.get(ct.geneName || ct.geneId);

// Check if this gene exists and overlaps this variant
if (gene) {
const start = gene.start - offset;
const end = gene.end + offset;
const variantStart = Math.min(variant.start, variant.end);
const variantEnd = Math.max(variant.start, variant.end);
if (variant.chromosome === gene.chromosome && variantStart <= end && start <= variantEnd) {
this.genesByVariant[id].add(ct.geneName || ct.geneId);
}
}
});
});
});
}
}

renderVariants() {
if (this.clinicalVariants && this.clinicalVariants.length > 0) {
this.renderLocalVariants();
Expand Down Expand Up @@ -250,6 +306,8 @@ export default class VariantInterpreterRearrangementGrid extends LitElement {
variantGrid: this,

ajax: params => {
let rearrangementResponse = null;

// Make a deep clone object to manipulate the query sent to OpenCGA
const internalQuery = JSON.parse(JSON.stringify(this.query));

Expand All @@ -272,15 +330,24 @@ export default class VariantInterpreterRearrangementGrid extends LitElement {
this.opencgaSession.opencgaClient.clinical().queryVariant(filters)
.then(res => {
this.isApproximateCount = res.responses[0].attributes?.approximateCount ?? false;
rearrangementResponse = res;

// Generate map of genes to variants
return this.generateGenesMapFromVariants(res.responses[0].results);
})
.then(() => {
// pairs will have the following format: [[v1, v2], [v3, v4], [v5, v6]];
const pairs = this.generateRowsFromVariants(res.responses[0].results);
const results = rearrangementResponse.responses[0].results;
const pairs = this.generateRowsFromVariants(results);
// It's important to overwrite results array
res.responses[0].results = pairs;
rearrangementResponse.responses[0].results = pairs;

params.success(res);
params.success(rearrangementResponse);
})
.catch(error => {
console.error(error);
params.error(error);
})
.catch(e => params.error(e))
.finally(() => {
LitUtils.dispatchCustomEvent(this, "queryComplete", null);
});
Expand Down Expand Up @@ -309,9 +376,29 @@ export default class VariantInterpreterRearrangementGrid extends LitElement {
this.table = $("#" + this.gridId);
this.table.bootstrapTable("destroy");
this.table.bootstrapTable({
data: variants,
columns: this._getDefaultColumns(),
sidePagination: "local",
sidePagination: "server",
// Josemi Note 2024-01-31: we have added the ajax function for local variants for getting genes info
// and map the genes to each variant
ajax: params => {
const tableOptions = $(this.table).bootstrapTable("getOptions");
const limit = params.data.limit || tableOptions.pageSize;
const skip = params.data.offset || 0;
const rows = variants.slice(skip, skip + limit);

// Generate map of genes to variants
this.generateGenesMapFromVariants(rows)
.then(() => params.success(rows))
.catch(error => params.error(error));
},
// Josemi Note 2024-01-31: we use this method to tell bootstrap-table how many rows we have in our data
responseHandler: response => {
return {
total: variants.length,
rows: response,
};
},

iconsPrefix: GridCommons.GRID_ICONS_PREFIX,
icons: GridCommons.GRID_ICONS,

Expand Down Expand Up @@ -509,7 +596,9 @@ export default class VariantInterpreterRearrangementGrid extends LitElement {
field: "gene",
rowspan: 2,
colspan: 1,
formatter: (value, row, index) => VariantGridFormatter.geneFormatter(row[0], index, this.query, this.opencgaSession),
formatter: (value, row) => {
return VariantInterpreterGridFormatter.rearrangementGeneFormatter(row, this.genesByVariant, this.opencgaSession);
},
halign: "center",
visible: this.gridCommons.isColumnVisible("gene"),
},
Expand Down Expand Up @@ -607,7 +696,7 @@ export default class VariantInterpreterRearrangementGrid extends LitElement {
colspan: 1,
rowspan: 1,
formatter: (value, rows) => {
return VariantInterpreterGridFormatter.geneFeatureOverlapFormatter(rows[0], this.opencgaSession);
return VariantInterpreterGridFormatter.rearrangementFeatureOverlapFormatter(rows[0], this.genesByVariant[rows[0].id], this.opencgaSession);
},
halign: "center",
valign: "top",
Expand All @@ -619,7 +708,7 @@ export default class VariantInterpreterRearrangementGrid extends LitElement {
colspan: 1,
rowspan: 1,
formatter: (value, rows) => {
return VariantInterpreterGridFormatter.geneFeatureOverlapFormatter(rows[1], this.opencgaSession);
return VariantInterpreterGridFormatter.rearrangementFeatureOverlapFormatter(rows[1], this.genesByVariant[rows[1].id], this.opencgaSession);
},
halign: "center",
valign: "top",
Expand Down

0 comments on commit c389fe7

Please sign in to comment.